Compare commits
	
		
			51 Commits
		
	
	
		
			2014.11.24
			...
			2014.11.26
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					7c8ea53b96 | ||
| 
						 | 
					dcddc10a50 | ||
| 
						 | 
					a1008af412 | ||
| 
						 | 
					61c0663c1e | ||
| 
						 | 
					81a7a521c5 | ||
| 
						 | 
					e293711802 | ||
| 
						 | 
					ceb3367320 | ||
| 
						 | 
					a03aaaed2e | ||
| 
						 | 
					e075a44afb | ||
| 
						 | 
					8865bdeb37 | ||
| 
						 | 
					3aa578cad2 | ||
| 
						 | 
					d3b5101a91 | ||
| 
						 | 
					5c32110114 | ||
| 
						 | 
					24144e3b8d | ||
| 
						 | 
					b3034f9df7 | ||
| 
						 | 
					4c6d2ff8dc | ||
| 
						 | 
					faf3494894 | ||
| 
						 | 
					535a66ef66 | ||
| 
						 | 
					5c40bba82f | ||
| 
						 | 
					855dc479c2 | ||
| 
						 | 
					0792d5634e | ||
| 
						 | 
					e91cdcae1a | ||
| 
						 | 
					27e1400f55 | ||
| 
						 | 
					e0938e7731 | ||
| 
						 | 
					b72823a0a4 | ||
| 
						 | 
					673cf0e773 | ||
| 
						 | 
					f8aace93cd | ||
| 
						 | 
					80310134e0 | ||
| 
						 | 
					4d2d638df4 | ||
| 
						 | 
					0e44f90e18 | ||
| 
						 | 
					15938ab67a | ||
| 
						 | 
					ab4ee31eb1 | ||
| 
						 | 
					b061ea6e9f | ||
| 
						 | 
					4aae94f9d0 | ||
| 
						 | 
					acda92f6bc | ||
| 
						 | 
					ddfd0f2727 | ||
| 
						 | 
					d0720e7118 | ||
| 
						 | 
					4e262a8838 | ||
| 
						 | 
					b9ed3af343 | ||
| 
						 | 
					63c9b2c1d9 | ||
| 
						 | 
					65f3a228b1 | ||
| 
						 | 
					3004ae2c3a | ||
| 
						 | 
					d9836a5917 | ||
| 
						 | 
					be64b5b098 | ||
| 
						 | 
					c3e74731c2 | ||
| 
						 | 
					c920d7f00d | ||
| 
						 | 
					0bbf12239c | ||
| 
						 | 
					70d68eb46f | ||
| 
						 | 
					c553fe5d29 | ||
| 
						 | 
					f0c3d729d7 | ||
| 
						 | 
					1cdedfee10 | 
							
								
								
									
										2
									
								
								AUTHORS
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								AUTHORS
									
									
									
									
									
								
							@@ -86,3 +86,5 @@ Mauroy Sébastien
 | 
			
		||||
William Sewell
 | 
			
		||||
Dao Hoang Son
 | 
			
		||||
Oskar Jauch
 | 
			
		||||
Matthew Rayfield
 | 
			
		||||
t0mm0
 | 
			
		||||
 
 | 
			
		||||
@@ -30,7 +30,7 @@ Alternatively, refer to the developer instructions below for how to check out an
 | 
			
		||||
# DESCRIPTION
 | 
			
		||||
**youtube-dl** is a small command-line program to download videos from
 | 
			
		||||
YouTube.com and a few more sites. It requires the Python interpreter, version
 | 
			
		||||
2.6, 2.7, or 3.3+, and it is not platform specific. It should work on
 | 
			
		||||
2.6, 2.7, or 3.2+, and it is not platform specific. It should work on
 | 
			
		||||
your Unix box, on Windows or on Mac OS X. It is released to the public domain,
 | 
			
		||||
which means you can modify it, redistribute it or use it however you like.
 | 
			
		||||
 | 
			
		||||
@@ -93,7 +93,8 @@ which means you can modify it, redistribute it or use it however you like.
 | 
			
		||||
                                     COUNT views
 | 
			
		||||
    --max-views COUNT                Do not download any videos with more than
 | 
			
		||||
                                     COUNT views
 | 
			
		||||
    --no-playlist                    download only the currently playing video
 | 
			
		||||
    --no-playlist                    If the URL refers to a video and a
 | 
			
		||||
                                     playlist, download only the video.
 | 
			
		||||
    --age-limit YEARS                download only videos suitable for the given
 | 
			
		||||
                                     age
 | 
			
		||||
    --download-archive FILE          Download only videos not listed in the
 | 
			
		||||
@@ -492,14 +493,15 @@ If you want to add support for a new site, you can follow this quick list (assum
 | 
			
		||||
 | 
			
		||||
        def _real_extract(self, url):
 | 
			
		||||
            video_id = self._match_id(url)
 | 
			
		||||
            webpage = self._download_webpage(url, video_id)
 | 
			
		||||
 | 
			
		||||
            # TODO more code goes here, for example ...
 | 
			
		||||
            webpage = self._download_webpage(url, video_id)
 | 
			
		||||
            title = self._html_search_regex(r'<h1>(.*?)</h1>', webpage, 'title')
 | 
			
		||||
 | 
			
		||||
            return {
 | 
			
		||||
                'id': video_id,
 | 
			
		||||
                'title': title,
 | 
			
		||||
                'description': self._og_search_description(webpage),
 | 
			
		||||
                # TODO more properties (see youtube_dl/extractor/common.py)
 | 
			
		||||
            }
 | 
			
		||||
    ```
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,6 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
from os.path import dirname as dirn
 | 
			
		||||
import sys
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
This script employs a VERY basic heuristic ('porn' in webpage.lower()) to check
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
import json
 | 
			
		||||
import sys
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,6 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
import hashlib
 | 
			
		||||
import urllib.request
 | 
			
		||||
import json
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
from __future__ import unicode_literals, with_statement
 | 
			
		||||
 | 
			
		||||
import rsa
 | 
			
		||||
import json
 | 
			
		||||
@@ -29,4 +30,5 @@ signature = hexlify(rsa.pkcs1.sign(json.dumps(versions_info, sort_keys=True).enc
 | 
			
		||||
print('signature: ' + signature)
 | 
			
		||||
 | 
			
		||||
versions_info['signature'] = signature
 | 
			
		||||
json.dump(versions_info, open('update/versions.json', 'w'), indent=4, sort_keys=True)
 | 
			
		||||
with open('update/versions.json', 'w') as versionsf:
 | 
			
		||||
    json.dump(versions_info, versionsf, indent=4, sort_keys=True)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# coding: utf-8
 | 
			
		||||
 | 
			
		||||
from __future__ import with_statement
 | 
			
		||||
from __future__ import with_statement, unicode_literals
 | 
			
		||||
 | 
			
		||||
import datetime
 | 
			
		||||
import glob
 | 
			
		||||
@@ -13,7 +13,7 @@ year = str(datetime.datetime.now().year)
 | 
			
		||||
for fn in glob.glob('*.html*'):
 | 
			
		||||
    with io.open(fn, encoding='utf-8') as f:
 | 
			
		||||
        content = f.read()
 | 
			
		||||
    newc = re.sub(u'(?P<copyright>Copyright © 2006-)(?P<year>[0-9]{4})', u'Copyright © 2006-' + year, content)
 | 
			
		||||
    newc = re.sub(r'(?P<copyright>Copyright © 2006-)(?P<year>[0-9]{4})', 'Copyright © 2006-' + year, content)
 | 
			
		||||
    if content != newc:
 | 
			
		||||
        tmpFn = fn + '.part'
 | 
			
		||||
        with io.open(tmpFn, 'wt', encoding='utf-8') as outf:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
import datetime
 | 
			
		||||
import io
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
import sys
 | 
			
		||||
import os
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
import io
 | 
			
		||||
import sys
 | 
			
		||||
import re
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
import io
 | 
			
		||||
import os.path
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,6 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
from os.path import dirname as dirn
 | 
			
		||||
import sys
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								setup.py
									
									
									
									
									
								
							@@ -102,7 +102,9 @@ setup(
 | 
			
		||||
        "Programming Language :: Python :: 2.6",
 | 
			
		||||
        "Programming Language :: Python :: 2.7",
 | 
			
		||||
        "Programming Language :: Python :: 3",
 | 
			
		||||
        "Programming Language :: Python :: 3.3"
 | 
			
		||||
        "Programming Language :: Python :: 3.2",
 | 
			
		||||
        "Programming Language :: Python :: 3.3",
 | 
			
		||||
        "Programming Language :: Python :: 3.4",
 | 
			
		||||
    ],
 | 
			
		||||
 | 
			
		||||
    **params
 | 
			
		||||
 
 | 
			
		||||
@@ -97,7 +97,7 @@ def generator(test_case):
 | 
			
		||||
            return
 | 
			
		||||
        for other_ie in other_ies:
 | 
			
		||||
            if not other_ie.working():
 | 
			
		||||
                print_skipping(u'test depends on %sIE, marked as not WORKING' % other_ie.ie_key())
 | 
			
		||||
                print_skipping('test depends on %sIE, marked as not WORKING' % other_ie.ie_key())
 | 
			
		||||
                return
 | 
			
		||||
 | 
			
		||||
        params = get_params(test_case.get('params', {}))
 | 
			
		||||
@@ -143,7 +143,7 @@ def generator(test_case):
 | 
			
		||||
                        raise
 | 
			
		||||
 | 
			
		||||
                    if try_num == RETRIES:
 | 
			
		||||
                        report_warning(u'Failed due to network errors, skipping...')
 | 
			
		||||
                        report_warning('Failed due to network errors, skipping...')
 | 
			
		||||
                        return
 | 
			
		||||
 | 
			
		||||
                    print('Retrying: {0} failed tries\n\n##########\n\n'.format(try_num))
 | 
			
		||||
 
 | 
			
		||||
@@ -9,14 +9,13 @@ rootDir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 | 
			
		||||
 | 
			
		||||
IGNORED_FILES = [
 | 
			
		||||
    'setup.py',  # http://bugs.python.org/issue13943
 | 
			
		||||
    'conf.py',
 | 
			
		||||
    'buildserver.py',
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestUnicodeLiterals(unittest.TestCase):
 | 
			
		||||
    def test_all_files(self):
 | 
			
		||||
        print('Skipping this test (not yet fully implemented)')
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
        for dirpath, _, filenames in os.walk(rootDir):
 | 
			
		||||
            for basename in filenames:
 | 
			
		||||
                if not basename.endswith('.py'):
 | 
			
		||||
@@ -30,10 +29,10 @@ class TestUnicodeLiterals(unittest.TestCase):
 | 
			
		||||
 | 
			
		||||
                if "'" not in code and '"' not in code:
 | 
			
		||||
                    continue
 | 
			
		||||
                imps = 'from __future__ import unicode_literals'
 | 
			
		||||
                self.assertTrue(
 | 
			
		||||
                    imps in code,
 | 
			
		||||
                    ' %s  missing in %s' % (imps, fn))
 | 
			
		||||
                self.assertRegexpMatches(
 | 
			
		||||
                    code,
 | 
			
		||||
                    r'(?:#.*\n*)?from __future__ import (?:[a-z_]+,\s*)*unicode_literals',
 | 
			
		||||
                    'unicode_literals import  missing in %s' % fn)
 | 
			
		||||
 | 
			
		||||
                m = re.search(r'(?<=\s)u[\'"](?!\)|,|$)', code)
 | 
			
		||||
                if m is not None:
 | 
			
		||||
 
 | 
			
		||||
@@ -47,6 +47,7 @@ from youtube_dl.utils import (
 | 
			
		||||
    js_to_json,
 | 
			
		||||
    intlist_to_bytes,
 | 
			
		||||
    args_to_str,
 | 
			
		||||
    parse_filesize,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -170,7 +171,7 @@ class TestUtil(unittest.TestCase):
 | 
			
		||||
        self.assertEqual(find('media:song/url').text, 'http://server.com/download.mp3')
 | 
			
		||||
 | 
			
		||||
    def test_smuggle_url(self):
 | 
			
		||||
        data = {u"ö": u"ö", u"abc": [3]}
 | 
			
		||||
        data = {"ö": "ö", "abc": [3]}
 | 
			
		||||
        url = 'https://foo.bar/baz?x=y#a'
 | 
			
		||||
        smug_url = smuggle_url(url, data)
 | 
			
		||||
        unsmug_url, unsmug_data = unsmuggle_url(smug_url)
 | 
			
		||||
@@ -367,5 +368,14 @@ class TestUtil(unittest.TestCase):
 | 
			
		||||
            'foo ba/r -baz \'2 be\' \'\''
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_parse_filesize(self):
 | 
			
		||||
        self.assertEqual(parse_filesize(None), None)
 | 
			
		||||
        self.assertEqual(parse_filesize(''), None)
 | 
			
		||||
        self.assertEqual(parse_filesize('91 B'), 91)
 | 
			
		||||
        self.assertEqual(parse_filesize('foobar'), None)
 | 
			
		||||
        self.assertEqual(parse_filesize('2 MiB'), 2097152)
 | 
			
		||||
        self.assertEqual(parse_filesize('5 GB'), 5000000000)
 | 
			
		||||
        self.assertEqual(parse_filesize('1.2Tb'), 1200000000000)
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    unittest.main()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# coding: utf-8
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
# Allow direct execution
 | 
			
		||||
import os
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# coding: utf-8
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
# Allow direct execution
 | 
			
		||||
import os
 | 
			
		||||
@@ -32,7 +33,7 @@ params = get_params({
 | 
			
		||||
TEST_ID = 'BaW_jenozKc'
 | 
			
		||||
INFO_JSON_FILE = TEST_ID + '.info.json'
 | 
			
		||||
DESCRIPTION_FILE = TEST_ID + '.mp4.description'
 | 
			
		||||
EXPECTED_DESCRIPTION = u'''test chars:  "'/\ä↭𝕐
 | 
			
		||||
EXPECTED_DESCRIPTION = '''test chars:  "'/\ä↭𝕐
 | 
			
		||||
test URL: https://github.com/rg3/youtube-dl/issues/1892
 | 
			
		||||
 | 
			
		||||
This is a test video for youtube-dl.
 | 
			
		||||
@@ -53,11 +54,11 @@ class TestInfoJSON(unittest.TestCase):
 | 
			
		||||
        self.assertTrue(os.path.exists(INFO_JSON_FILE))
 | 
			
		||||
        with io.open(INFO_JSON_FILE, 'r', encoding='utf-8') as jsonf:
 | 
			
		||||
            jd = json.load(jsonf)
 | 
			
		||||
        self.assertEqual(jd['upload_date'], u'20121002')
 | 
			
		||||
        self.assertEqual(jd['upload_date'], '20121002')
 | 
			
		||||
        self.assertEqual(jd['description'], EXPECTED_DESCRIPTION)
 | 
			
		||||
        self.assertEqual(jd['id'], TEST_ID)
 | 
			
		||||
        self.assertEqual(jd['extractor'], 'youtube')
 | 
			
		||||
        self.assertEqual(jd['title'], u'''youtube-dl test video "'/\ä↭𝕐''')
 | 
			
		||||
        self.assertEqual(jd['title'], '''youtube-dl test video "'/\ä↭𝕐''')
 | 
			
		||||
        self.assertEqual(jd['uploader'], 'Philipp Hagemeister')
 | 
			
		||||
 | 
			
		||||
        self.assertTrue(os.path.exists(DESCRIPTION_FILE))
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
# Allow direct execution
 | 
			
		||||
import os
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
# Execute with
 | 
			
		||||
# $ python youtube_dl/__main__.py (2.6+)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
__all__ = ['aes_encrypt', 'key_expansion', 'aes_ctr_decrypt', 'aes_cbc_decrypt', 'aes_decrypt_text']
 | 
			
		||||
 | 
			
		||||
import base64
 | 
			
		||||
 
 | 
			
		||||
@@ -270,7 +270,7 @@ if sys.version_info < (3, 0):
 | 
			
		||||
        print(s.encode(preferredencoding(), 'xmlcharrefreplace'))
 | 
			
		||||
else:
 | 
			
		||||
    def compat_print(s):
 | 
			
		||||
        assert type(s) == type(u'')
 | 
			
		||||
        assert isinstance(s, compat_str)
 | 
			
		||||
        print(s)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -28,14 +28,14 @@ class HlsFD(FileDownloader):
 | 
			
		||||
            if check_executable(program, ['-version']):
 | 
			
		||||
                break
 | 
			
		||||
        else:
 | 
			
		||||
            self.report_error(u'm3u8 download detected but ffmpeg or avconv could not be found. Please install one.')
 | 
			
		||||
            self.report_error('m3u8 download detected but ffmpeg or avconv could not be found. Please install one.')
 | 
			
		||||
            return False
 | 
			
		||||
        cmd = [program] + args
 | 
			
		||||
 | 
			
		||||
        retval = subprocess.call(cmd)
 | 
			
		||||
        if retval == 0:
 | 
			
		||||
            fsize = os.path.getsize(encodeFilename(tmpfilename))
 | 
			
		||||
            self.to_screen(u'\r[%s] %s bytes' % (cmd[0], fsize))
 | 
			
		||||
            self.to_screen('\r[%s] %s bytes' % (cmd[0], fsize))
 | 
			
		||||
            self.try_rename(tmpfilename, filename)
 | 
			
		||||
            self._hook_progress({
 | 
			
		||||
                'downloaded_bytes': fsize,
 | 
			
		||||
@@ -45,8 +45,8 @@ class HlsFD(FileDownloader):
 | 
			
		||||
            })
 | 
			
		||||
            return True
 | 
			
		||||
        else:
 | 
			
		||||
            self.to_stderr(u"\n")
 | 
			
		||||
            self.report_error(u'%s exited with code %d' % (program, retval))
 | 
			
		||||
            self.to_stderr('\n')
 | 
			
		||||
            self.report_error('%s exited with code %d' % (program, retval))
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
import time
 | 
			
		||||
 | 
			
		||||
@@ -106,7 +108,7 @@ class HttpFD(FileDownloader):
 | 
			
		||||
                self.report_retry(count, retries)
 | 
			
		||||
 | 
			
		||||
        if count > retries:
 | 
			
		||||
            self.report_error(u'giving up after %s retries' % retries)
 | 
			
		||||
            self.report_error('giving up after %s retries' % retries)
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        data_len = data.info().get('Content-length', None)
 | 
			
		||||
@@ -124,10 +126,10 @@ class HttpFD(FileDownloader):
 | 
			
		||||
            min_data_len = self.params.get("min_filesize", None)
 | 
			
		||||
            max_data_len = self.params.get("max_filesize", None)
 | 
			
		||||
            if min_data_len is not None and data_len < min_data_len:
 | 
			
		||||
                self.to_screen(u'\r[download] File is smaller than min-filesize (%s bytes < %s bytes). Aborting.' % (data_len, min_data_len))
 | 
			
		||||
                self.to_screen('\r[download] File is smaller than min-filesize (%s bytes < %s bytes). Aborting.' % (data_len, min_data_len))
 | 
			
		||||
                return False
 | 
			
		||||
            if max_data_len is not None and data_len > max_data_len:
 | 
			
		||||
                self.to_screen(u'\r[download] File is larger than max-filesize (%s bytes > %s bytes). Aborting.' % (data_len, max_data_len))
 | 
			
		||||
                self.to_screen('\r[download] File is larger than max-filesize (%s bytes > %s bytes). Aborting.' % (data_len, max_data_len))
 | 
			
		||||
                return False
 | 
			
		||||
 | 
			
		||||
        data_len_str = format_bytes(data_len)
 | 
			
		||||
@@ -151,13 +153,13 @@ class HttpFD(FileDownloader):
 | 
			
		||||
                    filename = self.undo_temp_name(tmpfilename)
 | 
			
		||||
                    self.report_destination(filename)
 | 
			
		||||
                except (OSError, IOError) as err:
 | 
			
		||||
                    self.report_error(u'unable to open for writing: %s' % str(err))
 | 
			
		||||
                    self.report_error('unable to open for writing: %s' % str(err))
 | 
			
		||||
                    return False
 | 
			
		||||
            try:
 | 
			
		||||
                stream.write(data_block)
 | 
			
		||||
            except (IOError, OSError) as err:
 | 
			
		||||
                self.to_stderr(u"\n")
 | 
			
		||||
                self.report_error(u'unable to write data: %s' % str(err))
 | 
			
		||||
                self.to_stderr('\n')
 | 
			
		||||
                self.report_error('unable to write data: %s' % str(err))
 | 
			
		||||
                return False
 | 
			
		||||
            if not self.params.get('noresizebuffer', False):
 | 
			
		||||
                block_size = self.best_block_size(after - before, len(data_block))
 | 
			
		||||
@@ -188,10 +190,10 @@ class HttpFD(FileDownloader):
 | 
			
		||||
            self.slow_down(start, byte_counter - resume_len)
 | 
			
		||||
 | 
			
		||||
        if stream is None:
 | 
			
		||||
            self.to_stderr(u"\n")
 | 
			
		||||
            self.report_error(u'Did not get any data blocks')
 | 
			
		||||
            self.to_stderr('\n')
 | 
			
		||||
            self.report_error('Did not get any data blocks')
 | 
			
		||||
            return False
 | 
			
		||||
        if tmpfilename != u'-':
 | 
			
		||||
        if tmpfilename != '-':
 | 
			
		||||
            stream.close()
 | 
			
		||||
        self.report_finish(data_len_str, (time.time() - start))
 | 
			
		||||
        if data_len is not None and byte_counter != data_len:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,10 @@
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
import subprocess
 | 
			
		||||
 | 
			
		||||
from .common import FileDownloader
 | 
			
		||||
from ..compat import compat_subprocess_get_DEVNULL
 | 
			
		||||
from ..utils import (
 | 
			
		||||
    encodeFilename,
 | 
			
		||||
)
 | 
			
		||||
@@ -13,19 +16,23 @@ class MplayerFD(FileDownloader):
 | 
			
		||||
        self.report_destination(filename)
 | 
			
		||||
        tmpfilename = self.temp_name(filename)
 | 
			
		||||
 | 
			
		||||
        args = ['mplayer', '-really-quiet', '-vo', 'null', '-vc', 'dummy', '-dumpstream', '-dumpfile', tmpfilename, url]
 | 
			
		||||
        args = [
 | 
			
		||||
            'mplayer', '-really-quiet', '-vo', 'null', '-vc', 'dummy',
 | 
			
		||||
            '-dumpstream', '-dumpfile', tmpfilename, url]
 | 
			
		||||
        # Check for mplayer first
 | 
			
		||||
        try:
 | 
			
		||||
            subprocess.call(['mplayer', '-h'], stdout=(open(os.path.devnull, 'w')), stderr=subprocess.STDOUT)
 | 
			
		||||
            subprocess.call(
 | 
			
		||||
                ['mplayer', '-h'],
 | 
			
		||||
                stdout=compat_subprocess_get_DEVNULL(), stderr=subprocess.STDOUT)
 | 
			
		||||
        except (OSError, IOError):
 | 
			
		||||
            self.report_error(u'MMS or RTSP download detected but "%s" could not be run' % args[0])
 | 
			
		||||
            self.report_error('MMS or RTSP download detected but "%s" could not be run' % args[0])
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        # Download using mplayer.
 | 
			
		||||
        retval = subprocess.call(args)
 | 
			
		||||
        if retval == 0:
 | 
			
		||||
            fsize = os.path.getsize(encodeFilename(tmpfilename))
 | 
			
		||||
            self.to_screen(u'\r[%s] %s bytes' % (args[0], fsize))
 | 
			
		||||
            self.to_screen('\r[%s] %s bytes' % (args[0], fsize))
 | 
			
		||||
            self.try_rename(tmpfilename, filename)
 | 
			
		||||
            self._hook_progress({
 | 
			
		||||
                'downloaded_bytes': fsize,
 | 
			
		||||
@@ -35,6 +42,6 @@ class MplayerFD(FileDownloader):
 | 
			
		||||
            })
 | 
			
		||||
            return True
 | 
			
		||||
        else:
 | 
			
		||||
            self.to_stderr(u"\n")
 | 
			
		||||
            self.report_error(u'mplayer exited with code %d' % retval)
 | 
			
		||||
            self.to_stderr('\n')
 | 
			
		||||
            self.report_error('mplayer exited with code %d' % retval)
 | 
			
		||||
            return False
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
from .abc import ABCIE
 | 
			
		||||
from .academicearth import AcademicEarthCourseIE
 | 
			
		||||
from .addanime import AddAnimeIE
 | 
			
		||||
@@ -373,6 +375,7 @@ from .syfy import SyfyIE
 | 
			
		||||
from .sztvhu import SztvHuIE
 | 
			
		||||
from .tagesschau import TagesschauIE
 | 
			
		||||
from .tapely import TapelyIE
 | 
			
		||||
from .tass import TassIE
 | 
			
		||||
from .teachertube import (
 | 
			
		||||
    TeacherTubeIE,
 | 
			
		||||
    TeacherTubeUserIE,
 | 
			
		||||
@@ -393,6 +396,7 @@ from .thesixtyone import TheSixtyOneIE
 | 
			
		||||
from .thisav import ThisAVIE
 | 
			
		||||
from .tinypic import TinyPicIE
 | 
			
		||||
from .tlc import TlcIE, TlcDeIE
 | 
			
		||||
from .tmz import TMZIE
 | 
			
		||||
from .tnaflix import TNAFlixIE
 | 
			
		||||
from .thvideo import (
 | 
			
		||||
    THVideoIE,
 | 
			
		||||
@@ -483,6 +487,7 @@ from .wrzuta import WrzutaIE
 | 
			
		||||
from .xbef import XBefIE
 | 
			
		||||
from .xboxclips import XboxClipsIE
 | 
			
		||||
from .xhamster import XHamsterIE
 | 
			
		||||
from .xminus import XMinusIE
 | 
			
		||||
from .xnxx import XNXXIE
 | 
			
		||||
from .xvideos import XVideosIE
 | 
			
		||||
from .xtube import XTubeUserIE, XTubeIE
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
import re
 | 
			
		||||
 | 
			
		||||
from .common import InfoExtractor
 | 
			
		||||
@@ -18,15 +19,14 @@ class AcademicEarthCourseIE(InfoExtractor):
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    def _real_extract(self, url):
 | 
			
		||||
        m = re.match(self._VALID_URL, url)
 | 
			
		||||
        playlist_id = m.group('id')
 | 
			
		||||
        playlist_id = self._match_id(url)
 | 
			
		||||
 | 
			
		||||
        webpage = self._download_webpage(url, playlist_id)
 | 
			
		||||
        title = self._html_search_regex(
 | 
			
		||||
            r'<h1 class="playlist-name"[^>]*?>(.*?)</h1>', webpage, u'title')
 | 
			
		||||
            r'<h1 class="playlist-name"[^>]*?>(.*?)</h1>', webpage, 'title')
 | 
			
		||||
        description = self._html_search_regex(
 | 
			
		||||
            r'<p class="excerpt"[^>]*?>(.*?)</p>',
 | 
			
		||||
            webpage, u'description', fatal=False)
 | 
			
		||||
            webpage, 'description', fatal=False)
 | 
			
		||||
        urls = re.findall(
 | 
			
		||||
            r'<li class="lecture-preview">\s*?<a target="_blank" href="([^"]+)">',
 | 
			
		||||
            webpage)
 | 
			
		||||
 
 | 
			
		||||
@@ -15,8 +15,7 @@ from ..utils import (
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AddAnimeIE(InfoExtractor):
 | 
			
		||||
 | 
			
		||||
    _VALID_URL = r'^http://(?:\w+\.)?add-anime\.net/watch_video\.php\?(?:.*?)v=(?P<video_id>[\w_]+)(?:.*)'
 | 
			
		||||
    _VALID_URL = r'^http://(?:\w+\.)?add-anime\.net/watch_video\.php\?(?:.*?)v=(?P<id>[\w_]+)(?:.*)'
 | 
			
		||||
    _TEST = {
 | 
			
		||||
        'url': 'http://www.add-anime.net/watch_video.php?v=24MR3YO5SAS9',
 | 
			
		||||
        'md5': '72954ea10bc979ab5e2eb288b21425a0',
 | 
			
		||||
@@ -29,9 +28,9 @@ class AddAnimeIE(InfoExtractor):
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    def _real_extract(self, url):
 | 
			
		||||
        video_id = self._match_id(url)
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            mobj = re.match(self._VALID_URL, url)
 | 
			
		||||
            video_id = mobj.group('video_id')
 | 
			
		||||
            webpage = self._download_webpage(url, video_id)
 | 
			
		||||
        except ExtractorError as ee:
 | 
			
		||||
            if not isinstance(ee.cause, compat_HTTPError) or \
 | 
			
		||||
@@ -49,7 +48,7 @@ class AddAnimeIE(InfoExtractor):
 | 
			
		||||
                r'a\.value = ([0-9]+)[+]([0-9]+)[*]([0-9]+);',
 | 
			
		||||
                redir_webpage)
 | 
			
		||||
            if av is None:
 | 
			
		||||
                raise ExtractorError(u'Cannot find redirect math task')
 | 
			
		||||
                raise ExtractorError('Cannot find redirect math task')
 | 
			
		||||
            av_res = int(av.group(1)) + int(av.group(2)) * int(av.group(3))
 | 
			
		||||
 | 
			
		||||
            parsed_url = compat_urllib_parse_urlparse(url)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,4 @@
 | 
			
		||||
# coding: utf-8
 | 
			
		||||
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
import re
 | 
			
		||||
@@ -26,8 +25,7 @@ class AparatIE(InfoExtractor):
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    def _real_extract(self, url):
 | 
			
		||||
        m = re.match(self._VALID_URL, url)
 | 
			
		||||
        video_id = m.group('id')
 | 
			
		||||
        video_id = self._match_id(url)
 | 
			
		||||
 | 
			
		||||
        # Note: There is an easier-to-parse configuration at
 | 
			
		||||
        # http://www.aparat.com/video/video/config/videohash/%video_id
 | 
			
		||||
@@ -40,15 +38,15 @@ class AparatIE(InfoExtractor):
 | 
			
		||||
        for i, video_url in enumerate(video_urls):
 | 
			
		||||
            req = HEADRequest(video_url)
 | 
			
		||||
            res = self._request_webpage(
 | 
			
		||||
                req, video_id, note=u'Testing video URL %d' % i, errnote=False)
 | 
			
		||||
                req, video_id, note='Testing video URL %d' % i, errnote=False)
 | 
			
		||||
            if res:
 | 
			
		||||
                break
 | 
			
		||||
        else:
 | 
			
		||||
            raise ExtractorError(u'No working video URLs found')
 | 
			
		||||
            raise ExtractorError('No working video URLs found')
 | 
			
		||||
 | 
			
		||||
        title = self._search_regex(r'\s+title:\s*"([^"]+)"', webpage, u'title')
 | 
			
		||||
        title = self._search_regex(r'\s+title:\s*"([^"]+)"', webpage, 'title')
 | 
			
		||||
        thumbnail = self._search_regex(
 | 
			
		||||
            r'\s+image:\s*"([^"]+)"', webpage, u'thumbnail', fatal=False)
 | 
			
		||||
            r'\s+image:\s*"([^"]+)"', webpage, 'thumbnail', fatal=False)
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
            'id': video_id,
 | 
			
		||||
 
 | 
			
		||||
@@ -80,7 +80,7 @@ class AppleTrailersIE(InfoExtractor):
 | 
			
		||||
            def _clean_json(m):
 | 
			
		||||
                return 'iTunes.playURL(%s);' % m.group(1).replace('\'', ''')
 | 
			
		||||
            s = re.sub(self._JSON_RE, _clean_json, s)
 | 
			
		||||
            s = '<html>' + s + u'</html>'
 | 
			
		||||
            s = '<html>%s</html>' % s
 | 
			
		||||
            return s
 | 
			
		||||
        doc = self._download_xml(playlist_url, movie, transform_source=fix_html)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,7 @@ class BambuserIE(InfoExtractor):
 | 
			
		||||
    _TEST = {
 | 
			
		||||
        'url': 'http://bambuser.com/v/4050584',
 | 
			
		||||
        # MD5 seems to be flaky, see https://travis-ci.org/rg3/youtube-dl/jobs/14051016#L388
 | 
			
		||||
        # u'md5': 'fba8f7693e48fd4e8641b3fd5539a641',
 | 
			
		||||
        # 'md5': 'fba8f7693e48fd4e8641b3fd5539a641',
 | 
			
		||||
        'info_dict': {
 | 
			
		||||
            'id': '4050584',
 | 
			
		||||
            'ext': 'flv',
 | 
			
		||||
 
 | 
			
		||||
@@ -45,4 +45,4 @@ class CBSIE(InfoExtractor):
 | 
			
		||||
        real_id = self._search_regex(
 | 
			
		||||
            r"video\.settings\.pid\s*=\s*'([^']+)';",
 | 
			
		||||
            webpage, 'real video ID')
 | 
			
		||||
        return self.url_result(u'theplatform:%s' % real_id)
 | 
			
		||||
        return self.url_result('theplatform:%s' % real_id)
 | 
			
		||||
 
 | 
			
		||||
@@ -24,7 +24,7 @@ class ClipfishIE(InfoExtractor):
 | 
			
		||||
            'title': 'FIFA 14 - E3 2013 Trailer',
 | 
			
		||||
            'duration': 82,
 | 
			
		||||
        },
 | 
			
		||||
        u'skip': 'Blocked in the US'
 | 
			
		||||
        'skip': 'Blocked in the US'
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    def _real_extract(self, url):
 | 
			
		||||
@@ -34,7 +34,7 @@ class ClipfishIE(InfoExtractor):
 | 
			
		||||
        info_url = ('http://www.clipfish.de/devxml/videoinfo/%s?ts=%d' %
 | 
			
		||||
                    (video_id, int(time.time())))
 | 
			
		||||
        doc = self._download_xml(
 | 
			
		||||
            info_url, video_id, note=u'Downloading info page')
 | 
			
		||||
            info_url, video_id, note='Downloading info page')
 | 
			
		||||
        title = doc.find('title').text
 | 
			
		||||
        video_url = doc.find('filename').text
 | 
			
		||||
        if video_url is None:
 | 
			
		||||
 
 | 
			
		||||
@@ -296,9 +296,11 @@ class InfoExtractor(object):
 | 
			
		||||
        content = self._webpage_read_content(urlh, url_or_request, video_id, note, errnote, fatal)
 | 
			
		||||
        return (content, urlh)
 | 
			
		||||
 | 
			
		||||
    def _webpage_read_content(self, urlh, url_or_request, video_id, note=None, errnote=None, fatal=True):
 | 
			
		||||
    def _webpage_read_content(self, urlh, url_or_request, video_id, note=None, errnote=None, fatal=True, prefix=None):
 | 
			
		||||
        content_type = urlh.headers.get('Content-Type', '')
 | 
			
		||||
        webpage_bytes = urlh.read()
 | 
			
		||||
        if prefix is not None:
 | 
			
		||||
            webpage_bytes = prefix + webpage_bytes
 | 
			
		||||
        m = re.match(r'[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+\s*;\s*charset=(.+)', content_type)
 | 
			
		||||
        if m:
 | 
			
		||||
            encoding = m.group(1)
 | 
			
		||||
 
 | 
			
		||||
@@ -125,7 +125,7 @@ class EightTracksIE(InfoExtractor):
 | 
			
		||||
            info = {
 | 
			
		||||
                'id': compat_str(track_data['id']),
 | 
			
		||||
                'url': track_data['track_file_stream_url'],
 | 
			
		||||
                'title': track_data['performer'] + u' - ' + track_data['name'],
 | 
			
		||||
                'title': track_data['performer'] + ' - ' + track_data['name'],
 | 
			
		||||
                'raw_title': track_data['name'],
 | 
			
		||||
                'uploader_id': data['user']['login'],
 | 
			
		||||
                'ext': 'm4a',
 | 
			
		||||
 
 | 
			
		||||
@@ -40,8 +40,6 @@ class FranceTVBaseInfoExtractor(InfoExtractor):
 | 
			
		||||
        else:
 | 
			
		||||
            georestricted = False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        formats = []
 | 
			
		||||
        for video in info['videos']:
 | 
			
		||||
            if video['statut'] != 'ONLINE':
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ class GamekingsIE(InfoExtractor):
 | 
			
		||||
        'url': 'http://www.gamekings.tv/videos/phoenix-wright-ace-attorney-dual-destinies-review/',
 | 
			
		||||
        # MD5 is flaky, seems to change regularly
 | 
			
		||||
        # 'md5': '2f32b1f7b80fdc5cb616efb4f387f8a3',
 | 
			
		||||
        u'info_dict': {
 | 
			
		||||
        'info_dict': {
 | 
			
		||||
            'id': '20130811',
 | 
			
		||||
            'ext': 'mp4',
 | 
			
		||||
            'title': 'Phoenix Wright: Ace Attorney \u2013 Dual Destinies Review',
 | 
			
		||||
 
 | 
			
		||||
@@ -445,6 +445,30 @@ class GenericIE(InfoExtractor):
 | 
			
		||||
                'title': 'Rosetta #CometLanding webcast HL 10',
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        # LazyYT
 | 
			
		||||
        {
 | 
			
		||||
            'url': 'http://discourse.ubuntu.com/t/unity-8-desktop-mode-windows-on-mir/1986',
 | 
			
		||||
            'info_dict': {
 | 
			
		||||
                'title': 'Unity 8 desktop-mode windows on Mir! - Ubuntu Discourse',
 | 
			
		||||
            },
 | 
			
		||||
            'playlist_mincount': 2,
 | 
			
		||||
        },
 | 
			
		||||
        # Direct link with incorrect MIME type
 | 
			
		||||
        {
 | 
			
		||||
            'url': 'http://ftp.nluug.nl/video/nluug/2014-11-20_nj14/zaal-2/5_Lennart_Poettering_-_Systemd.webm',
 | 
			
		||||
            'md5': '4ccbebe5f36706d85221f204d7eb5913',
 | 
			
		||||
            'info_dict': {
 | 
			
		||||
                'url': 'http://ftp.nluug.nl/video/nluug/2014-11-20_nj14/zaal-2/5_Lennart_Poettering_-_Systemd.webm',
 | 
			
		||||
                'id': '5_Lennart_Poettering_-_Systemd',
 | 
			
		||||
                'ext': 'webm',
 | 
			
		||||
                'title': '5_Lennart_Poettering_-_Systemd',
 | 
			
		||||
                'upload_date': '20141120',
 | 
			
		||||
            },
 | 
			
		||||
            'expected_warnings': [
 | 
			
		||||
                'URL could be a direct video link, returning it as such.'
 | 
			
		||||
            ]
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    def report_following_redirect(self, new_url):
 | 
			
		||||
@@ -598,10 +622,28 @@ class GenericIE(InfoExtractor):
 | 
			
		||||
        if not self._downloader.params.get('test', False) and not is_intentional:
 | 
			
		||||
            self._downloader.report_warning('Falling back on generic information extractor.')
 | 
			
		||||
 | 
			
		||||
        if full_response:
 | 
			
		||||
            webpage = self._webpage_read_content(full_response, url, video_id)
 | 
			
		||||
        else:
 | 
			
		||||
            webpage = self._download_webpage(url, video_id)
 | 
			
		||||
        if not full_response:
 | 
			
		||||
            full_response = self._request_webpage(url, video_id)
 | 
			
		||||
 | 
			
		||||
        # Maybe it's a direct link to a video?
 | 
			
		||||
        # Be careful not to download the whole thing!
 | 
			
		||||
        first_bytes = full_response.read(512)
 | 
			
		||||
        if not re.match(r'^\s*<', first_bytes.decode('utf-8', 'replace')):
 | 
			
		||||
            self._downloader.report_warning(
 | 
			
		||||
                'URL could be a direct video link, returning it as such.')
 | 
			
		||||
            upload_date = unified_strdate(
 | 
			
		||||
                head_response.headers.get('Last-Modified'))
 | 
			
		||||
            return {
 | 
			
		||||
                'id': video_id,
 | 
			
		||||
                'title': os.path.splitext(url_basename(url))[0],
 | 
			
		||||
                'direct': True,
 | 
			
		||||
                'url': url,
 | 
			
		||||
                'upload_date': upload_date,
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        webpage = self._webpage_read_content(
 | 
			
		||||
            full_response, url, video_id, prefix=first_bytes)
 | 
			
		||||
 | 
			
		||||
        self.report_extraction(video_id)
 | 
			
		||||
 | 
			
		||||
        # Is it an RSS feed?
 | 
			
		||||
@@ -702,6 +744,12 @@ class GenericIE(InfoExtractor):
 | 
			
		||||
            return _playlist_from_matches(
 | 
			
		||||
                matches, lambda m: unescapeHTML(m[1]))
 | 
			
		||||
 | 
			
		||||
        # Look for lazyYT YouTube embed
 | 
			
		||||
        matches = re.findall(
 | 
			
		||||
            r'class="lazyYT" data-youtube-id="([^"]+)"', webpage)
 | 
			
		||||
        if matches:
 | 
			
		||||
            return _playlist_from_matches(matches, lambda m: unescapeHTML(m))
 | 
			
		||||
 | 
			
		||||
        # Look for embedded Dailymotion player
 | 
			
		||||
        matches = re.findall(
 | 
			
		||||
            r'<iframe[^>]+?src=(["\'])(?P<url>(?:https?:)?//(?:www\.)?dailymotion\.com/embed/video/.+?)\1', webpage)
 | 
			
		||||
 
 | 
			
		||||
@@ -9,14 +9,15 @@ from ..utils import (
 | 
			
		||||
    determine_ext,
 | 
			
		||||
    compat_urllib_parse,
 | 
			
		||||
    compat_urllib_request,
 | 
			
		||||
    int_or_none,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GorillaVidIE(InfoExtractor):
 | 
			
		||||
    IE_DESC = 'GorillaVid.in, daclips.in and movpod.in'
 | 
			
		||||
    IE_DESC = 'GorillaVid.in, daclips.in, movpod.in and fastvideo.in'
 | 
			
		||||
    _VALID_URL = r'''(?x)
 | 
			
		||||
        https?://(?P<host>(?:www\.)?
 | 
			
		||||
            (?:daclips\.in|gorillavid\.in|movpod\.in))/
 | 
			
		||||
            (?:daclips\.in|gorillavid\.in|movpod\.in|fastvideo\.in))/
 | 
			
		||||
        (?:embed-)?(?P<id>[0-9a-zA-Z]+)(?:-[0-9]+x[0-9]+\.html)?
 | 
			
		||||
    '''
 | 
			
		||||
 | 
			
		||||
@@ -49,6 +50,16 @@ class GorillaVidIE(InfoExtractor):
 | 
			
		||||
            'title': 'Micro Pig piglets ready on 16th July 2009-bG0PdrCdxUc',
 | 
			
		||||
            'thumbnail': 're:http://.*\.jpg',
 | 
			
		||||
        }
 | 
			
		||||
    }, {
 | 
			
		||||
        # video with countdown timeout
 | 
			
		||||
        'url': 'http://fastvideo.in/1qmdn1lmsmbw',
 | 
			
		||||
        'md5': '8b87ec3f6564a3108a0e8e66594842ba',
 | 
			
		||||
        'info_dict': {
 | 
			
		||||
            'id': '1qmdn1lmsmbw',
 | 
			
		||||
            'ext': 'mp4',
 | 
			
		||||
            'title': 'Man of Steel - Trailer',
 | 
			
		||||
            'thumbnail': 're:http://.*\.jpg',
 | 
			
		||||
        },
 | 
			
		||||
    }, {
 | 
			
		||||
        'url': 'http://movpod.in/0wguyyxi1yca',
 | 
			
		||||
        'only_matching': True,
 | 
			
		||||
@@ -71,6 +82,12 @@ class GorillaVidIE(InfoExtractor):
 | 
			
		||||
            ''', webpage))
 | 
			
		||||
 | 
			
		||||
        if fields['op'] == 'download1':
 | 
			
		||||
            countdown = int_or_none(self._search_regex(
 | 
			
		||||
                r'<span id="countdown_str">(?:[Ww]ait)?\s*<span id="cxc">(\d+)</span>\s*(?:seconds?)?</span>',
 | 
			
		||||
                webpage, 'countdown', default=None))
 | 
			
		||||
            if countdown:
 | 
			
		||||
                self._sleep(countdown, video_id)
 | 
			
		||||
 | 
			
		||||
            post = compat_urllib_parse.urlencode(fields)
 | 
			
		||||
 | 
			
		||||
            req = compat_urllib_request.Request(url, post)
 | 
			
		||||
@@ -78,9 +95,13 @@ class GorillaVidIE(InfoExtractor):
 | 
			
		||||
 | 
			
		||||
            webpage = self._download_webpage(req, video_id, 'Downloading video page')
 | 
			
		||||
 | 
			
		||||
        title = self._search_regex(r'style="z-index: [0-9]+;">([^<]+)</span>', webpage, 'title')
 | 
			
		||||
        video_url = self._search_regex(r'file\s*:\s*\'(http[^\']+)\',', webpage, 'file url')
 | 
			
		||||
        thumbnail = self._search_regex(r'image\s*:\s*\'(http[^\']+)\',', webpage, 'thumbnail', fatal=False)
 | 
			
		||||
        title = self._search_regex(
 | 
			
		||||
            r'style="z-index: [0-9]+;">([^<]+)</span>',
 | 
			
		||||
            webpage, 'title', default=None) or self._og_search_title(webpage)
 | 
			
		||||
        video_url = self._search_regex(
 | 
			
		||||
            r'file\s*:\s*["\'](http[^"\']+)["\'],', webpage, 'file url')
 | 
			
		||||
        thumbnail = self._search_regex(
 | 
			
		||||
            r'image\s*:\s*["\'](http[^"\']+)["\'],', webpage, 'thumbnail', fatal=False)
 | 
			
		||||
 | 
			
		||||
        formats = [{
 | 
			
		||||
            'format_id': 'sd',
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,13 @@
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
import re
 | 
			
		||||
import base64
 | 
			
		||||
 | 
			
		||||
from .common import InfoExtractor
 | 
			
		||||
from ..utils import (
 | 
			
		||||
from ..compat import (
 | 
			
		||||
    compat_urllib_parse,
 | 
			
		||||
    compat_urllib_request,
 | 
			
		||||
)
 | 
			
		||||
from ..utils import (
 | 
			
		||||
    ExtractorError,
 | 
			
		||||
    HEADRequest,
 | 
			
		||||
)
 | 
			
		||||
@@ -16,25 +17,24 @@ class HotNewHipHopIE(InfoExtractor):
 | 
			
		||||
    _VALID_URL = r'http://www\.hotnewhiphop\.com/.*\.(?P<id>.*)\.html'
 | 
			
		||||
    _TEST = {
 | 
			
		||||
        'url': 'http://www.hotnewhiphop.com/freddie-gibbs-lay-it-down-song.1435540.html',
 | 
			
		||||
        'file': '1435540.mp3',
 | 
			
		||||
        'md5': '2c2cd2f76ef11a9b3b581e8b232f3d96',
 | 
			
		||||
        'info_dict': {
 | 
			
		||||
            'id': '1435540',
 | 
			
		||||
            'ext': 'mp3',
 | 
			
		||||
            'title': 'Freddie Gibbs - Lay It Down'
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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_id = self._match_id(url)
 | 
			
		||||
        webpage = self._download_webpage(url, video_id)
 | 
			
		||||
 | 
			
		||||
        video_url_base64 = self._search_regex(
 | 
			
		||||
            r'data-path="(.*?)"', webpage_src, u'video URL', fatal=False)
 | 
			
		||||
            r'data-path="(.*?)"', webpage, 'video URL', default=None)
 | 
			
		||||
 | 
			
		||||
        if video_url_base64 is None:
 | 
			
		||||
            video_url = self._search_regex(
 | 
			
		||||
                r'"contentUrl" content="(.*?)"', webpage_src, u'video URL')
 | 
			
		||||
                r'"contentUrl" content="(.*?)"', webpage, 'content URL')
 | 
			
		||||
            return self.url_result(video_url, ie='Youtube')
 | 
			
		||||
 | 
			
		||||
        reqdata = compat_urllib_parse.urlencode([
 | 
			
		||||
@@ -59,11 +59,11 @@ class HotNewHipHopIE(InfoExtractor):
 | 
			
		||||
        if video_url.endswith('.html'):
 | 
			
		||||
            raise ExtractorError('Redirect failed')
 | 
			
		||||
 | 
			
		||||
        video_title = self._og_search_title(webpage_src).strip()
 | 
			
		||||
        video_title = self._og_search_title(webpage).strip()
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
            'id': video_id,
 | 
			
		||||
            'url': video_url,
 | 
			
		||||
            'title': video_title,
 | 
			
		||||
            'thumbnail': self._og_search_thumbnail(webpage_src),
 | 
			
		||||
            'thumbnail': self._og_search_thumbnail(webpage),
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -49,7 +49,7 @@ class MooshareIE(InfoExtractor):
 | 
			
		||||
        page = self._download_webpage(url, video_id, 'Downloading page')
 | 
			
		||||
 | 
			
		||||
        if re.search(r'>Video Not Found or Deleted<', page) is not None:
 | 
			
		||||
            raise ExtractorError(u'Video %s does not exist' % video_id, expected=True)
 | 
			
		||||
            raise ExtractorError('Video %s does not exist' % video_id, expected=True)
 | 
			
		||||
 | 
			
		||||
        hash_key = self._html_search_regex(r'<input type="hidden" name="hash" value="([^"]+)">', page, 'hash')
 | 
			
		||||
        title = self._html_search_regex(r'(?m)<div class="blockTitle">\s*<h2>Watch ([^<]+)</h2>', page, 'title')
 | 
			
		||||
 
 | 
			
		||||
@@ -164,7 +164,7 @@ class MTVServicesInfoExtractor(InfoExtractor):
 | 
			
		||||
        if mgid is None or ':' not in mgid:
 | 
			
		||||
            mgid = self._search_regex(
 | 
			
		||||
                [r'data-mgid="(.*?)"', r'swfobject.embedSWF\(".*?(mgid:.*?)"'],
 | 
			
		||||
                webpage, u'mgid')
 | 
			
		||||
                webpage, 'mgid')
 | 
			
		||||
        return self._get_videos_info(mgid)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,64 +1,65 @@
 | 
			
		||||
import re
 | 
			
		||||
import json
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
from .common import InfoExtractor
 | 
			
		||||
from ..utils import (
 | 
			
		||||
from ..compat import (
 | 
			
		||||
    compat_urllib_parse,
 | 
			
		||||
    determine_ext,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MuzuTVIE(InfoExtractor):
 | 
			
		||||
    _VALID_URL = r'https?://www\.muzu\.tv/(.+?)/(.+?)/(?P<id>\d+)'
 | 
			
		||||
    IE_NAME = u'muzu.tv'
 | 
			
		||||
    IE_NAME = 'muzu.tv'
 | 
			
		||||
 | 
			
		||||
    _TEST = {
 | 
			
		||||
        u'url': u'http://www.muzu.tv/defected/marcashken-featuring-sos-cat-walk-original-mix-music-video/1981454/',
 | 
			
		||||
        u'file': u'1981454.mp4',
 | 
			
		||||
        u'md5': u'98f8b2c7bc50578d6a0364fff2bfb000',
 | 
			
		||||
        u'info_dict': {
 | 
			
		||||
            u'title': u'Cat Walk (Original Mix)',
 | 
			
		||||
            u'description': u'md5:90e868994de201b2570e4e5854e19420',
 | 
			
		||||
            u'uploader': u'MarcAshken featuring SOS',
 | 
			
		||||
        'url': 'http://www.muzu.tv/defected/marcashken-featuring-sos-cat-walk-original-mix-music-video/1981454/',
 | 
			
		||||
        'md5': '98f8b2c7bc50578d6a0364fff2bfb000',
 | 
			
		||||
        'info_dict': {
 | 
			
		||||
            'id': '1981454',
 | 
			
		||||
            'ext': 'mp4',
 | 
			
		||||
            'title': 'Cat Walk (Original Mix)',
 | 
			
		||||
            'description': 'md5:90e868994de201b2570e4e5854e19420',
 | 
			
		||||
            'uploader': 'MarcAshken featuring SOS',
 | 
			
		||||
        },
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    def _real_extract(self, url):
 | 
			
		||||
        mobj = re.match(self._VALID_URL, url)
 | 
			
		||||
        video_id = mobj.group('id')
 | 
			
		||||
        video_id = self._match_id(url)
 | 
			
		||||
 | 
			
		||||
        info_data = compat_urllib_parse.urlencode({'format': 'json',
 | 
			
		||||
                                                   'url': url,
 | 
			
		||||
                                                   })
 | 
			
		||||
        video_info_page = self._download_webpage('http://www.muzu.tv/api/oembed/?%s' % info_data,
 | 
			
		||||
                                                 video_id, u'Downloading video info')
 | 
			
		||||
        info = json.loads(video_info_page)
 | 
			
		||||
        info_data = compat_urllib_parse.urlencode({
 | 
			
		||||
            'format': 'json',
 | 
			
		||||
            'url': url,
 | 
			
		||||
        })
 | 
			
		||||
        info = self._download_json(
 | 
			
		||||
            'http://www.muzu.tv/api/oembed/?%s' % info_data,
 | 
			
		||||
            video_id, 'Downloading video info')
 | 
			
		||||
 | 
			
		||||
        player_info_page = self._download_webpage('http://player.muzu.tv/player/playerInit?ai=%s' % video_id,
 | 
			
		||||
                                                  video_id, u'Downloading player info')
 | 
			
		||||
        video_info = json.loads(player_info_page)['videos'][0]
 | 
			
		||||
        player_info = self._download_json(
 | 
			
		||||
            'http://player.muzu.tv/player/playerInit?ai=%s' % video_id,
 | 
			
		||||
            video_id, 'Downloading player info')
 | 
			
		||||
        video_info = player_info['videos'][0]
 | 
			
		||||
        for quality in ['1080', '720', '480', '360']:
 | 
			
		||||
            if video_info.get('v%s' % quality):
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
        data = compat_urllib_parse.urlencode({'ai': video_id,
 | 
			
		||||
                                              # Even if each time you watch a video the hash changes,
 | 
			
		||||
                                              # it seems to work for different videos, and it will work
 | 
			
		||||
                                              # even if you use any non empty string as a hash
 | 
			
		||||
                                              'viewhash': 'VBNff6djeV4HV5TRPW5kOHub2k',
 | 
			
		||||
                                              'device': 'web',
 | 
			
		||||
                                              'qv': quality,
 | 
			
		||||
                                              })
 | 
			
		||||
        video_url_page = self._download_webpage('http://player.muzu.tv/player/requestVideo?%s' % data,
 | 
			
		||||
                                                video_id, u'Downloading video url')
 | 
			
		||||
        video_url_info = json.loads(video_url_page)
 | 
			
		||||
        data = compat_urllib_parse.urlencode({
 | 
			
		||||
            'ai': video_id,
 | 
			
		||||
            # Even if each time you watch a video the hash changes,
 | 
			
		||||
            # it seems to work for different videos, and it will work
 | 
			
		||||
            # even if you use any non empty string as a hash
 | 
			
		||||
            'viewhash': 'VBNff6djeV4HV5TRPW5kOHub2k',
 | 
			
		||||
            'device': 'web',
 | 
			
		||||
            'qv': quality,
 | 
			
		||||
        })
 | 
			
		||||
        video_url_info = self._download_json(
 | 
			
		||||
            'http://player.muzu.tv/player/requestVideo?%s' % data,
 | 
			
		||||
            video_id, 'Downloading video url')
 | 
			
		||||
        video_url = video_url_info['url']
 | 
			
		||||
 | 
			
		||||
        return {'id': video_id,
 | 
			
		||||
                'title': info['title'],
 | 
			
		||||
                'url': video_url,
 | 
			
		||||
                'ext': determine_ext(video_url),
 | 
			
		||||
                'thumbnail': info['thumbnail_url'],
 | 
			
		||||
                'description': info['description'],
 | 
			
		||||
                'uploader': info['author_name'],
 | 
			
		||||
                }
 | 
			
		||||
        return {
 | 
			
		||||
            'id': video_id,
 | 
			
		||||
            'title': info['title'],
 | 
			
		||||
            'url': video_url,
 | 
			
		||||
            'thumbnail': info['thumbnail_url'],
 | 
			
		||||
            'description': info['description'],
 | 
			
		||||
            'uploader': info['author_name'],
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -4,9 +4,11 @@ import re
 | 
			
		||||
import json
 | 
			
		||||
 | 
			
		||||
from .common import InfoExtractor
 | 
			
		||||
from ..utils import (
 | 
			
		||||
from ..compat import (
 | 
			
		||||
    compat_urlparse,
 | 
			
		||||
    compat_urllib_parse,
 | 
			
		||||
)
 | 
			
		||||
from ..utils import (
 | 
			
		||||
    unified_strdate,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -122,7 +124,7 @@ class NHLVideocenterIE(NHLBaseInfoExtractor):
 | 
			
		||||
        response = self._download_webpage(request_url, playlist_title)
 | 
			
		||||
        response = self._fix_json(response)
 | 
			
		||||
        if not response.strip():
 | 
			
		||||
            self._downloader.report_warning(u'Got an empty reponse, trying '
 | 
			
		||||
            self._downloader.report_warning('Got an empty reponse, trying '
 | 
			
		||||
                                            'adding the "newvideos" parameter')
 | 
			
		||||
            response = self._download_webpage(request_url + '&newvideos=true',
 | 
			
		||||
                                              playlist_title)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,5 @@
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
import re
 | 
			
		||||
 | 
			
		||||
from .common import InfoExtractor
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -9,32 +7,23 @@ class RedTubeIE(InfoExtractor):
 | 
			
		||||
    _VALID_URL = r'http://(?:www\.)?redtube\.com/(?P<id>[0-9]+)'
 | 
			
		||||
    _TEST = {
 | 
			
		||||
        'url': 'http://www.redtube.com/66418',
 | 
			
		||||
        'file': '66418.mp4',
 | 
			
		||||
        # md5 varies from time to time, as in
 | 
			
		||||
        # https://travis-ci.org/rg3/youtube-dl/jobs/14052463#L295
 | 
			
		||||
        #'md5': u'7b8c22b5e7098a3e1c09709df1126d2d',
 | 
			
		||||
        'info_dict': {
 | 
			
		||||
            'id': '66418',
 | 
			
		||||
            'ext': 'mp4',
 | 
			
		||||
            "title": "Sucked on a toilet",
 | 
			
		||||
            "age_limit": 18,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    def _real_extract(self, url):
 | 
			
		||||
        mobj = re.match(self._VALID_URL, url)
 | 
			
		||||
 | 
			
		||||
        video_id = mobj.group('id')
 | 
			
		||||
        video_extension = 'mp4'
 | 
			
		||||
        video_id = self._match_id(url)
 | 
			
		||||
        webpage = self._download_webpage(url, video_id)
 | 
			
		||||
 | 
			
		||||
        self.report_extraction(video_id)
 | 
			
		||||
 | 
			
		||||
        video_url = self._html_search_regex(
 | 
			
		||||
            r'<source src="(.+?)" type="video/mp4">', webpage, u'video URL')
 | 
			
		||||
 | 
			
		||||
            r'<source src="(.+?)" type="video/mp4">', webpage, 'video URL')
 | 
			
		||||
        video_title = self._html_search_regex(
 | 
			
		||||
            r'<h1 class="videoTitle[^"]*">(.+?)</h1>',
 | 
			
		||||
            webpage, u'title')
 | 
			
		||||
 | 
			
		||||
            webpage, 'title')
 | 
			
		||||
        video_thumbnail = self._og_search_thumbnail(webpage)
 | 
			
		||||
 | 
			
		||||
        # No self-labeling, but they describe themselves as
 | 
			
		||||
@@ -44,7 +33,7 @@ class RedTubeIE(InfoExtractor):
 | 
			
		||||
        return {
 | 
			
		||||
            'id': video_id,
 | 
			
		||||
            'url': video_url,
 | 
			
		||||
            'ext': video_extension,
 | 
			
		||||
            'ext': 'mp4',
 | 
			
		||||
            'title': video_title,
 | 
			
		||||
            'thumbnail': video_thumbnail,
 | 
			
		||||
            'age_limit': age_limit,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
# encoding: utf-8
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
import json
 | 
			
		||||
import re
 | 
			
		||||
@@ -11,13 +12,14 @@ class SohuIE(InfoExtractor):
 | 
			
		||||
    _VALID_URL = r'https?://(?P<mytv>my\.)?tv\.sohu\.com/.+?/(?(mytv)|n)(?P<id>\d+)\.shtml.*?'
 | 
			
		||||
 | 
			
		||||
    _TEST = {
 | 
			
		||||
        u'url': u'http://tv.sohu.com/20130724/n382479172.shtml#super',
 | 
			
		||||
        u'file': u'382479172.mp4',
 | 
			
		||||
        u'md5': u'bde8d9a6ffd82c63a1eefaef4eeefec7',
 | 
			
		||||
        u'info_dict': {
 | 
			
		||||
            u'title': u'MV:Far East Movement《The Illest》',
 | 
			
		||||
        'url': 'http://tv.sohu.com/20130724/n382479172.shtml#super',
 | 
			
		||||
        'md5': 'bde8d9a6ffd82c63a1eefaef4eeefec7',
 | 
			
		||||
        'info_dict': {
 | 
			
		||||
            'id': '382479172',
 | 
			
		||||
            'ext': 'mp4',
 | 
			
		||||
            'title': 'MV:Far East Movement《The Illest》',
 | 
			
		||||
        },
 | 
			
		||||
        u'skip': u'Only available from China',
 | 
			
		||||
        'skip': 'Only available from China',
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    def _real_extract(self, url):
 | 
			
		||||
@@ -26,11 +28,11 @@ class SohuIE(InfoExtractor):
 | 
			
		||||
            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='
 | 
			
		||||
                base_data_url = '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,
 | 
			
		||||
                note=u'Downloading JSON data for ' + str(vid_id))
 | 
			
		||||
                note='Downloading JSON data for ' + str(vid_id))
 | 
			
		||||
            return json.loads(data_json)
 | 
			
		||||
 | 
			
		||||
        mobj = re.match(self._VALID_URL, url)
 | 
			
		||||
@@ -39,11 +41,11 @@ class SohuIE(InfoExtractor):
 | 
			
		||||
 | 
			
		||||
        webpage = self._download_webpage(url, video_id)
 | 
			
		||||
        raw_title = self._html_search_regex(r'(?s)<title>(.+?)</title>',
 | 
			
		||||
                                            webpage, u'video title')
 | 
			
		||||
                                            webpage, 'video title')
 | 
			
		||||
        title = raw_title.partition('-')[0].strip()
 | 
			
		||||
 | 
			
		||||
        vid = self._html_search_regex(r'var vid ?= ?["\'](\d+)["\']', webpage,
 | 
			
		||||
                                      u'video path')
 | 
			
		||||
                                      'video path')
 | 
			
		||||
        data = _fetch_data(vid, mytv)
 | 
			
		||||
 | 
			
		||||
        QUALITIES = ('ori', 'super', 'high', 'nor')
 | 
			
		||||
@@ -51,7 +53,7 @@ class SohuIE(InfoExtractor):
 | 
			
		||||
                   for q in QUALITIES
 | 
			
		||||
                   if data['data'][q + 'Vid'] != 0]
 | 
			
		||||
        if not vid_ids:
 | 
			
		||||
            raise ExtractorError(u'No formats available for this video')
 | 
			
		||||
            raise ExtractorError('No formats available for this video')
 | 
			
		||||
 | 
			
		||||
        # For now, we just pick the highest available quality
 | 
			
		||||
        vid_id = vid_ids[-1]
 | 
			
		||||
@@ -69,7 +71,7 @@ class SohuIE(InfoExtractor):
 | 
			
		||||
                        (allot, prot, clipsURL[i], su[i]))
 | 
			
		||||
            part_str = self._download_webpage(
 | 
			
		||||
                part_url, video_id,
 | 
			
		||||
                note=u'Downloading part %d of %d' % (i + 1, part_count))
 | 
			
		||||
                note='Downloading part %d of %d' % (i + 1, part_count))
 | 
			
		||||
 | 
			
		||||
            part_info = part_str.split('|')
 | 
			
		||||
            video_url = '%s%s?key=%s' % (part_info[0], su[i], part_info[3])
 | 
			
		||||
 
 | 
			
		||||
@@ -33,5 +33,6 @@ class SpaceIE(InfoExtractor):
 | 
			
		||||
            # Other videos works fine with the info from the object
 | 
			
		||||
            brightcove_url = BrightcoveIE._extract_brightcove_url(webpage)
 | 
			
		||||
        if brightcove_url is None:
 | 
			
		||||
            raise ExtractorError(u'The webpage does not contain a video', expected=True)
 | 
			
		||||
            raise ExtractorError(
 | 
			
		||||
                'The webpage does not contain a video', expected=True)
 | 
			
		||||
        return self.url_result(brightcove_url, BrightcoveIE.ie_key())
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,8 @@
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
from .common import InfoExtractor
 | 
			
		||||
 | 
			
		||||
from ..compat import compat_str
 | 
			
		||||
from ..utils import (
 | 
			
		||||
    compat_str,
 | 
			
		||||
    ExtractorError,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -17,10 +18,10 @@ class SubtitlesInfoExtractor(InfoExtractor):
 | 
			
		||||
        sub_lang_list = self._get_available_subtitles(video_id, webpage)
 | 
			
		||||
        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' %
 | 
			
		||||
        self.to_screen('%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' %
 | 
			
		||||
        self.to_screen('%s: Available automatic captions for video: %s' %
 | 
			
		||||
                       (video_id, auto_lang))
 | 
			
		||||
 | 
			
		||||
    def extract_subtitles(self, video_id, webpage):
 | 
			
		||||
@@ -51,7 +52,7 @@ class SubtitlesInfoExtractor(InfoExtractor):
 | 
			
		||||
            sub_lang_list = {}
 | 
			
		||||
            for sub_lang in requested_langs:
 | 
			
		||||
                if sub_lang not in available_subs_list:
 | 
			
		||||
                    self._downloader.report_warning(u'no closed captions found in the specified language "%s"' % sub_lang)
 | 
			
		||||
                    self._downloader.report_warning('no closed captions found in the specified language "%s"' % sub_lang)
 | 
			
		||||
                    continue
 | 
			
		||||
                sub_lang_list[sub_lang] = available_subs_list[sub_lang]
 | 
			
		||||
 | 
			
		||||
@@ -70,10 +71,10 @@ class SubtitlesInfoExtractor(InfoExtractor):
 | 
			
		||||
        try:
 | 
			
		||||
            sub = self._download_subtitle_url(sub_lang, url)
 | 
			
		||||
        except ExtractorError as err:
 | 
			
		||||
            self._downloader.report_warning(u'unable to download video subtitles for %s: %s' % (sub_lang, compat_str(err)))
 | 
			
		||||
            self._downloader.report_warning('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')
 | 
			
		||||
            self._downloader.report_warning('Did not fetch video subtitles')
 | 
			
		||||
            return
 | 
			
		||||
        return sub
 | 
			
		||||
 | 
			
		||||
@@ -94,5 +95,5 @@ class SubtitlesInfoExtractor(InfoExtractor):
 | 
			
		||||
        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')
 | 
			
		||||
        self._downloader.report_warning('Automatic Captions not supported by this server')
 | 
			
		||||
        return {}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										62
									
								
								youtube_dl/extractor/tass.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								youtube_dl/extractor/tass.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,62 @@
 | 
			
		||||
# encoding: utf-8
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
import json
 | 
			
		||||
 | 
			
		||||
from .common import InfoExtractor
 | 
			
		||||
from ..utils import (
 | 
			
		||||
    js_to_json,
 | 
			
		||||
    qualities,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TassIE(InfoExtractor):
 | 
			
		||||
    _VALID_URL = r'https?://(?:tass\.ru|itar-tass\.com)/[^/]+/(?P<id>\d+)'
 | 
			
		||||
    _TESTS = [
 | 
			
		||||
        {
 | 
			
		||||
            'url': 'http://tass.ru/obschestvo/1586870',
 | 
			
		||||
            'md5': '3b4cdd011bc59174596b6145cda474a4',
 | 
			
		||||
            'info_dict': {
 | 
			
		||||
                'id': '1586870',
 | 
			
		||||
                'ext': 'mp4',
 | 
			
		||||
                'title': 'Посетителям московского зоопарка показали красную панду',
 | 
			
		||||
                'description': 'Приехавшую из Дублина Зейну можно увидеть в павильоне "Кошки тропиков"',
 | 
			
		||||
                'thumbnail': 're:^https?://.*\.jpg$',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            'url': 'http://itar-tass.com/obschestvo/1600009',
 | 
			
		||||
            'only_matching': True,
 | 
			
		||||
        },
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    def _real_extract(self, url):
 | 
			
		||||
        video_id = self._match_id(url)
 | 
			
		||||
 | 
			
		||||
        webpage = self._download_webpage(url, video_id)
 | 
			
		||||
 | 
			
		||||
        sources = json.loads(js_to_json(self._search_regex(
 | 
			
		||||
            r'(?s)sources\s*:\s*(\[.+?\])', webpage, 'sources')))
 | 
			
		||||
 | 
			
		||||
        quality = qualities(['sd', 'hd'])
 | 
			
		||||
 | 
			
		||||
        formats = []
 | 
			
		||||
        for source in sources:
 | 
			
		||||
            video_url = source.get('file')
 | 
			
		||||
            if not video_url or not video_url.startswith('http') or not video_url.endswith('.mp4'):
 | 
			
		||||
                continue
 | 
			
		||||
            label = source.get('label')
 | 
			
		||||
            formats.append({
 | 
			
		||||
                'url': video_url,
 | 
			
		||||
                'format_id': label,
 | 
			
		||||
                'quality': quality(label),
 | 
			
		||||
            })
 | 
			
		||||
        self._sort_formats(formats)
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
            'id': video_id,
 | 
			
		||||
            'title': self._og_search_title(webpage),
 | 
			
		||||
            'description': self._og_search_description(webpage),
 | 
			
		||||
            'thumbnail': self._og_search_thumbnail(webpage),
 | 
			
		||||
            'formats': formats,
 | 
			
		||||
        }
 | 
			
		||||
							
								
								
									
										32
									
								
								youtube_dl/extractor/tmz.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								youtube_dl/extractor/tmz.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
# coding: utf-8
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
from .common import InfoExtractor
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TMZIE(InfoExtractor):
 | 
			
		||||
    _VALID_URL = r'https?://(?:www\.)?tmz\.com/videos/(?P<id>[^/]+)/?'
 | 
			
		||||
    _TEST = {
 | 
			
		||||
        'url': 'http://www.tmz.com/videos/0_okj015ty/',
 | 
			
		||||
        'md5': '791204e3bf790b1426cb2db0706184c0',
 | 
			
		||||
        'info_dict': {
 | 
			
		||||
            'id': '0_okj015ty',
 | 
			
		||||
            'url': 'http://tmz.vo.llnwd.net/o28/2014-03/13/0_okj015ty_0_rt8ro3si_2.mp4',
 | 
			
		||||
            'ext': 'mp4',
 | 
			
		||||
            'title': 'Kim Kardashian\'s Boobs Unlock a Mystery!',
 | 
			
		||||
            'description': 'Did Kim Kardasain try to one-up Khloe by one-upping Kylie???  Or is she just showing off her amazing boobs?',
 | 
			
		||||
            'thumbnail': 'http://cdnbakmi.kaltura.com/p/591531/sp/59153100/thumbnail/entry_id/0_okj015ty/version/100002/acv/182/width/640',
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    def _real_extract(self, url):
 | 
			
		||||
        video_id = self._match_id(url)
 | 
			
		||||
        webpage = self._download_webpage(url, video_id)
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
            'id': video_id,
 | 
			
		||||
            'url': self._html_search_meta('VideoURL', webpage, fatal=True),
 | 
			
		||||
            'title': self._og_search_title(webpage),
 | 
			
		||||
            'description': self._og_search_description(webpage),
 | 
			
		||||
            'thumbnail': self._html_search_meta('ThumbURL', webpage),
 | 
			
		||||
        }
 | 
			
		||||
@@ -1,28 +1,28 @@
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
import json
 | 
			
		||||
import re
 | 
			
		||||
 | 
			
		||||
from .common import InfoExtractor
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TriluliluIE(InfoExtractor):
 | 
			
		||||
    _VALID_URL = r'(?x)(?:https?://)?(?:www\.)?trilulilu\.ro/video-(?P<category>[^/]+)/(?P<video_id>[^/]+)'
 | 
			
		||||
    _VALID_URL = r'https?://(?:www\.)?trilulilu\.ro/video-[^/]+/(?P<id>[^/]+)'
 | 
			
		||||
    _TEST = {
 | 
			
		||||
        u"url": u"http://www.trilulilu.ro/video-animatie/big-buck-bunny-1",
 | 
			
		||||
        u'file': u"big-buck-bunny-1.mp4",
 | 
			
		||||
        u'info_dict': {
 | 
			
		||||
            u"title": u"Big Buck Bunny",
 | 
			
		||||
            u"description": u":) pentru copilul din noi",
 | 
			
		||||
        'url': 'http://www.trilulilu.ro/video-animatie/big-buck-bunny-1',
 | 
			
		||||
        'info_dict': {
 | 
			
		||||
            'id': 'big-buck-bunny-1',
 | 
			
		||||
            'ext': 'mp4',
 | 
			
		||||
            'title': 'Big Buck Bunny',
 | 
			
		||||
            'description': ':) pentru copilul din noi',
 | 
			
		||||
        },
 | 
			
		||||
        # Server ignores Range headers (--test)
 | 
			
		||||
        u"params": {
 | 
			
		||||
            u"skip_download": True
 | 
			
		||||
        'params': {
 | 
			
		||||
            'skip_download': True
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    def _real_extract(self, url):
 | 
			
		||||
        mobj = re.match(self._VALID_URL, url)
 | 
			
		||||
        video_id = mobj.group('video_id')
 | 
			
		||||
 | 
			
		||||
        video_id = self._match_id(url)
 | 
			
		||||
        webpage = self._download_webpage(url, video_id)
 | 
			
		||||
 | 
			
		||||
        title = self._og_search_title(webpage)
 | 
			
		||||
@@ -30,20 +30,20 @@ class TriluliluIE(InfoExtractor):
 | 
			
		||||
        description = self._og_search_description(webpage)
 | 
			
		||||
 | 
			
		||||
        log_str = self._search_regex(
 | 
			
		||||
            r'block_flash_vars[ ]=[ ]({[^}]+})', webpage, u'log info')
 | 
			
		||||
            r'block_flash_vars[ ]=[ ]({[^}]+})', webpage, 'log info')
 | 
			
		||||
        log = json.loads(log_str)
 | 
			
		||||
 | 
			
		||||
        format_url = (u'http://fs%(server)s.trilulilu.ro/%(hash)s/'
 | 
			
		||||
                      u'video-formats2' % log)
 | 
			
		||||
        format_url = ('http://fs%(server)s.trilulilu.ro/%(hash)s/'
 | 
			
		||||
                      'video-formats2' % log)
 | 
			
		||||
        format_doc = self._download_xml(
 | 
			
		||||
            format_url, video_id,
 | 
			
		||||
            note=u'Downloading formats',
 | 
			
		||||
            errnote=u'Error while downloading formats')
 | 
			
		||||
            note='Downloading formats',
 | 
			
		||||
            errnote='Error while downloading formats')
 | 
			
		||||
 | 
			
		||||
        video_url_template = (
 | 
			
		||||
            u'http://fs%(server)s.trilulilu.ro/stream.php?type=video'
 | 
			
		||||
            u'&source=site&hash=%(hash)s&username=%(userid)s&'
 | 
			
		||||
            u'key=ministhebest&format=%%s&sig=&exp=' %
 | 
			
		||||
            'http://fs%(server)s.trilulilu.ro/stream.php?type=video'
 | 
			
		||||
            '&source=site&hash=%(hash)s&username=%(userid)s&'
 | 
			
		||||
            'key=ministhebest&format=%%s&sig=&exp=' %
 | 
			
		||||
            log)
 | 
			
		||||
        formats = [
 | 
			
		||||
            {
 | 
			
		||||
 
 | 
			
		||||
@@ -73,7 +73,7 @@ class TudouIE(InfoExtractor):
 | 
			
		||||
        result = []
 | 
			
		||||
        len_parts = len(parts)
 | 
			
		||||
        if len_parts > 1:
 | 
			
		||||
            self.to_screen(u'%s: found %s parts' % (video_id, len_parts))
 | 
			
		||||
            self.to_screen('%s: found %s parts' % (video_id, len_parts))
 | 
			
		||||
        for part in parts:
 | 
			
		||||
            part_id = part['k']
 | 
			
		||||
            final_url = self._url_for_id(part_id, quality)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,40 +1,35 @@
 | 
			
		||||
import json
 | 
			
		||||
import re
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
from .common import InfoExtractor
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TvpIE(InfoExtractor):
 | 
			
		||||
    IE_NAME = u'tvp.pl'
 | 
			
		||||
    IE_NAME = 'tvp.pl'
 | 
			
		||||
    _VALID_URL = r'https?://www\.tvp\.pl/.*?wideo/(?P<date>\d+)/(?P<id>\d+)'
 | 
			
		||||
 | 
			
		||||
    _TEST = {
 | 
			
		||||
        u'url': u'http://www.tvp.pl/warszawa/magazyny/campusnews/wideo/31102013/12878238',
 | 
			
		||||
        u'md5': u'148408967a6a468953c0a75cbdaf0d7a',
 | 
			
		||||
        u'file': u'12878238.wmv',
 | 
			
		||||
        u'info_dict': {
 | 
			
		||||
            u'title': u'31.10.2013 - Odcinek 2',
 | 
			
		||||
            u'description': u'31.10.2013 - Odcinek 2',
 | 
			
		||||
        'url': 'http://www.tvp.pl/warszawa/magazyny/campusnews/wideo/31102013/12878238',
 | 
			
		||||
        'md5': '148408967a6a468953c0a75cbdaf0d7a',
 | 
			
		||||
        'info_dict': {
 | 
			
		||||
            'id': '12878238',
 | 
			
		||||
            'ext': 'wmv',
 | 
			
		||||
            'title': '31.10.2013 - Odcinek 2',
 | 
			
		||||
            'description': '31.10.2013 - Odcinek 2',
 | 
			
		||||
        },
 | 
			
		||||
        u'skip': u'Download has to use same server IP as extraction. Therefore, a good (load-balancing) DNS resolver will make the download fail.'
 | 
			
		||||
        'skip': 'Download has to use same server IP as extraction. Therefore, a good (load-balancing) DNS resolver will make the download fail.'
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    def _real_extract(self, url):
 | 
			
		||||
        mobj = re.match(self._VALID_URL, url)
 | 
			
		||||
        video_id = mobj.group('id')
 | 
			
		||||
        video_id = self._match_id(url)
 | 
			
		||||
        webpage = self._download_webpage(url, video_id)
 | 
			
		||||
        json_url = 'http://www.tvp.pl/pub/stat/videofileinfo?video_id=%s' % video_id
 | 
			
		||||
        json_params = self._download_webpage(
 | 
			
		||||
            json_url, video_id, u"Downloading video metadata")
 | 
			
		||||
 | 
			
		||||
        params = json.loads(json_params)
 | 
			
		||||
        self.report_extraction(video_id)
 | 
			
		||||
        params = self._download_json(
 | 
			
		||||
            json_url, video_id, "Downloading video metadata")
 | 
			
		||||
        video_url = params['video_url']
 | 
			
		||||
 | 
			
		||||
        title = self._og_search_title(webpage, fatal=True)
 | 
			
		||||
        return {
 | 
			
		||||
            'id': video_id,
 | 
			
		||||
            'title': title,
 | 
			
		||||
            'title': self._og_search_title(webpage),
 | 
			
		||||
            'ext': 'wmv',
 | 
			
		||||
            'url': video_url,
 | 
			
		||||
            'description': self._og_search_description(webpage),
 | 
			
		||||
 
 | 
			
		||||
@@ -40,8 +40,24 @@ class UdemyIE(InfoExtractor):
 | 
			
		||||
                error_str += ' - %s' % error_data.get('formErrors')
 | 
			
		||||
            raise ExtractorError(error_str, expected=True)
 | 
			
		||||
 | 
			
		||||
    def _download_json(self, url, video_id, note='Downloading JSON metadata'):
 | 
			
		||||
        response = super(UdemyIE, self)._download_json(url, video_id, note)
 | 
			
		||||
    def _download_json(self, url_or_request, video_id, note='Downloading JSON metadata'):
 | 
			
		||||
        headers = {
 | 
			
		||||
            'X-Udemy-Snail-Case': 'true',
 | 
			
		||||
            'X-Requested-With': 'XMLHttpRequest',
 | 
			
		||||
        }
 | 
			
		||||
        for cookie in self._downloader.cookiejar:
 | 
			
		||||
            if cookie.name == 'client_id':
 | 
			
		||||
                headers['X-Udemy-Client-Id'] = cookie.value
 | 
			
		||||
            elif cookie.name == 'access_token':
 | 
			
		||||
                headers['X-Udemy-Bearer-Token'] = cookie.value
 | 
			
		||||
 | 
			
		||||
        if isinstance(url_or_request, compat_urllib_request.Request):
 | 
			
		||||
            for header, value in headers.items():
 | 
			
		||||
                url_or_request.add_header(header, value)
 | 
			
		||||
        else:
 | 
			
		||||
            url_or_request = compat_urllib_request.Request(url_or_request, headers=headers)
 | 
			
		||||
 | 
			
		||||
        response = super(UdemyIE, self)._download_json(url_or_request, video_id, note)
 | 
			
		||||
        self._handle_error(response)
 | 
			
		||||
        return response
 | 
			
		||||
 | 
			
		||||
@@ -62,7 +78,9 @@ class UdemyIE(InfoExtractor):
 | 
			
		||||
        if login_popup == '<div class="run-command close-popup redirect" data-url="https://www.udemy.com/"></div>':
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        csrf = self._html_search_regex(r'<input type="hidden" name="csrf" value="(.+?)"', login_popup, 'csrf token')
 | 
			
		||||
        csrf = self._html_search_regex(
 | 
			
		||||
            r'<input type="hidden" name="csrf" value="(.+?)"',
 | 
			
		||||
            login_popup, 'csrf token')
 | 
			
		||||
 | 
			
		||||
        login_form = {
 | 
			
		||||
            'email': username,
 | 
			
		||||
@@ -71,42 +89,52 @@ class UdemyIE(InfoExtractor):
 | 
			
		||||
            'displayType': 'json',
 | 
			
		||||
            'isSubmitted': '1',
 | 
			
		||||
        }
 | 
			
		||||
        request = compat_urllib_request.Request(self._LOGIN_URL, compat_urllib_parse.urlencode(login_form))
 | 
			
		||||
        response = self._download_json(request, None, 'Logging in as %s' % username)
 | 
			
		||||
        request = compat_urllib_request.Request(
 | 
			
		||||
            self._LOGIN_URL, compat_urllib_parse.urlencode(login_form).encode('utf-8'))
 | 
			
		||||
        response = self._download_json(
 | 
			
		||||
            request, None, 'Logging in as %s' % username)
 | 
			
		||||
 | 
			
		||||
        if 'returnUrl' not in response:
 | 
			
		||||
            raise ExtractorError('Unable to log in')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def _real_extract(self, url):
 | 
			
		||||
        mobj = re.match(self._VALID_URL, url)
 | 
			
		||||
        lecture_id = mobj.group('id')
 | 
			
		||||
 | 
			
		||||
        lecture = self._download_json(
 | 
			
		||||
            'https://www.udemy.com/api-1.1/lectures/%s' % lecture_id, lecture_id, 'Downloading lecture JSON')
 | 
			
		||||
            'https://www.udemy.com/api-1.1/lectures/%s' % lecture_id,
 | 
			
		||||
            lecture_id, 'Downloading lecture JSON')
 | 
			
		||||
 | 
			
		||||
        if lecture['assetType'] != 'Video':
 | 
			
		||||
            raise ExtractorError('Lecture %s is not a video' % lecture_id, expected=True)
 | 
			
		||||
        asset_type = lecture.get('assetType') or lecture.get('asset_type')
 | 
			
		||||
        if asset_type != 'Video':
 | 
			
		||||
            raise ExtractorError(
 | 
			
		||||
                'Lecture %s is not a video' % lecture_id, expected=True)
 | 
			
		||||
 | 
			
		||||
        asset = lecture['asset']
 | 
			
		||||
 | 
			
		||||
        stream_url = asset['streamUrl']
 | 
			
		||||
        stream_url = asset.get('streamUrl') or asset.get('stream_url')
 | 
			
		||||
        mobj = re.search(r'(https?://www\.youtube\.com/watch\?v=.*)', stream_url)
 | 
			
		||||
        if mobj:
 | 
			
		||||
            return self.url_result(mobj.group(1), 'Youtube')
 | 
			
		||||
 | 
			
		||||
        video_id = asset['id']
 | 
			
		||||
        thumbnail = asset['thumbnailUrl']
 | 
			
		||||
        thumbnail = asset.get('thumbnailUrl') or asset.get('thumbnail_url')
 | 
			
		||||
        duration = asset['data']['duration']
 | 
			
		||||
 | 
			
		||||
        download_url = asset['downloadUrl']
 | 
			
		||||
        download_url = asset.get('downloadUrl') or asset.get('download_url')
 | 
			
		||||
 | 
			
		||||
        video = download_url.get('Video') or download_url.get('video')
 | 
			
		||||
        video_480p = download_url.get('Video480p') or download_url.get('video_480p')
 | 
			
		||||
 | 
			
		||||
        formats = [
 | 
			
		||||
            {
 | 
			
		||||
                'url': download_url['Video480p'][0],
 | 
			
		||||
                'url': video_480p[0],
 | 
			
		||||
                'format_id': '360p',
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                'url': download_url['Video'][0],
 | 
			
		||||
                'url': video[0],
 | 
			
		||||
                'format_id': '720p',
 | 
			
		||||
            },
 | 
			
		||||
        ]
 | 
			
		||||
@@ -140,25 +168,29 @@ class UdemyCourseIE(UdemyIE):
 | 
			
		||||
        course_path = mobj.group('coursepath')
 | 
			
		||||
 | 
			
		||||
        response = self._download_json(
 | 
			
		||||
            'https://www.udemy.com/api-1.1/courses/%s' % course_path, course_path, 'Downloading course JSON')
 | 
			
		||||
            'https://www.udemy.com/api-1.1/courses/%s' % course_path,
 | 
			
		||||
            course_path, 'Downloading course JSON')
 | 
			
		||||
 | 
			
		||||
        course_id = int(response['id'])
 | 
			
		||||
        course_title = response['title']
 | 
			
		||||
 | 
			
		||||
        webpage = self._download_webpage(
 | 
			
		||||
            'https://www.udemy.com/course/subscribe/?courseId=%s' % course_id, course_id, 'Enrolling in the course')
 | 
			
		||||
            'https://www.udemy.com/course/subscribe/?courseId=%s' % course_id,
 | 
			
		||||
            course_id, 'Enrolling in the course')
 | 
			
		||||
 | 
			
		||||
        if self._SUCCESSFULLY_ENROLLED in webpage:
 | 
			
		||||
            self.to_screen('%s: Successfully enrolled in' % course_id)
 | 
			
		||||
        elif self._ALREADY_ENROLLED in webpage:
 | 
			
		||||
            self.to_screen('%s: Already enrolled in' % course_id)
 | 
			
		||||
 | 
			
		||||
        response = self._download_json('https://www.udemy.com/api-1.1/courses/%s/curriculum' % course_id,
 | 
			
		||||
                                       course_id, 'Downloading course curriculum')
 | 
			
		||||
        response = self._download_json(
 | 
			
		||||
            'https://www.udemy.com/api-1.1/courses/%s/curriculum' % course_id,
 | 
			
		||||
            course_id, 'Downloading course curriculum')
 | 
			
		||||
 | 
			
		||||
        entries = [
 | 
			
		||||
            self.url_result('https://www.udemy.com/%s/#/lecture/%s' % (course_path, asset['id']), 'Udemy')
 | 
			
		||||
            for asset in response if asset.get('assetType') == 'Video'
 | 
			
		||||
            self.url_result(
 | 
			
		||||
                'https://www.udemy.com/%s/#/lecture/%s' % (course_path, asset['id']), 'Udemy')
 | 
			
		||||
            for asset in response if asset.get('assetType') or asset.get('asset_type') == 'Video'
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        return self.playlist_result(entries, course_id, course_title)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,32 +1,33 @@
 | 
			
		||||
import re
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
from .common import InfoExtractor
 | 
			
		||||
from ..utils import (
 | 
			
		||||
    find_xpath_attr,
 | 
			
		||||
    determine_ext,
 | 
			
		||||
    int_or_none,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class VideofyMeIE(InfoExtractor):
 | 
			
		||||
    _VALID_URL = r'https?://(www\.videofy\.me/.+?|p\.videofy\.me/v)/(?P<id>\d+)(&|#|$)'
 | 
			
		||||
    IE_NAME = u'videofy.me'
 | 
			
		||||
    _VALID_URL = r'https?://(?:www\.videofy\.me/.+?|p\.videofy\.me/v)/(?P<id>\d+)(&|#|$)'
 | 
			
		||||
    IE_NAME = 'videofy.me'
 | 
			
		||||
 | 
			
		||||
    _TEST = {
 | 
			
		||||
        u'url': u'http://www.videofy.me/thisisvideofyme/1100701',
 | 
			
		||||
        u'file': u'1100701.mp4',
 | 
			
		||||
        u'md5': u'c77d700bdc16ae2e9f3c26019bd96143',
 | 
			
		||||
        u'info_dict': {
 | 
			
		||||
            u'title': u'This is VideofyMe',
 | 
			
		||||
            u'description': None,
 | 
			
		||||
            u'uploader': u'VideofyMe',
 | 
			
		||||
            u'uploader_id': u'thisisvideofyme',
 | 
			
		||||
        'url': 'http://www.videofy.me/thisisvideofyme/1100701',
 | 
			
		||||
        'md5': 'c77d700bdc16ae2e9f3c26019bd96143',
 | 
			
		||||
        'info_dict': {
 | 
			
		||||
            'id': '1100701',
 | 
			
		||||
            'ext': 'mp4',
 | 
			
		||||
            'title': 'This is VideofyMe',
 | 
			
		||||
            'description': None,
 | 
			
		||||
            'uploader': 'VideofyMe',
 | 
			
		||||
            'uploader_id': 'thisisvideofyme',
 | 
			
		||||
            'view_count': int,
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    def _real_extract(self, url):
 | 
			
		||||
        mobj = re.match(self._VALID_URL, url)
 | 
			
		||||
        video_id = mobj.group('id')
 | 
			
		||||
        video_id = self._match_id(url)
 | 
			
		||||
        config = self._download_xml('http://sunshine.videofy.me/?videoId=%s' % video_id,
 | 
			
		||||
                                    video_id)
 | 
			
		||||
        video = config.find('video')
 | 
			
		||||
@@ -34,14 +35,16 @@ class VideofyMeIE(InfoExtractor):
 | 
			
		||||
        url_node = next(node for node in [find_xpath_attr(sources, 'source', 'id', 'HQ %s' % key)
 | 
			
		||||
                                          for key in ['on', 'av', 'off']] if node is not None)
 | 
			
		||||
        video_url = url_node.find('url').text
 | 
			
		||||
        view_count = int_or_none(self._search_regex(
 | 
			
		||||
            r'([0-9]+)', video.find('views').text, 'view count', fatal=False))
 | 
			
		||||
 | 
			
		||||
        return {'id': video_id,
 | 
			
		||||
                'title': video.find('title').text,
 | 
			
		||||
                'url': video_url,
 | 
			
		||||
                'ext': determine_ext(video_url),
 | 
			
		||||
                'thumbnail': video.find('thumb').text,
 | 
			
		||||
                'description': video.find('description').text,
 | 
			
		||||
                'uploader': config.find('blog/name').text,
 | 
			
		||||
                'uploader_id': video.find('identifier').text,
 | 
			
		||||
                'view_count': re.search(r'\d+', video.find('views').text).group(),
 | 
			
		||||
                }
 | 
			
		||||
        return {
 | 
			
		||||
            'id': video_id,
 | 
			
		||||
            'title': video.find('title').text,
 | 
			
		||||
            'url': video_url,
 | 
			
		||||
            'thumbnail': video.find('thumb').text,
 | 
			
		||||
            'description': video.find('description').text,
 | 
			
		||||
            'uploader': config.find('blog/name').text,
 | 
			
		||||
            'uploader_id': video.find('identifier').text,
 | 
			
		||||
            'view_count': view_count,
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
import re
 | 
			
		||||
import random
 | 
			
		||||
 | 
			
		||||
@@ -5,23 +7,22 @@ from .common import InfoExtractor
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class VideoPremiumIE(InfoExtractor):
 | 
			
		||||
    _VALID_URL = r'(?:https?://)?(?:www\.)?videopremium\.(?:tv|me)/(?P<id>\w+)(?:/.*)?'
 | 
			
		||||
    _VALID_URL = r'https?://(?:www\.)?videopremium\.(?:tv|me)/(?P<id>\w+)(?:/.*)?'
 | 
			
		||||
    _TEST = {
 | 
			
		||||
        u'url': u'http://videopremium.tv/4w7oadjsf156',
 | 
			
		||||
        u'file': u'4w7oadjsf156.f4v',
 | 
			
		||||
        u'info_dict': {
 | 
			
		||||
            u"title": u"youtube-dl_test_video____a_________-BaW_jenozKc.mp4.mp4"
 | 
			
		||||
        'url': 'http://videopremium.tv/4w7oadjsf156',
 | 
			
		||||
        'info_dict': {
 | 
			
		||||
            'id': '4w7oadjsf156',
 | 
			
		||||
            'ext': 'f4v',
 | 
			
		||||
            'title': 'youtube-dl_test_video____a_________-BaW_jenozKc.mp4.mp4'
 | 
			
		||||
        },
 | 
			
		||||
        u'params': {
 | 
			
		||||
            u'skip_download': True,
 | 
			
		||||
        'params': {
 | 
			
		||||
            'skip_download': True,
 | 
			
		||||
        },
 | 
			
		||||
        u'skip': u'Test file has been deleted.',
 | 
			
		||||
        'skip': 'Test file has been deleted.',
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    def _real_extract(self, url):
 | 
			
		||||
        mobj = re.match(self._VALID_URL, url)
 | 
			
		||||
 | 
			
		||||
        video_id = mobj.group('id')
 | 
			
		||||
        video_id = self._match_id(url)
 | 
			
		||||
        webpage_url = 'http://videopremium.tv/' + video_id
 | 
			
		||||
        webpage = self._download_webpage(webpage_url, video_id)
 | 
			
		||||
 | 
			
		||||
@@ -29,10 +30,10 @@ class VideoPremiumIE(InfoExtractor):
 | 
			
		||||
            # Download again, we need a cookie
 | 
			
		||||
            webpage = self._download_webpage(
 | 
			
		||||
                webpage_url, video_id,
 | 
			
		||||
                note=u'Downloading webpage again (with cookie)')
 | 
			
		||||
                note='Downloading webpage again (with cookie)')
 | 
			
		||||
 | 
			
		||||
        video_title = self._html_search_regex(
 | 
			
		||||
            r'<h2(?:.*?)>\s*(.+?)\s*<', webpage, u'video title')
 | 
			
		||||
            r'<h2(?:.*?)>\s*(.+?)\s*<', webpage, 'video title')
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
            'id': video_id,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										67
									
								
								youtube_dl/extractor/xminus.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								youtube_dl/extractor/xminus.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,67 @@
 | 
			
		||||
# coding: utf-8
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
from .common import InfoExtractor
 | 
			
		||||
from ..compat import (
 | 
			
		||||
    compat_chr,
 | 
			
		||||
    compat_ord,
 | 
			
		||||
)
 | 
			
		||||
from ..utils import (
 | 
			
		||||
    int_or_none,
 | 
			
		||||
    parse_filesize,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class XMinusIE(InfoExtractor):
 | 
			
		||||
    _VALID_URL = r'https?://(?:www\.)?x-minus\.org/track/(?P<id>[0-9]+)'
 | 
			
		||||
    _TEST = {
 | 
			
		||||
        'url': 'http://x-minus.org/track/4542/%D0%BF%D0%B5%D1%81%D0%B5%D0%BD%D0%BA%D0%B0-%D1%88%D0%BE%D1%84%D0%B5%D1%80%D0%B0.html',
 | 
			
		||||
        'md5': '401a15f2d2dcf6d592cb95528d72a2a8',
 | 
			
		||||
        'info_dict': {
 | 
			
		||||
            'id': '4542',
 | 
			
		||||
            'ext': 'mp3',
 | 
			
		||||
            'title': 'Леонид Агутин-Песенка шофера',
 | 
			
		||||
            'duration': 156,
 | 
			
		||||
            'tbr': 320,
 | 
			
		||||
            'filesize_approx': 5900000,
 | 
			
		||||
            'view_count': int,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    def _real_extract(self, url):
 | 
			
		||||
        video_id = self._match_id(url)
 | 
			
		||||
        webpage = self._download_webpage(url, video_id)
 | 
			
		||||
 | 
			
		||||
        artist = self._html_search_regex(
 | 
			
		||||
            r'minus_track\.artist="(.+?)"', webpage, 'artist')
 | 
			
		||||
        title = artist + '-' + self._html_search_regex(
 | 
			
		||||
            r'minus_track\.title="(.+?)"', webpage, 'title')
 | 
			
		||||
        duration = int_or_none(self._html_search_regex(
 | 
			
		||||
            r'minus_track\.dur_sec=\'([0-9]*?)\'',
 | 
			
		||||
            webpage, 'duration', fatal=False))
 | 
			
		||||
        filesize_approx = parse_filesize(self._html_search_regex(
 | 
			
		||||
            r'<div class="filesize[^"]*"></div>\s*([0-9.]+\s*[a-zA-Z][bB])',
 | 
			
		||||
            webpage, 'approximate filesize', fatal=False))
 | 
			
		||||
        tbr = int_or_none(self._html_search_regex(
 | 
			
		||||
            r'<div class="quality[^"]*"></div>\s*([0-9]+)\s*kbps',
 | 
			
		||||
            webpage, 'bitrate', fatal=False))
 | 
			
		||||
        view_count = int_or_none(self._html_search_regex(
 | 
			
		||||
            r'<div class="quality.*?► ([0-9]+)',
 | 
			
		||||
            webpage, 'view count', fatal=False))
 | 
			
		||||
 | 
			
		||||
        enc_token = self._html_search_regex(
 | 
			
		||||
            r'data-mt="(.*?)"', webpage, 'enc_token')
 | 
			
		||||
        token = ''.join(
 | 
			
		||||
            c if pos == 3 else compat_chr(compat_ord(c) - 1)
 | 
			
		||||
            for pos, c in enumerate(reversed(enc_token)))
 | 
			
		||||
        video_url = 'http://x-minus.org/dwlf/%s/%s.mp3' % (video_id, token)
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
            'id': video_id,
 | 
			
		||||
            'title': title,
 | 
			
		||||
            'url': video_url,
 | 
			
		||||
            'duration': duration,
 | 
			
		||||
            'filesize_approx': filesize_approx,
 | 
			
		||||
            'tbr': tbr,
 | 
			
		||||
            'view_count': view_count,
 | 
			
		||||
        }
 | 
			
		||||
@@ -49,7 +49,7 @@ class YouPornIE(InfoExtractor):
 | 
			
		||||
        try:
 | 
			
		||||
            params = json.loads(json_params)
 | 
			
		||||
        except:
 | 
			
		||||
            raise ExtractorError(u'Invalid JSON')
 | 
			
		||||
            raise ExtractorError('Invalid JSON')
 | 
			
		||||
 | 
			
		||||
        self.report_extraction(video_id)
 | 
			
		||||
        try:
 | 
			
		||||
@@ -103,7 +103,7 @@ class YouPornIE(InfoExtractor):
 | 
			
		||||
        self._sort_formats(formats)
 | 
			
		||||
 | 
			
		||||
        if not formats:
 | 
			
		||||
            raise ExtractorError(u'ERROR: no known formats available for video')
 | 
			
		||||
            raise ExtractorError('ERROR: no known formats available for video')
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
            'id': video_id,
 | 
			
		||||
 
 | 
			
		||||
@@ -609,9 +609,9 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):
 | 
			
		||||
            return {}
 | 
			
		||||
        player_config = json.loads(mobj.group(1))
 | 
			
		||||
        try:
 | 
			
		||||
            args = player_config[u'args']
 | 
			
		||||
            caption_url = args[u'ttsurl']
 | 
			
		||||
            timestamp = args[u'timestamp']
 | 
			
		||||
            args = player_config['args']
 | 
			
		||||
            caption_url = args['ttsurl']
 | 
			
		||||
            timestamp = args['timestamp']
 | 
			
		||||
            # We get the available subtitles
 | 
			
		||||
            list_params = compat_urllib_parse.urlencode({
 | 
			
		||||
                'type': 'list',
 | 
			
		||||
 
 | 
			
		||||
@@ -222,7 +222,7 @@ def parseOpts(overrideArguments=None):
 | 
			
		||||
    selection.add_option(
 | 
			
		||||
        '--no-playlist',
 | 
			
		||||
        action='store_true', dest='noplaylist', default=False,
 | 
			
		||||
        help='download only the currently playing video')
 | 
			
		||||
        help='If the URL refers to a video and a playlist, download only the video.')
 | 
			
		||||
    selection.add_option(
 | 
			
		||||
        '--age-limit',
 | 
			
		||||
        metavar='YEARS', dest='age_limit', default=None, type=int,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
from .atomicparsley import AtomicParsleyPP
 | 
			
		||||
from .ffmpeg import (
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
from ..utils import PostProcessingError
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
import subprocess
 | 
			
		||||
import sys
 | 
			
		||||
@@ -33,12 +35,12 @@ class FFmpegPostProcessor(PostProcessor):
 | 
			
		||||
 | 
			
		||||
    def check_version(self):
 | 
			
		||||
        if not self._executable:
 | 
			
		||||
            raise FFmpegPostProcessorError(u'ffmpeg or avconv not found. Please install one.')
 | 
			
		||||
            raise FFmpegPostProcessorError('ffmpeg or avconv not found. Please install one.')
 | 
			
		||||
 | 
			
		||||
        REQUIRED_VERSION = '1.0'
 | 
			
		||||
        if is_outdated_version(
 | 
			
		||||
                self._versions[self._executable], REQUIRED_VERSION):
 | 
			
		||||
            warning = u'Your copy of %s is outdated, update %s to version %s or newer if you encounter any errors.' % (
 | 
			
		||||
            warning = 'Your copy of %s is outdated, update %s to version %s or newer if you encounter any errors.' % (
 | 
			
		||||
                self._executable, self._executable, REQUIRED_VERSION)
 | 
			
		||||
            if self._downloader:
 | 
			
		||||
                self._downloader.report_warning(warning)
 | 
			
		||||
@@ -84,7 +86,7 @@ class FFmpegPostProcessor(PostProcessor):
 | 
			
		||||
               [encodeFilename(self._ffmpeg_filename_argument(out_path), True)])
 | 
			
		||||
 | 
			
		||||
        if self._downloader.params.get('verbose', False):
 | 
			
		||||
            self._downloader.to_screen(u'[debug] ffmpeg command line: %s' % shell_quote(cmd))
 | 
			
		||||
            self._downloader.to_screen('[debug] ffmpeg command line: %s' % shell_quote(cmd))
 | 
			
		||||
        p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
 | 
			
		||||
        stdout, stderr = p.communicate()
 | 
			
		||||
        if p.returncode != 0:
 | 
			
		||||
@@ -100,8 +102,8 @@ class FFmpegPostProcessor(PostProcessor):
 | 
			
		||||
 | 
			
		||||
    def _ffmpeg_filename_argument(self, fn):
 | 
			
		||||
        # ffmpeg broke --, see https://ffmpeg.org/trac/ffmpeg/ticket/2127 for details
 | 
			
		||||
        if fn.startswith(u'-'):
 | 
			
		||||
            return u'./' + fn
 | 
			
		||||
        if fn.startswith('-'):
 | 
			
		||||
            return './' + fn
 | 
			
		||||
        return fn
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -117,7 +119,7 @@ class FFmpegExtractAudioPP(FFmpegPostProcessor):
 | 
			
		||||
    def get_audio_codec(self, path):
 | 
			
		||||
 | 
			
		||||
        if not self._probe_executable:
 | 
			
		||||
            raise PostProcessingError(u'ffprobe or avprobe not found. Please install one.')
 | 
			
		||||
            raise PostProcessingError('ffprobe or avprobe not found. Please install one.')
 | 
			
		||||
        try:
 | 
			
		||||
            cmd = [
 | 
			
		||||
                self._probe_executable,
 | 
			
		||||
@@ -153,7 +155,7 @@ class FFmpegExtractAudioPP(FFmpegPostProcessor):
 | 
			
		||||
 | 
			
		||||
        filecodec = self.get_audio_codec(path)
 | 
			
		||||
        if filecodec is None:
 | 
			
		||||
            raise PostProcessingError(u'WARNING: unable to obtain file audio codec with ffprobe')
 | 
			
		||||
            raise PostProcessingError('WARNING: unable to obtain file audio codec with ffprobe')
 | 
			
		||||
 | 
			
		||||
        uses_avconv = self._uses_avconv()
 | 
			
		||||
        more_opts = []
 | 
			
		||||
@@ -202,7 +204,7 @@ class FFmpegExtractAudioPP(FFmpegPostProcessor):
 | 
			
		||||
                extension = 'wav'
 | 
			
		||||
                more_opts += ['-f', 'wav']
 | 
			
		||||
 | 
			
		||||
        prefix, sep, ext = path.rpartition(u'.')  # not os.path.splitext, since the latter does not work on unicode in all setups
 | 
			
		||||
        prefix, sep, ext = path.rpartition('.')  # not os.path.splitext, since the latter does not work on unicode in all setups
 | 
			
		||||
        new_path = prefix + sep + extension
 | 
			
		||||
 | 
			
		||||
        # If we download foo.mp3 and convert it to... foo.mp3, then don't delete foo.mp3, silly.
 | 
			
		||||
@@ -211,16 +213,16 @@ class FFmpegExtractAudioPP(FFmpegPostProcessor):
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            if self._nopostoverwrites and os.path.exists(encodeFilename(new_path)):
 | 
			
		||||
                self._downloader.to_screen(u'[youtube] Post-process file %s exists, skipping' % new_path)
 | 
			
		||||
                self._downloader.to_screen('[youtube] Post-process file %s exists, skipping' % new_path)
 | 
			
		||||
            else:
 | 
			
		||||
                self._downloader.to_screen(u'[' + self._executable + '] Destination: ' + new_path)
 | 
			
		||||
                self._downloader.to_screen('[' + self._executable + '] Destination: ' + new_path)
 | 
			
		||||
                self.run_ffmpeg(path, new_path, acodec, more_opts)
 | 
			
		||||
        except:
 | 
			
		||||
            etype, e, tb = sys.exc_info()
 | 
			
		||||
            if isinstance(e, AudioConversionError):
 | 
			
		||||
                msg = u'audio conversion failed: ' + e.msg
 | 
			
		||||
                msg = 'audio conversion failed: ' + e.msg
 | 
			
		||||
            else:
 | 
			
		||||
                msg = u'error running ' + self._executable
 | 
			
		||||
                msg = 'error running ' + self._executable
 | 
			
		||||
            raise PostProcessingError(msg)
 | 
			
		||||
 | 
			
		||||
        # Try to update the date time for extracted audio file.
 | 
			
		||||
@@ -228,7 +230,7 @@ class FFmpegExtractAudioPP(FFmpegPostProcessor):
 | 
			
		||||
            try:
 | 
			
		||||
                os.utime(encodeFilename(new_path), (time.time(), information['filetime']))
 | 
			
		||||
            except:
 | 
			
		||||
                self._downloader.report_warning(u'Cannot update utime of audio file')
 | 
			
		||||
                self._downloader.report_warning('Cannot update utime of audio file')
 | 
			
		||||
 | 
			
		||||
        information['filepath'] = new_path
 | 
			
		||||
        return self._nopostoverwrites, information
 | 
			
		||||
@@ -241,12 +243,12 @@ class FFmpegVideoConvertor(FFmpegPostProcessor):
 | 
			
		||||
 | 
			
		||||
    def run(self, information):
 | 
			
		||||
        path = information['filepath']
 | 
			
		||||
        prefix, sep, ext = path.rpartition(u'.')
 | 
			
		||||
        prefix, sep, ext = path.rpartition('.')
 | 
			
		||||
        outpath = prefix + sep + self._preferedformat
 | 
			
		||||
        if information['ext'] == self._preferedformat:
 | 
			
		||||
            self._downloader.to_screen(u'[ffmpeg] Not converting video file %s - already is in target format %s' % (path, self._preferedformat))
 | 
			
		||||
            self._downloader.to_screen('[ffmpeg] Not converting video file %s - already is in target format %s' % (path, self._preferedformat))
 | 
			
		||||
            return True, information
 | 
			
		||||
        self._downloader.to_screen(u'[' + 'ffmpeg' + '] Converting video from %s to %s, Destination: ' % (information['ext'], self._preferedformat) + outpath)
 | 
			
		||||
        self._downloader.to_screen('[' + 'ffmpeg' + '] Converting video from %s to %s, Destination: ' % (information['ext'], self._preferedformat) + outpath)
 | 
			
		||||
        self.run_ffmpeg(path, outpath, [])
 | 
			
		||||
        information['filepath'] = outpath
 | 
			
		||||
        information['format'] = self._preferedformat
 | 
			
		||||
@@ -453,11 +455,11 @@ class FFmpegEmbedSubtitlePP(FFmpegPostProcessor):
 | 
			
		||||
        return cls._lang_map.get(code[:2])
 | 
			
		||||
 | 
			
		||||
    def run(self, information):
 | 
			
		||||
        if information['ext'] != u'mp4':
 | 
			
		||||
            self._downloader.to_screen(u'[ffmpeg] Subtitles can only be embedded in mp4 files')
 | 
			
		||||
        if information['ext'] != 'mp4':
 | 
			
		||||
            self._downloader.to_screen('[ffmpeg] Subtitles can only be embedded in mp4 files')
 | 
			
		||||
            return True, information
 | 
			
		||||
        if not information.get('subtitles'):
 | 
			
		||||
            self._downloader.to_screen(u'[ffmpeg] There aren\'t any subtitles to embed')
 | 
			
		||||
            self._downloader.to_screen('[ffmpeg] There aren\'t any subtitles to embed')
 | 
			
		||||
            return True, information
 | 
			
		||||
 | 
			
		||||
        sub_langs = [key for key in information['subtitles']]
 | 
			
		||||
@@ -472,8 +474,8 @@ class FFmpegEmbedSubtitlePP(FFmpegPostProcessor):
 | 
			
		||||
                opts.extend(['-metadata:s:s:%d' % i, 'language=%s' % lang_code])
 | 
			
		||||
        opts.extend(['-f', 'mp4'])
 | 
			
		||||
 | 
			
		||||
        temp_filename = filename + u'.temp'
 | 
			
		||||
        self._downloader.to_screen(u'[ffmpeg] Embedding subtitles in \'%s\'' % filename)
 | 
			
		||||
        temp_filename = filename + '.temp'
 | 
			
		||||
        self._downloader.to_screen('[ffmpeg] Embedding subtitles in \'%s\'' % filename)
 | 
			
		||||
        self.run_ffmpeg_multiple_files(input_files, temp_filename, opts)
 | 
			
		||||
        os.remove(encodeFilename(filename))
 | 
			
		||||
        os.rename(encodeFilename(temp_filename), encodeFilename(filename))
 | 
			
		||||
@@ -494,13 +496,13 @@ class FFmpegMetadataPP(FFmpegPostProcessor):
 | 
			
		||||
            metadata['artist'] = info['uploader_id']
 | 
			
		||||
 | 
			
		||||
        if not metadata:
 | 
			
		||||
            self._downloader.to_screen(u'[ffmpeg] There isn\'t any metadata to add')
 | 
			
		||||
            self._downloader.to_screen('[ffmpeg] There isn\'t any metadata to add')
 | 
			
		||||
            return True, info
 | 
			
		||||
 | 
			
		||||
        filename = info['filepath']
 | 
			
		||||
        temp_filename = prepend_extension(filename, 'temp')
 | 
			
		||||
 | 
			
		||||
        if info['ext'] == u'm4a':
 | 
			
		||||
        if info['ext'] == 'm4a':
 | 
			
		||||
            options = ['-vn', '-acodec', 'copy']
 | 
			
		||||
        else:
 | 
			
		||||
            options = ['-c', 'copy']
 | 
			
		||||
@@ -508,7 +510,7 @@ class FFmpegMetadataPP(FFmpegPostProcessor):
 | 
			
		||||
        for (name, value) in metadata.items():
 | 
			
		||||
            options.extend(['-metadata', '%s=%s' % (name, value)])
 | 
			
		||||
 | 
			
		||||
        self._downloader.to_screen(u'[ffmpeg] Adding metadata to \'%s\'' % filename)
 | 
			
		||||
        self._downloader.to_screen('[ffmpeg] Adding metadata to \'%s\'' % filename)
 | 
			
		||||
        self.run_ffmpeg(filename, temp_filename, options)
 | 
			
		||||
        os.remove(encodeFilename(filename))
 | 
			
		||||
        os.rename(encodeFilename(temp_filename), encodeFilename(filename))
 | 
			
		||||
@@ -519,7 +521,7 @@ class FFmpegMergerPP(FFmpegPostProcessor):
 | 
			
		||||
    def run(self, info):
 | 
			
		||||
        filename = info['filepath']
 | 
			
		||||
        args = ['-c', 'copy', '-map', '0:v:0', '-map', '1:a:0', '-shortest']
 | 
			
		||||
        self._downloader.to_screen(u'[ffmpeg] Merging formats into "%s"' % filename)
 | 
			
		||||
        self._downloader.to_screen('[ffmpeg] Merging formats into "%s"' % filename)
 | 
			
		||||
        self.run_ffmpeg_multiple_files(info['__files_to_merge'], filename, args)
 | 
			
		||||
        return True, info
 | 
			
		||||
 | 
			
		||||
@@ -530,7 +532,7 @@ class FFmpegAudioFixPP(FFmpegPostProcessor):
 | 
			
		||||
        temp_filename = prepend_extension(filename, 'temp')
 | 
			
		||||
 | 
			
		||||
        options = ['-vn', '-acodec', 'copy']
 | 
			
		||||
        self._downloader.to_screen(u'[ffmpeg] Fixing audio file "%s"' % filename)
 | 
			
		||||
        self._downloader.to_screen('[ffmpeg] Fixing audio file "%s"' % filename)
 | 
			
		||||
        self.run_ffmpeg(filename, temp_filename, options)
 | 
			
		||||
 | 
			
		||||
        os.remove(encodeFilename(filename))
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
import subprocess
 | 
			
		||||
import sys
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
import io
 | 
			
		||||
import json
 | 
			
		||||
import traceback
 | 
			
		||||
@@ -7,7 +9,7 @@ import subprocess
 | 
			
		||||
import sys
 | 
			
		||||
from zipimport import zipimporter
 | 
			
		||||
 | 
			
		||||
from .utils import (
 | 
			
		||||
from .compat import (
 | 
			
		||||
    compat_str,
 | 
			
		||||
    compat_urllib_request,
 | 
			
		||||
)
 | 
			
		||||
@@ -17,14 +19,8 @@ from .version import __version__
 | 
			
		||||
def rsa_verify(message, signature, key):
 | 
			
		||||
    from struct import pack
 | 
			
		||||
    from hashlib import sha256
 | 
			
		||||
    from sys import version_info
 | 
			
		||||
 | 
			
		||||
    def b(x):
 | 
			
		||||
        if version_info[0] == 2:
 | 
			
		||||
            return x
 | 
			
		||||
        else:
 | 
			
		||||
            return x.encode('latin1')
 | 
			
		||||
    assert(type(message) == type(b('')))
 | 
			
		||||
    assert isinstance(message, bytes)
 | 
			
		||||
    block_size = 0
 | 
			
		||||
    n = key[0]
 | 
			
		||||
    while n:
 | 
			
		||||
@@ -35,14 +31,14 @@ def rsa_verify(message, signature, key):
 | 
			
		||||
    while signature:
 | 
			
		||||
        raw_bytes.insert(0, pack("B", signature & 0xFF))
 | 
			
		||||
        signature >>= 8
 | 
			
		||||
    signature = (block_size - len(raw_bytes)) * b('\x00') + b('').join(raw_bytes)
 | 
			
		||||
    if signature[0:2] != b('\x00\x01'):
 | 
			
		||||
    signature = (block_size - len(raw_bytes)) * b'\x00' + b''.join(raw_bytes)
 | 
			
		||||
    if signature[0:2] != b'\x00\x01':
 | 
			
		||||
        return False
 | 
			
		||||
    signature = signature[2:]
 | 
			
		||||
    if not b('\x00') in signature:
 | 
			
		||||
    if b'\x00' not in signature:
 | 
			
		||||
        return False
 | 
			
		||||
    signature = signature[signature.index(b('\x00')) + 1:]
 | 
			
		||||
    if not signature.startswith(b('\x30\x31\x30\x0D\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20')):
 | 
			
		||||
    signature = signature[signature.index(b'\x00') + 1:]
 | 
			
		||||
    if not signature.startswith(b'\x30\x31\x30\x0D\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20'):
 | 
			
		||||
        return False
 | 
			
		||||
    signature = signature[19:]
 | 
			
		||||
    if signature != sha256(message).digest():
 | 
			
		||||
@@ -59,7 +55,7 @@ def update_self(to_screen, verbose):
 | 
			
		||||
    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.')
 | 
			
		||||
        to_screen('It looks like you installed youtube-dl with a package manager, pip, setup.py or a tarball. Please use that to update.')
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    # Check if there is a new version
 | 
			
		||||
@@ -68,10 +64,10 @@ def update_self(to_screen, verbose):
 | 
			
		||||
    except:
 | 
			
		||||
        if verbose:
 | 
			
		||||
            to_screen(compat_str(traceback.format_exc()))
 | 
			
		||||
        to_screen(u'ERROR: can\'t find the current version. Please try again later.')
 | 
			
		||||
        to_screen('ERROR: can\'t find the current version. Please try again later.')
 | 
			
		||||
        return
 | 
			
		||||
    if newversion == __version__:
 | 
			
		||||
        to_screen(u'youtube-dl is up-to-date (' + __version__ + ')')
 | 
			
		||||
        to_screen('youtube-dl is up-to-date (' + __version__ + ')')
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    # Download and check versions info
 | 
			
		||||
@@ -81,15 +77,15 @@ def update_self(to_screen, verbose):
 | 
			
		||||
    except:
 | 
			
		||||
        if verbose:
 | 
			
		||||
            to_screen(compat_str(traceback.format_exc()))
 | 
			
		||||
        to_screen(u'ERROR: can\'t obtain versions info. Please try again later.')
 | 
			
		||||
        to_screen('ERROR: can\'t obtain versions info. Please try again later.')
 | 
			
		||||
        return
 | 
			
		||||
    if not 'signature' in versions_info:
 | 
			
		||||
        to_screen(u'ERROR: the versions file is not signed or corrupted. Aborting.')
 | 
			
		||||
        to_screen('ERROR: the versions file is not signed or corrupted. Aborting.')
 | 
			
		||||
        return
 | 
			
		||||
    signature = versions_info['signature']
 | 
			
		||||
    del versions_info['signature']
 | 
			
		||||
    if not rsa_verify(json.dumps(versions_info, sort_keys=True).encode('utf-8'), signature, UPDATES_RSA_KEY):
 | 
			
		||||
        to_screen(u'ERROR: the versions file signature is invalid. Aborting.')
 | 
			
		||||
        to_screen('ERROR: the versions file signature is invalid. Aborting.')
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    version_id = versions_info['latest']
 | 
			
		||||
@@ -97,10 +93,10 @@ def update_self(to_screen, verbose):
 | 
			
		||||
    def version_tuple(version_str):
 | 
			
		||||
        return tuple(map(int, version_str.split('.')))
 | 
			
		||||
    if version_tuple(__version__) >= version_tuple(version_id):
 | 
			
		||||
        to_screen(u'youtube-dl is up to date (%s)' % __version__)
 | 
			
		||||
        to_screen('youtube-dl is up to date (%s)' % __version__)
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    to_screen(u'Updating to version ' + version_id + ' ...')
 | 
			
		||||
    to_screen('Updating to version ' + version_id + ' ...')
 | 
			
		||||
    version = versions_info['versions'][version_id]
 | 
			
		||||
 | 
			
		||||
    print_notes(to_screen, versions_info['versions'])
 | 
			
		||||
@@ -108,11 +104,11 @@ def update_self(to_screen, verbose):
 | 
			
		||||
    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 os.path.isfile(filename + '.exe'):
 | 
			
		||||
            filename += '.exe'
 | 
			
		||||
 | 
			
		||||
    if not os.access(filename, os.W_OK):
 | 
			
		||||
        to_screen(u'ERROR: no write permissions on %s' % filename)
 | 
			
		||||
        to_screen('ERROR: no write permissions on %s' % filename)
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    # Py2EXE
 | 
			
		||||
@@ -120,7 +116,7 @@ def update_self(to_screen, verbose):
 | 
			
		||||
        exe = os.path.abspath(filename)
 | 
			
		||||
        directory = os.path.dirname(exe)
 | 
			
		||||
        if not os.access(directory, os.W_OK):
 | 
			
		||||
            to_screen(u'ERROR: no write permissions on %s' % directory)
 | 
			
		||||
            to_screen('ERROR: no write permissions on %s' % directory)
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
@@ -130,12 +126,12 @@ def update_self(to_screen, verbose):
 | 
			
		||||
        except (IOError, OSError):
 | 
			
		||||
            if verbose:
 | 
			
		||||
                to_screen(compat_str(traceback.format_exc()))
 | 
			
		||||
            to_screen(u'ERROR: unable to download latest version')
 | 
			
		||||
            to_screen('ERROR: unable to download latest version')
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        newcontent_hash = hashlib.sha256(newcontent).hexdigest()
 | 
			
		||||
        if newcontent_hash != version['exe'][1]:
 | 
			
		||||
            to_screen(u'ERROR: the downloaded file hash does not match. Aborting.')
 | 
			
		||||
            to_screen('ERROR: the downloaded file hash does not match. Aborting.')
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
@@ -144,27 +140,27 @@ def update_self(to_screen, verbose):
 | 
			
		||||
        except (IOError, OSError):
 | 
			
		||||
            if verbose:
 | 
			
		||||
                to_screen(compat_str(traceback.format_exc()))
 | 
			
		||||
            to_screen(u'ERROR: unable to write the new version')
 | 
			
		||||
            to_screen('ERROR: unable to write the new version')
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            bat = os.path.join(directory, 'youtube-dl-updater.bat')
 | 
			
		||||
            with io.open(bat, 'w') as batfile:
 | 
			
		||||
                batfile.write(u"""
 | 
			
		||||
                batfile.write('''
 | 
			
		||||
@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" > NUL
 | 
			
		||||
echo Updated youtube-dl to version %s.
 | 
			
		||||
start /b "" cmd /c del "%%~f0"&exit /b"
 | 
			
		||||
                \n""" % (exe, exe, version_id))
 | 
			
		||||
                \n''' % (exe, exe, version_id))
 | 
			
		||||
 | 
			
		||||
            subprocess.Popen([bat])  # Continues to run in the background
 | 
			
		||||
            return  # Do not show premature success messages
 | 
			
		||||
        except (IOError, OSError):
 | 
			
		||||
            if verbose:
 | 
			
		||||
                to_screen(compat_str(traceback.format_exc()))
 | 
			
		||||
            to_screen(u'ERROR: unable to overwrite current version')
 | 
			
		||||
            to_screen('ERROR: unable to overwrite current version')
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
    # Zip unix package
 | 
			
		||||
@@ -176,12 +172,12 @@ start /b "" cmd /c del "%%~f0"&exit /b"
 | 
			
		||||
        except (IOError, OSError):
 | 
			
		||||
            if verbose:
 | 
			
		||||
                to_screen(compat_str(traceback.format_exc()))
 | 
			
		||||
            to_screen(u'ERROR: unable to download latest version')
 | 
			
		||||
            to_screen('ERROR: unable to download latest version')
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        newcontent_hash = hashlib.sha256(newcontent).hexdigest()
 | 
			
		||||
        if newcontent_hash != version['bin'][1]:
 | 
			
		||||
            to_screen(u'ERROR: the downloaded file hash does not match. Aborting.')
 | 
			
		||||
            to_screen('ERROR: the downloaded file hash does not match. Aborting.')
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
@@ -190,10 +186,10 @@ start /b "" cmd /c del "%%~f0"&exit /b"
 | 
			
		||||
        except (IOError, OSError):
 | 
			
		||||
            if verbose:
 | 
			
		||||
                to_screen(compat_str(traceback.format_exc()))
 | 
			
		||||
            to_screen(u'ERROR: unable to overwrite current version')
 | 
			
		||||
            to_screen('ERROR: unable to overwrite current version')
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
    to_screen(u'Updated youtube-dl. Restart youtube-dl to use the new version.')
 | 
			
		||||
    to_screen('Updated youtube-dl. Restart youtube-dl to use the new version.')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_notes(versions, fromVersion):
 | 
			
		||||
@@ -207,6 +203,6 @@ def get_notes(versions, fromVersion):
 | 
			
		||||
def print_notes(to_screen, versions, fromVersion=__version__):
 | 
			
		||||
    notes = get_notes(versions, fromVersion)
 | 
			
		||||
    if notes:
 | 
			
		||||
        to_screen(u'PLEASE NOTE:')
 | 
			
		||||
        to_screen('PLEASE NOTE:')
 | 
			
		||||
        for note in notes:
 | 
			
		||||
            to_screen(note)
 | 
			
		||||
 
 | 
			
		||||
@@ -131,7 +131,7 @@ if sys.version_info >= (2, 7):
 | 
			
		||||
        """ Find the xpath xpath[@key=val] """
 | 
			
		||||
        assert re.match(r'^[a-zA-Z-]+$', key)
 | 
			
		||||
        assert re.match(r'^[a-zA-Z0-9@\s:._-]*$', val)
 | 
			
		||||
        expr = xpath + u"[@%s='%s']" % (key, val)
 | 
			
		||||
        expr = xpath + "[@%s='%s']" % (key, val)
 | 
			
		||||
        return node.find(expr)
 | 
			
		||||
else:
 | 
			
		||||
    def find_xpath_attr(node, xpath, key, val):
 | 
			
		||||
@@ -1046,6 +1046,57 @@ def format_bytes(bytes):
 | 
			
		||||
    return '%.2f%s' % (converted, suffix)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def parse_filesize(s):
 | 
			
		||||
    if s is None:
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
    # The lower-case forms are of course incorrect and inofficial,
 | 
			
		||||
    # but we support those too
 | 
			
		||||
    _UNIT_TABLE = {
 | 
			
		||||
        'B': 1,
 | 
			
		||||
        'b': 1,
 | 
			
		||||
        'KiB': 1024,
 | 
			
		||||
        'KB': 1000,
 | 
			
		||||
        'kB': 1024,
 | 
			
		||||
        'Kb': 1000,
 | 
			
		||||
        'MiB': 1024 ** 2,
 | 
			
		||||
        'MB': 1000 ** 2,
 | 
			
		||||
        'mB': 1024 ** 2,
 | 
			
		||||
        'Mb': 1000 ** 2,
 | 
			
		||||
        'GiB': 1024 ** 3,
 | 
			
		||||
        'GB': 1000 ** 3,
 | 
			
		||||
        'gB': 1024 ** 3,
 | 
			
		||||
        'Gb': 1000 ** 3,
 | 
			
		||||
        'TiB': 1024 ** 4,
 | 
			
		||||
        'TB': 1000 ** 4,
 | 
			
		||||
        'tB': 1024 ** 4,
 | 
			
		||||
        'Tb': 1000 ** 4,
 | 
			
		||||
        'PiB': 1024 ** 5,
 | 
			
		||||
        'PB': 1000 ** 5,
 | 
			
		||||
        'pB': 1024 ** 5,
 | 
			
		||||
        'Pb': 1000 ** 5,
 | 
			
		||||
        'EiB': 1024 ** 6,
 | 
			
		||||
        'EB': 1000 ** 6,
 | 
			
		||||
        'eB': 1024 ** 6,
 | 
			
		||||
        'Eb': 1000 ** 6,
 | 
			
		||||
        'ZiB': 1024 ** 7,
 | 
			
		||||
        'ZB': 1000 ** 7,
 | 
			
		||||
        'zB': 1024 ** 7,
 | 
			
		||||
        'Zb': 1000 ** 7,
 | 
			
		||||
        'YiB': 1024 ** 8,
 | 
			
		||||
        'YB': 1000 ** 8,
 | 
			
		||||
        'yB': 1024 ** 8,
 | 
			
		||||
        'Yb': 1000 ** 8,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    units_re = '|'.join(re.escape(u) for u in _UNIT_TABLE)
 | 
			
		||||
    m = re.match(r'(?P<num>[0-9]+(?:\.[0-9]*)?)\s*(?P<unit>%s)' % units_re, s)
 | 
			
		||||
    if not m:
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
    return int(float(m.group('num')) * _UNIT_TABLE[m.group('unit')])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_term_width():
 | 
			
		||||
    columns = compat_getenv('COLUMNS', None)
 | 
			
		||||
    if columns:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,2 +1,3 @@
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
__version__ = '2014.11.24'
 | 
			
		||||
__version__ = '2014.11.26.1'
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user