Compare commits
191 Commits
2014.02.13
...
2014.03.04
Author | SHA1 | Date | |
---|---|---|---|
e2cc7983e9 | |||
c9ae7b9565 | |||
86fb4347f7 | |||
2fcec131f5 | |||
9f62eaf4ef | |||
f92259c026 | |||
0afef30b23 | |||
dcdfd1c711 | |||
2acc1f8f50 | |||
2c39b0c695 | |||
e77c5b4f63 | |||
409a16cb72 | |||
94d5e90b4f | |||
2d73b45805 | |||
271a2dbfa2 | |||
bf4adcac66 | |||
fb8b8fdd62 | |||
5a0b26252e | |||
7d78f0cc48 | |||
f00fc78674 | |||
392017874c | |||
c3cb92d1ab | |||
aa5590fa07 | |||
8cfb5bbf92 | |||
69bb54ebf9 | |||
ca97a56e4b | |||
fc26f3b4c2 | |||
f604c93c64 | |||
dc3727b65c | |||
aba3231de1 | |||
9193bab91d | |||
fbcf3e416d | |||
c0e5d85631 | |||
ca7fa3dcb3 | |||
4ccfba28d9 | |||
abb82f1ddc | |||
cda008cff1 | |||
1877a14049 | |||
546582ec3e | |||
4534485586 | |||
a9ab8855e4 | |||
8a44ef6868 | |||
0c7214c404 | |||
4cf9654693 | |||
50a138d95c | |||
91346358b0 | |||
f3783d4b77 | |||
89ef304bed | |||
83cebb8b7a | |||
9e68f9fdf1 | |||
2acea5c03d | |||
978177527e | |||
2648c436f3 | |||
33f1f2c455 | |||
995befe0e9 | |||
1bb92aff55 | |||
b8e1471d3a | |||
60daf7f0bb | |||
a83a3139d1 | |||
fdb7ca3b8d | |||
0d7caf5cdf | |||
a339d7ba91 | |||
7216de55d6 | |||
2437fbca64 | |||
7d75d06b78 | |||
13ef5648c4 | |||
5b2478e2ba | |||
8b286571c3 | |||
f3ac523794 | |||
020cf5ebfd | |||
54ab193970 | |||
8f563f32ab | |||
151bae3566 | |||
76df418cba | |||
d0a72674c6 | |||
1d430674c7 | |||
70cb73922b | |||
344400951c | |||
ea5a0be811 | |||
3c7fd0bdb2 | |||
6cadf8c858 | |||
27579b9e4c | |||
4d756a9cc0 | |||
3e668e05be | |||
60d3a2e0f8 | |||
cc3a3b6b47 | |||
eda1d49a62 | |||
62e609ab77 | |||
2bfe4ead4b | |||
b1c6c32f78 | |||
f6acbdecf4 | |||
f1c9dfcc01 | |||
ce78943ae1 | |||
d6f0d86649 | |||
5bb67dbfea | |||
47610c4d3e | |||
b732f3581f | |||
9e57ce716f | |||
cd7ee7aa44 | |||
3cfe791473 | |||
973f2532f5 | |||
bc3be21d59 | |||
0bf5cf9886 | |||
919052d094 | |||
a2dafe2887 | |||
92661c994b | |||
ffe8fe356a | |||
bc2f773b4f | |||
f919201ecc | |||
7ff5d5c2e2 | |||
9b77f951c7 | |||
a25f2f990a | |||
78b373975d | |||
2fcc873c4c | |||
23c2baadb3 | |||
521ee82334 | |||
1df96e59ce | |||
3e123c1e28 | |||
f38da66731 | |||
06aabfc422 | |||
1052d2bfec | |||
5e0b652344 | |||
0f8f097183 | |||
491ed3dda2 | |||
af284c6d1b | |||
41d3ec5fba | |||
0568c352f3 | |||
2e7b4cb714 | |||
9767726b66 | |||
9ddfd84e41 | |||
1cf563d84b | |||
7928024f57 | |||
3eb38acb43 | |||
f7300c5c90 | |||
3489b7d26c | |||
acd2bcc384 | |||
43e77ca455 | |||
da36297988 | |||
dbb94fb044 | |||
d68f0cdb23 | |||
eae16eb67b | |||
4fc946b546 | |||
280bc5dad6 | |||
f43770d8c9 | |||
98c4b8fa1b | |||
ccb079ee67 | |||
2ea237472c | |||
0d4b4865cc | |||
fe52f9f956 | |||
882907a818 | |||
572a89cc4e | |||
c377110539 | |||
a9c7198a0b | |||
f6f01ea17b | |||
f2d0fc6823 | |||
f7000f3a1b | |||
c7f0177fa7 | |||
09c4d50944 | |||
2eb5d315d4 | |||
ad5976b4d9 | |||
a0dfcdce5e | |||
96d1637082 | |||
960f317171 | |||
4412ca751d | |||
cbffec0c95 | |||
0cea52cc18 | |||
6d784e87f4 | |||
ae6cae78f1 | |||
0f99566c01 | |||
2db806b4aa | |||
3f32c0ba4c | |||
541cb26c0d | |||
5544e038ab | |||
9032dc28a6 | |||
03635e2a71 | |||
00cf938aa5 | |||
a5f707c495 | |||
1824b48169 | |||
07ad22b8af | |||
b53466e168 | |||
6a7a389679 | |||
4edff78531 | |||
99043c2ea5 | |||
e68abba910 | |||
3165dc4d9f | |||
66c43a53e4 | |||
463b334616 | |||
b71dbc57c4 | |||
72ca1d7f45 | |||
cf1eb45153 | |||
a97bcd80ba |
20
README.md
20
README.md
@ -20,7 +20,7 @@ which means you can modify it, redistribute it or use it however you like.
|
||||
sure that you have sufficient permissions
|
||||
(run with sudo if needed)
|
||||
-i, --ignore-errors continue on download errors, for example to
|
||||
to skip unavailable videos in a playlist
|
||||
skip unavailable videos in a playlist
|
||||
--abort-on-error Abort downloading of further videos (in the
|
||||
playlist or the command line) if an error
|
||||
occurs
|
||||
@ -246,7 +246,7 @@ which means you can modify it, redistribute it or use it however you like.
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
You can configure youtube-dl by placing default arguments (such as `--extract-audio --no-mtime` to always extract the audio and not copy the mtime) into `/etc/youtube-dl.conf` and/or `~/.config/youtube-dl.conf`. On Windows, the configuration file locations are `%APPDATA%\youtube-dl\config.txt` and `C:\Users\<Yourname>\youtube-dl.conf`.
|
||||
You can configure youtube-dl by placing default arguments (such as `--extract-audio --no-mtime` to always extract the audio and not copy the mtime) into `/etc/youtube-dl.conf` and/or `~/.config/youtube-dl/config`. On Windows, the configuration file locations are `%APPDATA%\youtube-dl\config.txt` and `C:\Users\<Yourname>\youtube-dl.conf`.
|
||||
|
||||
# OUTPUT TEMPLATE
|
||||
|
||||
@ -281,12 +281,14 @@ Videos can be filtered by their upload date using the options `--date`, `--dateb
|
||||
|
||||
Examples:
|
||||
|
||||
$ # Download only the videos uploaded in the last 6 months
|
||||
$ youtube-dl --dateafter now-6months
|
||||
$ # Download only the videos uploaded on January 1, 1970
|
||||
$ youtube-dl --date 19700101
|
||||
$ # will only download the videos uploaded in the 200x decade
|
||||
$ youtube-dl --dateafter 20000101 --datebefore 20091231
|
||||
# Download only the videos uploaded in the last 6 months
|
||||
$ youtube-dl --dateafter now-6months
|
||||
|
||||
# Download only the videos uploaded on January 1, 1970
|
||||
$ youtube-dl --date 19700101
|
||||
|
||||
$ # will only download the videos uploaded in the 200x decade
|
||||
$ youtube-dl --dateafter 20000101 --datebefore 20091231
|
||||
|
||||
# FAQ
|
||||
|
||||
@ -355,7 +357,7 @@ If you want to create a build of youtube-dl yourself, you'll need
|
||||
|
||||
### Adding support for a new site
|
||||
|
||||
If you want to add support for a new site, copy *any* [recently modified](https://github.com/rg3/youtube-dl/commits/master/youtube_dl/extractor) file in `youtube_dl/extractor`, add an import in [`youtube_dl/extractor/__init__.py`](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/__init__.py). Have a look at [`youtube_dl/common/extractor/common.py`](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/common.py) for possible helper methods and a [detailed description of what your extractor should return](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/common.py#L38). Don't forget to run the tests with `python test/test_download.py Test_Download.test_YourExtractor`! For a detailed tutorial, refer to [this blog post](http://filippo.io/add-support-for-a-new-video-site-to-youtube-dl/).
|
||||
If you want to add support for a new site, copy *any* [recently modified](https://github.com/rg3/youtube-dl/commits/master/youtube_dl/extractor) file in `youtube_dl/extractor`, add an import in [`youtube_dl/extractor/__init__.py`](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/__init__.py). Have a look at [`youtube_dl/common/extractor/common.py`](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/common.py) for possible helper methods and a [detailed description of what your extractor should return](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/common.py#L38). Don't forget to run the tests with `python test/test_download.py TestDownload.test_YourExtractor`! For a detailed tutorial, refer to [this blog post](http://filippo.io/add-support-for-a-new-video-site-to-youtube-dl/).
|
||||
|
||||
# BUGS
|
||||
|
||||
|
@ -14,9 +14,9 @@
|
||||
|
||||
set -e
|
||||
|
||||
skip_tests=false
|
||||
if [ "$1" = '--skip-test' ]; then
|
||||
skip_tests=true
|
||||
skip_tests=true
|
||||
if [ "$1" = '--run-tests' ]; then
|
||||
skip_tests=false
|
||||
shift
|
||||
fi
|
||||
|
||||
|
@ -68,6 +68,13 @@ class TestAllURLsMatching(unittest.TestCase):
|
||||
def test_youtube_show_matching(self):
|
||||
self.assertMatch('http://www.youtube.com/show/airdisasters', ['youtube:show'])
|
||||
|
||||
def test_youtube_truncated(self):
|
||||
self.assertMatch('http://www.youtube.com/watch?', ['youtube:truncated_url'])
|
||||
|
||||
def test_youtube_search_matching(self):
|
||||
self.assertMatch('http://www.youtube.com/results?search_query=making+mustard', ['youtube:search_url'])
|
||||
self.assertMatch('https://www.youtube.com/results?baz=bar&search_query=youtube-dl+test+video&filters=video&lclk=video', ['youtube:search_url'])
|
||||
|
||||
def test_justin_tv_channelid_matching(self):
|
||||
self.assertTrue(JustinTVIE.suitable(u"justin.tv/vanillatv"))
|
||||
self.assertTrue(JustinTVIE.suitable(u"twitch.tv/vanillatv"))
|
||||
|
@ -18,6 +18,7 @@ from test.helper import (
|
||||
import hashlib
|
||||
import io
|
||||
import json
|
||||
import re
|
||||
import socket
|
||||
|
||||
import youtube_dl.YoutubeDL
|
||||
@ -72,9 +73,7 @@ def generator(test_case):
|
||||
if 'playlist' not in test_case:
|
||||
info_dict = test_case.get('info_dict', {})
|
||||
if not test_case.get('file') and not (info_dict.get('id') and info_dict.get('ext')):
|
||||
print_skipping('The output file cannot be know, the "file" '
|
||||
'key is missing or the info_dict is incomplete')
|
||||
return
|
||||
raise Exception('Test definition incorrect. The output file cannot be known. Are both \'id\' and \'ext\' keys present?')
|
||||
if 'skip' in test_case:
|
||||
print_skipping(test_case['skip'])
|
||||
return
|
||||
@ -137,12 +136,21 @@ def generator(test_case):
|
||||
with io.open(info_json_fn, encoding='utf-8') as infof:
|
||||
info_dict = json.load(infof)
|
||||
for (info_field, expected) in tc.get('info_dict', {}).items():
|
||||
if isinstance(expected, compat_str) and expected.startswith('md5:'):
|
||||
got = 'md5:' + md5(info_dict.get(info_field))
|
||||
else:
|
||||
if isinstance(expected, compat_str) and expected.startswith('re:'):
|
||||
got = info_dict.get(info_field)
|
||||
self.assertEqual(expected, got,
|
||||
u'invalid value for field %s, expected %r, got %r' % (info_field, expected, got))
|
||||
match_str = expected[len('re:'):]
|
||||
match_rex = re.compile(match_str)
|
||||
|
||||
self.assertTrue(
|
||||
isinstance(got, compat_str) and match_rex.match(got),
|
||||
u'field %s (value: %r) should match %r' % (info_field, got, match_str))
|
||||
else:
|
||||
if isinstance(expected, compat_str) and expected.startswith('md5:'):
|
||||
got = 'md5:' + md5(info_dict.get(info_field))
|
||||
else:
|
||||
got = info_dict.get(info_field)
|
||||
self.assertEqual(expected, got,
|
||||
u'invalid value for field %s, expected %r, got %r' % (info_field, expected, got))
|
||||
|
||||
# If checkable fields are missing from the test case, print the info_dict
|
||||
test_info_dict = dict((key, value if not isinstance(value, compat_str) or len(value) < 250 else 'md5:' + md5(value))
|
||||
|
@ -55,10 +55,10 @@ class TestPlaylists(unittest.TestCase):
|
||||
def test_dailymotion_user(self):
|
||||
dl = FakeYDL()
|
||||
ie = DailymotionUserIE(dl)
|
||||
result = ie.extract('http://www.dailymotion.com/user/generation-quoi/')
|
||||
result = ie.extract('https://www.dailymotion.com/user/nqtv')
|
||||
self.assertIsPlaylist(result)
|
||||
self.assertEqual(result['title'], 'Génération Quoi')
|
||||
self.assertTrue(len(result['entries']) >= 26)
|
||||
self.assertEqual(result['title'], 'Rémi Gaillard')
|
||||
self.assertTrue(len(result['entries']) >= 100)
|
||||
|
||||
def test_vimeo_channel(self):
|
||||
dl = FakeYDL()
|
||||
@ -170,12 +170,12 @@ class TestPlaylists(unittest.TestCase):
|
||||
def test_AcademicEarthCourse(self):
|
||||
dl = FakeYDL()
|
||||
ie = AcademicEarthCourseIE(dl)
|
||||
result = ie.extract('http://academicearth.org/courses/building-dynamic-websites/')
|
||||
result = ie.extract('http://academicearth.org/playlists/laws-of-nature/')
|
||||
self.assertIsPlaylist(result)
|
||||
self.assertEqual(result['id'], 'building-dynamic-websites')
|
||||
self.assertEqual(result['title'], 'Building Dynamic Websites')
|
||||
self.assertEqual(result['description'], u"Today's websites are increasingly dynamic. Pages are no longer static HTML files but instead generated by scripts and database calls. User interfaces are more seamless, with technologies like Ajax replacing traditional page reloads. This course teaches students how to build dynamic websites with Ajax and with Linux, Apache, MySQL, and PHP (LAMP), one of today's most popular frameworks. Students learn how to set up domain names with DNS, how to structure pages with XHTML and CSS, how to program in JavaScript and PHP, how to configure Apache and MySQL, how to design and query databases with SQL, how to use Ajax with both XML and JSON, and how to build mashups. The course explores issues of security, scalability, and cross-browser support and also discusses enterprise-level deployments of websites, including third-party hosting, virtualization, colocation in data centers, firewalling, and load-balancing.")
|
||||
self.assertEqual(len(result['entries']), 10)
|
||||
self.assertEqual(result['id'], 'laws-of-nature')
|
||||
self.assertEqual(result['title'], 'Laws of Nature')
|
||||
self.assertEqual(result['description'],u'Introduce yourself to the laws of nature with these free online college lectures from Yale, Harvard, and MIT.')# u"Today's websites are increasingly dynamic. Pages are no longer static HTML files but instead generated by scripts and database calls. User interfaces are more seamless, with technologies like Ajax replacing traditional page reloads. This course teaches students how to build dynamic websites with Ajax and with Linux, Apache, MySQL, and PHP (LAMP), one of today's most popular frameworks. Students learn how to set up domain names with DNS, how to structure pages with XHTML and CSS, how to program in JavaScript and PHP, how to configure Apache and MySQL, how to design and query databases with SQL, how to use Ajax with both XML and JSON, and how to build mashups. The course explores issues of security, scalability, and cross-browser support and also discusses enterprise-level deployments of websites, including third-party hosting, virtualization, colocation in data centers, firewalling, and load-balancing.")
|
||||
self.assertEqual(len(result['entries']), 4)
|
||||
|
||||
def test_ivi_compilation(self):
|
||||
dl = FakeYDL()
|
||||
@ -250,5 +250,14 @@ class TestPlaylists(unittest.TestCase):
|
||||
self.assertEqual(result['title'], 'python language')
|
||||
self.assertTrue(len(result['entries']) == 15)
|
||||
|
||||
def test_generic_rss_feed(self):
|
||||
dl = FakeYDL()
|
||||
ie = GenericIE(dl)
|
||||
result = ie.extract('http://www.escapistmagazine.com/rss/videos/list/1.xml')
|
||||
self.assertIsPlaylist(result)
|
||||
self.assertEqual(result['id'], 'http://www.escapistmagazine.com/rss/videos/list/1.xml')
|
||||
self.assertEqual(result['title'], 'Zero Punctuation')
|
||||
self.assertTrue(len(result['entries']) > 10)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
@ -9,6 +9,7 @@ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
|
||||
# Various small unit tests
|
||||
import io
|
||||
import xml.etree.ElementTree
|
||||
|
||||
#from youtube_dl.utils import htmlentity_transform
|
||||
@ -21,10 +22,12 @@ from youtube_dl.utils import (
|
||||
orderedSet,
|
||||
PagedList,
|
||||
parse_duration,
|
||||
read_batch_urls,
|
||||
sanitize_filename,
|
||||
shell_quote,
|
||||
smuggle_url,
|
||||
str_to_int,
|
||||
struct_unpack,
|
||||
timeconvert,
|
||||
unescapeHTML,
|
||||
unified_strdate,
|
||||
@ -201,7 +204,16 @@ class TestUtil(unittest.TestCase):
|
||||
self.assertEqual(parse_duration('1'), 1)
|
||||
self.assertEqual(parse_duration('1337:12'), 80232)
|
||||
self.assertEqual(parse_duration('9:12:43'), 33163)
|
||||
self.assertEqual(parse_duration('12:00'), 720)
|
||||
self.assertEqual(parse_duration('00:01:01'), 61)
|
||||
self.assertEqual(parse_duration('x:y'), None)
|
||||
self.assertEqual(parse_duration('3h11m53s'), 11513)
|
||||
self.assertEqual(parse_duration('62m45s'), 3765)
|
||||
self.assertEqual(parse_duration('6m59s'), 419)
|
||||
self.assertEqual(parse_duration('49s'), 49)
|
||||
self.assertEqual(parse_duration('0h0m0s'), 0)
|
||||
self.assertEqual(parse_duration('0m0s'), 0)
|
||||
self.assertEqual(parse_duration('0s'), 0)
|
||||
|
||||
def test_fix_xml_ampersands(self):
|
||||
self.assertEqual(
|
||||
@ -237,5 +249,17 @@ class TestUtil(unittest.TestCase):
|
||||
testPL(5, 2, (2, 99), [2, 3, 4])
|
||||
testPL(5, 2, (20, 99), [])
|
||||
|
||||
def test_struct_unpack(self):
|
||||
self.assertEqual(struct_unpack(u'!B', b'\x00'), (0,))
|
||||
|
||||
def test_read_batch_urls(self):
|
||||
f = io.StringIO(u'''\xef\xbb\xbf foo
|
||||
bar\r
|
||||
baz
|
||||
# More after this line\r
|
||||
; or after this
|
||||
bam''')
|
||||
self.assertEqual(read_batch_urls(f), [u'foo', u'bar', u'baz', u'bam'])
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
@ -16,6 +16,7 @@ from youtube_dl.extractor import (
|
||||
YoutubeChannelIE,
|
||||
YoutubeShowIE,
|
||||
YoutubeTopListIE,
|
||||
YoutubeSearchURLIE,
|
||||
)
|
||||
|
||||
|
||||
@ -118,6 +119,8 @@ class TestYoutubeLists(unittest.TestCase):
|
||||
self.assertEqual(original_video['id'], 'rjFaenf1T-Y')
|
||||
|
||||
def test_youtube_toptracks(self):
|
||||
print('Skipping: The playlist page gives error 500')
|
||||
return
|
||||
dl = FakeYDL()
|
||||
ie = YoutubePlaylistIE(dl)
|
||||
result = ie.extract('https://www.youtube.com/playlist?list=MCUS')
|
||||
@ -131,5 +134,14 @@ class TestYoutubeLists(unittest.TestCase):
|
||||
entries = result['entries']
|
||||
self.assertTrue(len(entries) >= 5)
|
||||
|
||||
def test_youtube_search_url(self):
|
||||
dl = FakeYDL()
|
||||
ie = YoutubeSearchURLIE(dl)
|
||||
result = ie.extract('https://www.youtube.com/results?baz=bar&search_query=youtube-dl+test+video&filters=video&lclk=video')
|
||||
entries = result['entries']
|
||||
self.assertIsPlaylist(result)
|
||||
self.assertEqual(result['title'], 'youtube-dl test video')
|
||||
self.assertTrue(len(entries) >= 5)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
@ -675,6 +675,9 @@ class YoutubeDL(object):
|
||||
info_dict['playlist'] = None
|
||||
info_dict['playlist_index'] = None
|
||||
|
||||
if 'display_id' not in info_dict and 'id' in info_dict:
|
||||
info_dict['display_id'] = info_dict['id']
|
||||
|
||||
# This extractors handle format selection themselves
|
||||
if info_dict['extractor'] in ['Youku']:
|
||||
if download:
|
||||
|
@ -41,13 +41,22 @@ __authors__ = (
|
||||
'Chris Gahan',
|
||||
'Saimadhav Heblikar',
|
||||
'Mike Col',
|
||||
'Oleg Prutz',
|
||||
'pulpe',
|
||||
'Andreas Schmitz',
|
||||
'Michael Kaiser',
|
||||
'Niklas Laxström',
|
||||
'David Triendl',
|
||||
'Anthony Weems',
|
||||
'David Wagner',
|
||||
'Juan C. Olivares',
|
||||
)
|
||||
|
||||
__license__ = 'Public Domain'
|
||||
|
||||
import codecs
|
||||
import getpass
|
||||
import io
|
||||
import locale
|
||||
import optparse
|
||||
import os
|
||||
@ -66,6 +75,7 @@ from .utils import (
|
||||
get_cachedir,
|
||||
MaxDownloadsReached,
|
||||
preferredencoding,
|
||||
read_batch_urls,
|
||||
SameFileError,
|
||||
setproctitle,
|
||||
std_headers,
|
||||
@ -204,7 +214,7 @@ def parseOpts(overrideArguments=None):
|
||||
general.add_option('-U', '--update',
|
||||
action='store_true', dest='update_self', help='update this program to latest version. Make sure that you have sufficient permissions (run with sudo if needed)')
|
||||
general.add_option('-i', '--ignore-errors',
|
||||
action='store_true', dest='ignoreerrors', help='continue on download errors, for example to to skip unavailable videos in a playlist', default=False)
|
||||
action='store_true', dest='ignoreerrors', help='continue on download errors, for example to skip unavailable videos in a playlist', default=False)
|
||||
general.add_option('--abort-on-error',
|
||||
action='store_false', dest='ignoreerrors',
|
||||
help='Abort downloading of further videos (in the playlist or the command line) if an error occurs')
|
||||
@ -547,21 +557,19 @@ def _real_main(argv=None):
|
||||
sys.exit(0)
|
||||
|
||||
# Batch file verification
|
||||
batchurls = []
|
||||
batch_urls = []
|
||||
if opts.batchfile is not None:
|
||||
try:
|
||||
if opts.batchfile == '-':
|
||||
batchfd = sys.stdin
|
||||
else:
|
||||
batchfd = open(opts.batchfile, 'r')
|
||||
batchurls = batchfd.readlines()
|
||||
batchurls = [x.strip() for x in batchurls]
|
||||
batchurls = [x for x in batchurls if len(x) > 0 and not re.search(r'^[#/;]', x)]
|
||||
batchfd = io.open(opts.batchfile, 'r', encoding='utf-8', errors='ignore')
|
||||
batch_urls = read_batch_urls(batchfd)
|
||||
if opts.verbose:
|
||||
write_string(u'[debug] Batch file urls: ' + repr(batchurls) + u'\n')
|
||||
write_string(u'[debug] Batch file urls: ' + repr(batch_urls) + u'\n')
|
||||
except IOError:
|
||||
sys.exit(u'ERROR: batch file could not be read')
|
||||
all_urls = batchurls + args
|
||||
all_urls = batch_urls + args
|
||||
all_urls = [url.strip() for url in all_urls]
|
||||
_enc = preferredencoding()
|
||||
all_urls = [url.decode(_enc, 'ignore') if isinstance(url, bytes) else url for url in all_urls]
|
||||
|
@ -5,6 +5,7 @@ from .hls import HlsFD
|
||||
from .http import HttpFD
|
||||
from .mplayer import MplayerFD
|
||||
from .rtmp import RtmpFD
|
||||
from .f4m import F4mFD
|
||||
|
||||
from ..utils import (
|
||||
determine_ext,
|
||||
@ -22,5 +23,7 @@ def get_suitable_downloader(info_dict):
|
||||
return HlsFD
|
||||
if url.startswith('mms') or url.startswith('rtsp'):
|
||||
return MplayerFD
|
||||
if determine_ext(url) == 'f4m':
|
||||
return F4mFD
|
||||
else:
|
||||
return HttpFD
|
||||
|
314
youtube_dl/downloader/f4m.py
Normal file
314
youtube_dl/downloader/f4m.py
Normal file
@ -0,0 +1,314 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import base64
|
||||
import io
|
||||
import itertools
|
||||
import os
|
||||
import time
|
||||
import xml.etree.ElementTree as etree
|
||||
|
||||
from .common import FileDownloader
|
||||
from .http import HttpFD
|
||||
from ..utils import (
|
||||
struct_pack,
|
||||
struct_unpack,
|
||||
compat_urlparse,
|
||||
format_bytes,
|
||||
encodeFilename,
|
||||
sanitize_open,
|
||||
)
|
||||
|
||||
|
||||
class FlvReader(io.BytesIO):
|
||||
"""
|
||||
Reader for Flv files
|
||||
The file format is documented in https://www.adobe.com/devnet/f4v.html
|
||||
"""
|
||||
|
||||
# Utility functions for reading numbers and strings
|
||||
def read_unsigned_long_long(self):
|
||||
return struct_unpack('!Q', self.read(8))[0]
|
||||
|
||||
def read_unsigned_int(self):
|
||||
return struct_unpack('!I', self.read(4))[0]
|
||||
|
||||
def read_unsigned_char(self):
|
||||
return struct_unpack('!B', self.read(1))[0]
|
||||
|
||||
def read_string(self):
|
||||
res = b''
|
||||
while True:
|
||||
char = self.read(1)
|
||||
if char == b'\x00':
|
||||
break
|
||||
res += char
|
||||
return res
|
||||
|
||||
def read_box_info(self):
|
||||
"""
|
||||
Read a box and return the info as a tuple: (box_size, box_type, box_data)
|
||||
"""
|
||||
real_size = size = self.read_unsigned_int()
|
||||
box_type = self.read(4)
|
||||
header_end = 8
|
||||
if size == 1:
|
||||
real_size = self.read_unsigned_long_long()
|
||||
header_end = 16
|
||||
return real_size, box_type, self.read(real_size-header_end)
|
||||
|
||||
def read_asrt(self):
|
||||
# version
|
||||
self.read_unsigned_char()
|
||||
# flags
|
||||
self.read(3)
|
||||
quality_entry_count = self.read_unsigned_char()
|
||||
# QualityEntryCount
|
||||
for i in range(quality_entry_count):
|
||||
self.read_string()
|
||||
|
||||
segment_run_count = self.read_unsigned_int()
|
||||
segments = []
|
||||
for i in range(segment_run_count):
|
||||
first_segment = self.read_unsigned_int()
|
||||
fragments_per_segment = self.read_unsigned_int()
|
||||
segments.append((first_segment, fragments_per_segment))
|
||||
|
||||
return {
|
||||
'segment_run': segments,
|
||||
}
|
||||
|
||||
def read_afrt(self):
|
||||
# version
|
||||
self.read_unsigned_char()
|
||||
# flags
|
||||
self.read(3)
|
||||
# time scale
|
||||
self.read_unsigned_int()
|
||||
|
||||
quality_entry_count = self.read_unsigned_char()
|
||||
# QualitySegmentUrlModifiers
|
||||
for i in range(quality_entry_count):
|
||||
self.read_string()
|
||||
|
||||
fragments_count = self.read_unsigned_int()
|
||||
fragments = []
|
||||
for i in range(fragments_count):
|
||||
first = self.read_unsigned_int()
|
||||
first_ts = self.read_unsigned_long_long()
|
||||
duration = self.read_unsigned_int()
|
||||
if duration == 0:
|
||||
discontinuity_indicator = self.read_unsigned_char()
|
||||
else:
|
||||
discontinuity_indicator = None
|
||||
fragments.append({
|
||||
'first': first,
|
||||
'ts': first_ts,
|
||||
'duration': duration,
|
||||
'discontinuity_indicator': discontinuity_indicator,
|
||||
})
|
||||
|
||||
return {
|
||||
'fragments': fragments,
|
||||
}
|
||||
|
||||
def read_abst(self):
|
||||
# version
|
||||
self.read_unsigned_char()
|
||||
# flags
|
||||
self.read(3)
|
||||
|
||||
self.read_unsigned_int() # BootstrapinfoVersion
|
||||
# Profile,Live,Update,Reserved
|
||||
self.read(1)
|
||||
# time scale
|
||||
self.read_unsigned_int()
|
||||
# CurrentMediaTime
|
||||
self.read_unsigned_long_long()
|
||||
# SmpteTimeCodeOffset
|
||||
self.read_unsigned_long_long()
|
||||
|
||||
self.read_string() # MovieIdentifier
|
||||
server_count = self.read_unsigned_char()
|
||||
# ServerEntryTable
|
||||
for i in range(server_count):
|
||||
self.read_string()
|
||||
quality_count = self.read_unsigned_char()
|
||||
# QualityEntryTable
|
||||
for i in range(quality_count):
|
||||
self.read_string()
|
||||
# DrmData
|
||||
self.read_string()
|
||||
# MetaData
|
||||
self.read_string()
|
||||
|
||||
segments_count = self.read_unsigned_char()
|
||||
segments = []
|
||||
for i in range(segments_count):
|
||||
box_size, box_type, box_data = self.read_box_info()
|
||||
assert box_type == b'asrt'
|
||||
segment = FlvReader(box_data).read_asrt()
|
||||
segments.append(segment)
|
||||
fragments_run_count = self.read_unsigned_char()
|
||||
fragments = []
|
||||
for i in range(fragments_run_count):
|
||||
box_size, box_type, box_data = self.read_box_info()
|
||||
assert box_type == b'afrt'
|
||||
fragments.append(FlvReader(box_data).read_afrt())
|
||||
|
||||
return {
|
||||
'segments': segments,
|
||||
'fragments': fragments,
|
||||
}
|
||||
|
||||
def read_bootstrap_info(self):
|
||||
total_size, box_type, box_data = self.read_box_info()
|
||||
assert box_type == b'abst'
|
||||
return FlvReader(box_data).read_abst()
|
||||
|
||||
|
||||
def read_bootstrap_info(bootstrap_bytes):
|
||||
return FlvReader(bootstrap_bytes).read_bootstrap_info()
|
||||
|
||||
|
||||
def build_fragments_list(boot_info):
|
||||
""" Return a list of (segment, fragment) for each fragment in the video """
|
||||
res = []
|
||||
segment_run_table = boot_info['segments'][0]
|
||||
# I've only found videos with one segment
|
||||
segment_run_entry = segment_run_table['segment_run'][0]
|
||||
n_frags = segment_run_entry[1]
|
||||
fragment_run_entry_table = boot_info['fragments'][0]['fragments']
|
||||
first_frag_number = fragment_run_entry_table[0]['first']
|
||||
for (i, frag_number) in zip(range(1, n_frags+1), itertools.count(first_frag_number)):
|
||||
res.append((1, frag_number))
|
||||
return res
|
||||
|
||||
|
||||
def write_flv_header(stream, metadata):
|
||||
"""Writes the FLV header and the metadata to stream"""
|
||||
# FLV header
|
||||
stream.write(b'FLV\x01')
|
||||
stream.write(b'\x05')
|
||||
stream.write(b'\x00\x00\x00\x09')
|
||||
# FLV File body
|
||||
stream.write(b'\x00\x00\x00\x00')
|
||||
# FLVTAG
|
||||
# Script data
|
||||
stream.write(b'\x12')
|
||||
# Size of the metadata with 3 bytes
|
||||
stream.write(struct_pack('!L', len(metadata))[1:])
|
||||
stream.write(b'\x00\x00\x00\x00\x00\x00\x00')
|
||||
stream.write(metadata)
|
||||
# Magic numbers extracted from the output files produced by AdobeHDS.php
|
||||
#(https://github.com/K-S-V/Scripts)
|
||||
stream.write(b'\x00\x00\x01\x73')
|
||||
|
||||
|
||||
def _add_ns(prop):
|
||||
return '{http://ns.adobe.com/f4m/1.0}%s' % prop
|
||||
|
||||
|
||||
class HttpQuietDownloader(HttpFD):
|
||||
def to_screen(self, *args, **kargs):
|
||||
pass
|
||||
|
||||
|
||||
class F4mFD(FileDownloader):
|
||||
"""
|
||||
A downloader for f4m manifests or AdobeHDS.
|
||||
"""
|
||||
|
||||
def real_download(self, filename, info_dict):
|
||||
man_url = info_dict['url']
|
||||
self.to_screen('[download] Downloading f4m manifest')
|
||||
manifest = self.ydl.urlopen(man_url).read()
|
||||
self.report_destination(filename)
|
||||
http_dl = HttpQuietDownloader(self.ydl,
|
||||
{
|
||||
'continuedl': True,
|
||||
'quiet': True,
|
||||
'noprogress': True,
|
||||
'test': self.params.get('test', False),
|
||||
})
|
||||
|
||||
doc = etree.fromstring(manifest)
|
||||
formats = [(int(f.attrib.get('bitrate', -1)), f) for f in doc.findall(_add_ns('media'))]
|
||||
formats = sorted(formats, key=lambda f: f[0])
|
||||
rate, media = formats[-1]
|
||||
base_url = compat_urlparse.urljoin(man_url, media.attrib['url'])
|
||||
bootstrap = base64.b64decode(doc.find(_add_ns('bootstrapInfo')).text)
|
||||
metadata = base64.b64decode(media.find(_add_ns('metadata')).text)
|
||||
boot_info = read_bootstrap_info(bootstrap)
|
||||
fragments_list = build_fragments_list(boot_info)
|
||||
if self.params.get('test', False):
|
||||
# We only download the first fragment
|
||||
fragments_list = fragments_list[:1]
|
||||
total_frags = len(fragments_list)
|
||||
|
||||
tmpfilename = self.temp_name(filename)
|
||||
(dest_stream, tmpfilename) = sanitize_open(tmpfilename, 'wb')
|
||||
write_flv_header(dest_stream, metadata)
|
||||
|
||||
# This dict stores the download progress, it's updated by the progress
|
||||
# hook
|
||||
state = {
|
||||
'downloaded_bytes': 0,
|
||||
'frag_counter': 0,
|
||||
}
|
||||
start = time.time()
|
||||
|
||||
def frag_progress_hook(status):
|
||||
frag_total_bytes = status.get('total_bytes', 0)
|
||||
estimated_size = (state['downloaded_bytes'] +
|
||||
(total_frags - state['frag_counter']) * frag_total_bytes)
|
||||
if status['status'] == 'finished':
|
||||
state['downloaded_bytes'] += frag_total_bytes
|
||||
state['frag_counter'] += 1
|
||||
progress = self.calc_percent(state['frag_counter'], total_frags)
|
||||
byte_counter = state['downloaded_bytes']
|
||||
else:
|
||||
frag_downloaded_bytes = status['downloaded_bytes']
|
||||
byte_counter = state['downloaded_bytes'] + frag_downloaded_bytes
|
||||
frag_progress = self.calc_percent(frag_downloaded_bytes,
|
||||
frag_total_bytes)
|
||||
progress = self.calc_percent(state['frag_counter'], total_frags)
|
||||
progress += frag_progress / float(total_frags)
|
||||
|
||||
eta = self.calc_eta(start, time.time(), estimated_size, byte_counter)
|
||||
self.report_progress(progress, format_bytes(estimated_size),
|
||||
status.get('speed'), eta)
|
||||
http_dl.add_progress_hook(frag_progress_hook)
|
||||
|
||||
frags_filenames = []
|
||||
for (seg_i, frag_i) in fragments_list:
|
||||
name = 'Seg%d-Frag%d' % (seg_i, frag_i)
|
||||
url = base_url + name
|
||||
frag_filename = '%s-%s' % (tmpfilename, name)
|
||||
success = http_dl.download(frag_filename, {'url': url})
|
||||
if not success:
|
||||
return False
|
||||
with open(frag_filename, 'rb') as down:
|
||||
down_data = down.read()
|
||||
reader = FlvReader(down_data)
|
||||
while True:
|
||||
_, box_type, box_data = reader.read_box_info()
|
||||
if box_type == b'mdat':
|
||||
dest_stream.write(box_data)
|
||||
break
|
||||
frags_filenames.append(frag_filename)
|
||||
|
||||
self.report_finish(format_bytes(state['downloaded_bytes']), time.time() - start)
|
||||
|
||||
self.try_rename(tmpfilename, filename)
|
||||
for frag_file in frags_filenames:
|
||||
os.remove(frag_file)
|
||||
|
||||
fsize = os.path.getsize(encodeFilename(filename))
|
||||
self._hook_progress({
|
||||
'downloaded_bytes': fsize,
|
||||
'total_bytes': fsize,
|
||||
'filename': filename,
|
||||
'status': 'finished',
|
||||
})
|
||||
|
||||
return True
|
@ -85,6 +85,7 @@ class HttpFD(FileDownloader):
|
||||
else:
|
||||
# The length does not match, we start the download over
|
||||
self.report_unable_to_resume()
|
||||
resume_len = 0
|
||||
open_mode = 'wb'
|
||||
break
|
||||
# Retry
|
||||
|
@ -19,12 +19,15 @@ from .bbccouk import BBCCoUkIE
|
||||
from .blinkx import BlinkxIE
|
||||
from .bliptv import BlipTVIE, BlipTVUserIE
|
||||
from .bloomberg import BloombergIE
|
||||
from .br import BRIE
|
||||
from .breakcom import BreakIE
|
||||
from .brightcove import BrightcoveIE
|
||||
from .c56 import C56IE
|
||||
from .canal13cl import Canal13clIE
|
||||
from .canalplus import CanalplusIE
|
||||
from .canalc2 import Canalc2IE
|
||||
from .cbs import CBSIE
|
||||
from .ceskatelevize import CeskaTelevizeIE
|
||||
from .channel9 import Channel9IE
|
||||
from .chilloutzone import ChilloutzoneIE
|
||||
from .cinemassacre import CinemassacreIE
|
||||
@ -73,6 +76,7 @@ from .fktv import (
|
||||
FKTVPosteckeIE,
|
||||
)
|
||||
from .flickr import FlickrIE
|
||||
from .fourtube import FourTubeIE
|
||||
from .franceinter import FranceInterIE
|
||||
from .francetv import (
|
||||
PluzzIE,
|
||||
@ -87,10 +91,12 @@ from .funnyordie import FunnyOrDieIE
|
||||
from .gamekings import GamekingsIE
|
||||
from .gamespot import GameSpotIE
|
||||
from .gametrailers import GametrailersIE
|
||||
from .gdcvault import GDCVaultIE
|
||||
from .generic import GenericIE
|
||||
from .googleplus import GooglePlusIE
|
||||
from .googlesearch import GoogleSearchIE
|
||||
from .hark import HarkIE
|
||||
from .helsinki import HelsinkiIE
|
||||
from .hotnewhiphop import HotNewHipHopIE
|
||||
from .howcast import HowcastIE
|
||||
from .huffpost import HuffPostIE
|
||||
@ -130,11 +136,12 @@ from .lynda import (
|
||||
)
|
||||
from .m6 import M6IE
|
||||
from .macgamestore import MacGameStoreIE
|
||||
from .mailru import MailRuIE
|
||||
from .malemotion import MalemotionIE
|
||||
from .mdr import MDRIE
|
||||
from .metacafe import MetacafeIE
|
||||
from .metacritic import MetacriticIE
|
||||
from .mit import TechTVMITIE, MITIE
|
||||
from .mit import TechTVMITIE, MITIE, OCWMITIE
|
||||
from .mixcloud import MixcloudIE
|
||||
from .mpora import MporaIE
|
||||
from .mofosex import MofosexIE
|
||||
@ -149,7 +156,10 @@ from .myspass import MySpassIE
|
||||
from .myvideo import MyVideoIE
|
||||
from .naver import NaverIE
|
||||
from .nba import NBAIE
|
||||
from .nbc import NBCNewsIE
|
||||
from .nbc import (
|
||||
NBCIE,
|
||||
NBCNewsIE,
|
||||
)
|
||||
from .ndr import NDRIE
|
||||
from .ndtv import NDTVIE
|
||||
from .newgrounds import NewgroundsIE
|
||||
@ -158,7 +168,7 @@ from .nhl import NHLIE, NHLVideocenterIE
|
||||
from .niconico import NiconicoIE
|
||||
from .ninegag import NineGagIE
|
||||
from .normalboots import NormalbootsIE
|
||||
from .novamov import NovamovIE
|
||||
from .novamov import NovaMovIE
|
||||
from .nowness import NownessIE
|
||||
from .nowvideo import NowVideoIE
|
||||
from .ooyala import OoyalaIE
|
||||
@ -169,6 +179,7 @@ from .podomatic import PodomaticIE
|
||||
from .pornhd import PornHdIE
|
||||
from .pornhub import PornHubIE
|
||||
from .pornotube import PornotubeIE
|
||||
from .prosiebensat1 import ProSiebenSat1IE
|
||||
from .pyvideo import PyvideoIE
|
||||
from .radiofrance import RadioFranceIE
|
||||
from .rbmaradio import RBMARadioIE
|
||||
@ -184,6 +195,7 @@ from .rutube import (
|
||||
RutubeMovieIE,
|
||||
RutubePersonIE,
|
||||
)
|
||||
from .savefrom import SaveFromIE
|
||||
from .servingsys import ServingSysIE
|
||||
from .sina import SinaIE
|
||||
from .slashdot import SlashdotIE
|
||||
@ -209,10 +221,12 @@ from .statigram import StatigramIE
|
||||
from .steam import SteamIE
|
||||
from .streamcloud import StreamcloudIE
|
||||
from .streamcz import StreamCZIE
|
||||
from .syfy import SyfyIE
|
||||
from .sztvhu import SztvHuIE
|
||||
from .teamcoco import TeamcocoIE
|
||||
from .techtalks import TechTalksIE
|
||||
from .ted import TEDIE
|
||||
from .testurl import TestURLIE
|
||||
from .tf1 import TF1IE
|
||||
from .theplatform import ThePlatformIE
|
||||
from .thisav import ThisAVIE
|
||||
@ -220,10 +234,12 @@ from .tinypic import TinyPicIE
|
||||
from .toutv import TouTvIE
|
||||
from .traileraddict import TrailerAddictIE
|
||||
from .trilulilu import TriluliluIE
|
||||
from .trutube import TruTubeIE
|
||||
from .tube8 import Tube8IE
|
||||
from .tudou import TudouIE
|
||||
from .tumblr import TumblrIE
|
||||
from .tutv import TutvIE
|
||||
from .tvigle import TvigleIE
|
||||
from .tvp import TvpIE
|
||||
from .unistra import UnistraIE
|
||||
from .ustream import UstreamIE, UstreamChannelIE
|
||||
@ -234,6 +250,7 @@ from .vesti import VestiIE
|
||||
from .vevo import VevoIE
|
||||
from .vice import ViceIE
|
||||
from .viddler import ViddlerIE
|
||||
from .videobam import VideoBamIE
|
||||
from .videodetective import VideoDetectiveIE
|
||||
from .videofyme import VideofyMeIE
|
||||
from .videopremium import VideoPremiumIE
|
||||
@ -268,19 +285,20 @@ from .youku import YoukuIE
|
||||
from .youporn import YouPornIE
|
||||
from .youtube import (
|
||||
YoutubeIE,
|
||||
YoutubePlaylistIE,
|
||||
YoutubeSearchIE,
|
||||
YoutubeSearchDateIE,
|
||||
YoutubeUserIE,
|
||||
YoutubeChannelIE,
|
||||
YoutubeShowIE,
|
||||
YoutubeSubscriptionsIE,
|
||||
YoutubeRecommendedIE,
|
||||
YoutubeTruncatedURLIE,
|
||||
YoutubeWatchLaterIE,
|
||||
YoutubeFavouritesIE,
|
||||
YoutubeHistoryIE,
|
||||
YoutubePlaylistIE,
|
||||
YoutubeRecommendedIE,
|
||||
YoutubeSearchDateIE,
|
||||
YoutubeSearchIE,
|
||||
YoutubeSearchURLIE,
|
||||
YoutubeShowIE,
|
||||
YoutubeSubscriptionsIE,
|
||||
YoutubeTopListIE,
|
||||
YoutubeTruncatedURLIE,
|
||||
YoutubeUserIE,
|
||||
YoutubeWatchLaterIE,
|
||||
)
|
||||
from .zdf import ZDFIE
|
||||
|
||||
|
@ -5,7 +5,7 @@ from .common import InfoExtractor
|
||||
|
||||
|
||||
class AcademicEarthCourseIE(InfoExtractor):
|
||||
_VALID_URL = r'^https?://(?:www\.)?academicearth\.org/(?:courses|playlists)/(?P<id>[^?#/]+)'
|
||||
_VALID_URL = r'^https?://(?:www\.)?academicearth\.org/playlists/(?P<id>[^?#/]+)'
|
||||
IE_NAME = 'AcademicEarth:Course'
|
||||
|
||||
def _real_extract(self, url):
|
||||
@ -14,12 +14,12 @@ class AcademicEarthCourseIE(InfoExtractor):
|
||||
|
||||
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, u'title')
|
||||
description = self._html_search_regex(
|
||||
r'<p class="excerpt">(.*?)</p>',
|
||||
r'<p class="excerpt"[^>]*?>(.*?)</p>',
|
||||
webpage, u'description', fatal=False)
|
||||
urls = re.findall(
|
||||
r'<h3 class="lecture-title"><a target="_blank" href="([^"]+)">',
|
||||
r'<li class="lecture-preview">\s*?<a target="_blank" href="([^"]+)">',
|
||||
webpage)
|
||||
entries = [self.url_result(u) for u in urls]
|
||||
|
||||
|
@ -13,13 +13,13 @@ class BBCCoUkIE(SubtitlesInfoExtractor):
|
||||
|
||||
_TESTS = [
|
||||
{
|
||||
'url': 'http://www.bbc.co.uk/programmes/p01q7wz1',
|
||||
'url': 'http://www.bbc.co.uk/programmes/b039g8p7',
|
||||
'info_dict': {
|
||||
'id': 'p01q7wz4',
|
||||
'id': 'b039d07m',
|
||||
'ext': 'flv',
|
||||
'title': 'Friction: Blu Mar Ten guest mix: Blu Mar Ten - Guest Mix',
|
||||
'description': 'Blu Mar Ten deliver a Guest Mix for Friction.',
|
||||
'duration': 1936,
|
||||
'title': 'Kaleidoscope: Leonard Cohen',
|
||||
'description': 'md5:db4755d7a665ae72343779f7dacb402c',
|
||||
'duration': 1740,
|
||||
},
|
||||
'params': {
|
||||
# rtmp download
|
||||
@ -38,7 +38,8 @@ class BBCCoUkIE(SubtitlesInfoExtractor):
|
||||
'params': {
|
||||
# rtmp download
|
||||
'skip_download': True,
|
||||
}
|
||||
},
|
||||
'skip': 'Episode is no longer available on BBC iPlayer Radio',
|
||||
},
|
||||
{
|
||||
'url': 'http://www.bbc.co.uk/iplayer/episode/b03vhd1f/The_Voice_UK_Series_3_Blind_Auditions_5/',
|
||||
@ -161,6 +162,11 @@ class BBCCoUkIE(SubtitlesInfoExtractor):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
group_id = mobj.group('id')
|
||||
|
||||
webpage = self._download_webpage(url, group_id, 'Downloading video page')
|
||||
if re.search(r'id="emp-error" class="notinuk">', webpage):
|
||||
raise ExtractorError('Currently BBC iPlayer TV programmes are available to play in the UK only',
|
||||
expected=True)
|
||||
|
||||
playlist = self._download_xml('http://www.bbc.co.uk/iplayer/playlist/%s' % group_id, group_id,
|
||||
'Downloading playlist XML')
|
||||
|
||||
|
80
youtube_dl/extractor/br.py
Normal file
80
youtube_dl/extractor/br.py
Normal file
@ -0,0 +1,80 @@
|
||||
# coding: utf-8
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import ExtractorError
|
||||
|
||||
|
||||
class BRIE(InfoExtractor):
|
||||
IE_DESC = "Bayerischer Rundfunk Mediathek"
|
||||
_VALID_URL = r"^https?://(?:www\.)?br\.de/mediathek/video/(?:sendungen/)?(?P<id>[a-z0-9\-]+)\.html$"
|
||||
_BASE_URL = "http://www.br.de"
|
||||
|
||||
_TEST = {
|
||||
"url": "http://www.br.de/mediathek/video/anselm-gruen-114.html",
|
||||
"md5": "c4f83cf0f023ba5875aba0bf46860df2",
|
||||
"info_dict": {
|
||||
"id": "2c8d81c5-6fb7-4a74-88d4-e768e5856532",
|
||||
"ext": "mp4",
|
||||
"title": "Feiern und Verzichten",
|
||||
"description": "Anselm Grün: Feiern und Verzichten",
|
||||
"uploader": "BR/Birgit Baier",
|
||||
"upload_date": "20140301"
|
||||
}
|
||||
}
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
display_id = mobj.group('id')
|
||||
page = self._download_webpage(url, display_id)
|
||||
xml_url = self._search_regex(
|
||||
r"return BRavFramework\.register\(BRavFramework\('avPlayer_(?:[a-f0-9-]{36})'\)\.setup\({dataURL:'(/mediathek/video/[a-z0-9/~_.-]+)'}\)\);", page, "XMLURL")
|
||||
xml = self._download_xml(self._BASE_URL + xml_url, None)
|
||||
|
||||
videos = [{
|
||||
"id": xml_video.get("externalId"),
|
||||
"title": xml_video.find("title").text,
|
||||
"formats": self._extract_formats(xml_video.find("assets")),
|
||||
"thumbnails": self._extract_thumbnails(xml_video.find("teaserImage/variants")),
|
||||
"description": " ".join(xml_video.find("shareTitle").text.splitlines()),
|
||||
"uploader": xml_video.find("author").text,
|
||||
"upload_date": "".join(reversed(xml_video.find("broadcastDate").text.split("."))),
|
||||
"webpage_url": xml_video.find("permalink").text,
|
||||
} for xml_video in xml.findall("video")]
|
||||
|
||||
if len(videos) > 1:
|
||||
self._downloader.report_warning(
|
||||
'found multiple videos; please '
|
||||
'report this with the video URL to http://yt-dl.org/bug')
|
||||
if not videos:
|
||||
raise ExtractorError('No video entries found')
|
||||
return videos[0]
|
||||
|
||||
def _extract_formats(self, assets):
|
||||
formats = [{
|
||||
"url": asset.find("downloadUrl").text,
|
||||
"ext": asset.find("mediaType").text,
|
||||
"format_id": asset.get("type"),
|
||||
"width": int(asset.find("frameWidth").text),
|
||||
"height": int(asset.find("frameHeight").text),
|
||||
"tbr": int(asset.find("bitrateVideo").text),
|
||||
"abr": int(asset.find("bitrateAudio").text),
|
||||
"vcodec": asset.find("codecVideo").text,
|
||||
"container": asset.find("mediaType").text,
|
||||
"filesize": int(asset.find("size").text),
|
||||
} for asset in assets.findall("asset")
|
||||
if asset.find("downloadUrl") is not None]
|
||||
|
||||
self._sort_formats(formats)
|
||||
return formats
|
||||
|
||||
def _extract_thumbnails(self, variants):
|
||||
thumbnails = [{
|
||||
"url": self._BASE_URL + variant.find("url").text,
|
||||
"width": int(variant.find("width").text),
|
||||
"height": int(variant.find("height").text),
|
||||
} for variant in variants.findall("variant")]
|
||||
thumbnails.sort(key=lambda x: x["width"] * x["height"], reverse=True)
|
||||
return thumbnails
|
@ -23,8 +23,8 @@ class BreakIE(InfoExtractor):
|
||||
video_id = mobj.group(1).split("-")[-1]
|
||||
embed_url = 'http://www.break.com/embed/%s' % video_id
|
||||
webpage = self._download_webpage(embed_url, video_id)
|
||||
info_json = self._search_regex(r'var embedVars = ({.*?});', webpage,
|
||||
'info json', flags=re.DOTALL)
|
||||
info_json = self._search_regex(r'var embedVars = ({.*})\s*?</script>',
|
||||
webpage, 'info json', flags=re.DOTALL)
|
||||
info = json.loads(info_json)
|
||||
video_url = info['videoUri']
|
||||
m_youtube = re.search(r'(https?://www\.youtube\.com/watch\?v=.*)', video_url)
|
||||
|
48
youtube_dl/extractor/canal13cl.py
Normal file
48
youtube_dl/extractor/canal13cl.py
Normal file
@ -0,0 +1,48 @@
|
||||
# coding: utf-8
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
|
||||
|
||||
class Canal13clIE(InfoExtractor):
|
||||
_VALID_URL = r'^http://(?:www\.)?13\.cl/(?:[^/?#]+/)*(?P<id>[^/?#]+)'
|
||||
_TEST = {
|
||||
'url': 'http://www.13.cl/t13/nacional/el-circulo-de-hierro-de-michelle-bachelet-en-su-regreso-a-la-moneda',
|
||||
'md5': '4cb1fa38adcad8fea88487a078831755',
|
||||
'info_dict': {
|
||||
'id': '1403022125',
|
||||
'display_id': 'el-circulo-de-hierro-de-michelle-bachelet-en-su-regreso-a-la-moneda',
|
||||
'ext': 'mp4',
|
||||
'title': 'El "círculo de hierro" de Michelle Bachelet en su regreso a La Moneda',
|
||||
'description': '(Foto: Agencia Uno) En nueve días más, Michelle Bachelet va a asumir por segunda vez como presidenta de la República. Entre aquellos que la acompañarán hay caras que se repiten y otras que se consolidan en su entorno de colaboradores más cercanos.',
|
||||
}
|
||||
}
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
display_id = mobj.group('id')
|
||||
|
||||
webpage = self._download_webpage(url, display_id)
|
||||
|
||||
title = self._html_search_meta(
|
||||
'twitter:title', webpage, 'title', fatal=True)
|
||||
description = self._html_search_meta(
|
||||
'twitter:description', webpage, 'description')
|
||||
url = self._html_search_regex(
|
||||
r'articuloVideo = \"(.*?)\"', webpage, 'url')
|
||||
real_id = self._search_regex(
|
||||
r'[^0-9]([0-9]{7,})[^0-9]', url, 'id', default=display_id)
|
||||
thumbnail = self._html_search_regex(
|
||||
r'articuloImagen = \"(.*?)\"', webpage, 'thumbnail')
|
||||
|
||||
return {
|
||||
'id': real_id,
|
||||
'display_id': display_id,
|
||||
'url': url,
|
||||
'title': title,
|
||||
'description': description,
|
||||
'ext': 'mp4',
|
||||
'thumbnail': thumbnail,
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
# coding: utf-8
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
@ -9,11 +11,12 @@ class Canalc2IE(InfoExtractor):
|
||||
_VALID_URL = r'http://.*?\.canalc2\.tv/video\.asp\?.*?idVideo=(?P<id>\d+)'
|
||||
|
||||
_TEST = {
|
||||
u'url': u'http://www.canalc2.tv/video.asp?idVideo=12163&voir=oui',
|
||||
u'file': u'12163.mp4',
|
||||
u'md5': u'060158428b650f896c542dfbb3d6487f',
|
||||
u'info_dict': {
|
||||
u'title': u'Terrasses du Numérique'
|
||||
'url': 'http://www.canalc2.tv/video.asp?idVideo=12163&voir=oui',
|
||||
'md5': '060158428b650f896c542dfbb3d6487f',
|
||||
'info_dict': {
|
||||
'id': '12163',
|
||||
'ext': 'mp4',
|
||||
'title': 'Terrasses du Numérique'
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,10 +31,11 @@ class Canalc2IE(InfoExtractor):
|
||||
video_url = 'http://vod-flash.u-strasbg.fr:8080/' + file_name
|
||||
|
||||
title = self._html_search_regex(
|
||||
r'class="evenement8">(.*?)</a>', webpage, u'title')
|
||||
r'class="evenement8">(.*?)</a>', webpage, 'title')
|
||||
|
||||
return {'id': video_id,
|
||||
'ext': 'mp4',
|
||||
'url': video_url,
|
||||
'title': title,
|
||||
}
|
||||
return {
|
||||
'id': video_id,
|
||||
'ext': 'mp4',
|
||||
'url': video_url,
|
||||
'title': title,
|
||||
}
|
||||
|
126
youtube_dl/extractor/ceskatelevize.py
Normal file
126
youtube_dl/extractor/ceskatelevize.py
Normal file
@ -0,0 +1,126 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import (
|
||||
compat_urllib_request,
|
||||
compat_urllib_parse,
|
||||
compat_urllib_parse_urlparse,
|
||||
ExtractorError,
|
||||
)
|
||||
|
||||
|
||||
class CeskaTelevizeIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://www\.ceskatelevize\.cz/(porady|ivysilani)/(.+/)?(?P<id>[^?#]+)'
|
||||
|
||||
_TESTS = [
|
||||
{
|
||||
'url': 'http://www.ceskatelevize.cz/ivysilani/10532695142-prvni-republika/213512120230004-spanelska-chripka',
|
||||
'info_dict': {
|
||||
'id': '213512120230004',
|
||||
'ext': 'flv',
|
||||
'title': 'První republika: Španělská chřipka',
|
||||
'duration': 3107.4,
|
||||
},
|
||||
'params': {
|
||||
'skip_download': True, # requires rtmpdump
|
||||
},
|
||||
'skip': 'Works only from Czech Republic.',
|
||||
},
|
||||
{
|
||||
'url': 'http://www.ceskatelevize.cz/ivysilani/1030584952-tsatsiki-maminka-a-policajt',
|
||||
'info_dict': {
|
||||
'id': '20138143440',
|
||||
'ext': 'flv',
|
||||
'title': 'Tsatsiki, maminka a policajt',
|
||||
'duration': 6754.1,
|
||||
},
|
||||
'params': {
|
||||
'skip_download': True, # requires rtmpdump
|
||||
},
|
||||
'skip': 'Works only from Czech Republic.',
|
||||
},
|
||||
{
|
||||
'url': 'http://www.ceskatelevize.cz/ivysilani/10532695142-prvni-republika/bonus/14716-zpevacka-z-duparny-bobina',
|
||||
'info_dict': {
|
||||
'id': '14716',
|
||||
'ext': 'flv',
|
||||
'title': 'První republika: Zpěvačka z Dupárny Bobina',
|
||||
'duration': 90,
|
||||
},
|
||||
'params': {
|
||||
'skip_download': True, # requires rtmpdump
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
def _real_extract(self, url):
|
||||
url = url.replace('/porady/', '/ivysilani/').replace('/video/', '')
|
||||
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
video_id = mobj.group('id')
|
||||
|
||||
webpage = self._download_webpage(url, video_id)
|
||||
|
||||
NOT_AVAILABLE_STRING = 'This content is not available at your territory due to limited copyright.'
|
||||
if '%s</p>' % NOT_AVAILABLE_STRING in webpage:
|
||||
raise ExtractorError(NOT_AVAILABLE_STRING, expected=True)
|
||||
|
||||
typ = self._html_search_regex(r'getPlaylistUrl\(\[\{"type":"(.+?)","id":".+?"\}\],', webpage, 'type')
|
||||
episode_id = self._html_search_regex(r'getPlaylistUrl\(\[\{"type":".+?","id":"(.+?)"\}\],', webpage, 'episode_id')
|
||||
|
||||
data = {
|
||||
'playlist[0][type]': typ,
|
||||
'playlist[0][id]': episode_id,
|
||||
'requestUrl': compat_urllib_parse_urlparse(url).path,
|
||||
'requestSource': 'iVysilani',
|
||||
}
|
||||
|
||||
req = compat_urllib_request.Request('http://www.ceskatelevize.cz/ivysilani/ajax/get-playlist-url',
|
||||
data=compat_urllib_parse.urlencode(data))
|
||||
|
||||
req.add_header('Content-type', 'application/x-www-form-urlencoded')
|
||||
req.add_header('x-addr', '127.0.0.1')
|
||||
req.add_header('X-Requested-With', 'XMLHttpRequest')
|
||||
req.add_header('Referer', url)
|
||||
|
||||
playlistpage = self._download_json(req, video_id)
|
||||
|
||||
req = compat_urllib_request.Request(compat_urllib_parse.unquote(playlistpage['url']))
|
||||
req.add_header('Referer', url)
|
||||
|
||||
playlist = self._download_xml(req, video_id)
|
||||
|
||||
formats = []
|
||||
for i in playlist.find('smilRoot/body'):
|
||||
if 'AD' not in i.attrib['id']:
|
||||
base_url = i.attrib['base']
|
||||
parsedurl = compat_urllib_parse_urlparse(base_url)
|
||||
duration = i.attrib['duration']
|
||||
|
||||
for video in i.findall('video'):
|
||||
if video.attrib['label'] != 'AD':
|
||||
format_id = video.attrib['label']
|
||||
play_path = video.attrib['src']
|
||||
vbr = int(video.attrib['system-bitrate'])
|
||||
|
||||
formats.append({
|
||||
'format_id': format_id,
|
||||
'url': base_url,
|
||||
'vbr': vbr,
|
||||
'play_path': play_path,
|
||||
'app': parsedurl.path[1:] + '?' + parsedurl.query,
|
||||
'rtmp_live': True,
|
||||
'ext': 'flv',
|
||||
})
|
||||
|
||||
self._sort_formats(formats)
|
||||
|
||||
return {
|
||||
'id': episode_id,
|
||||
'title': self._html_search_regex(r'<title>(.+?) — iVysílání — Česká televize</title>', webpage, 'title'),
|
||||
'duration': float(duration),
|
||||
'formats': formats,
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
# encoding: utf-8
|
||||
from __future__ import unicode_literals
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
@ -8,73 +9,63 @@ from ..utils import (
|
||||
|
||||
|
||||
class CinemassacreIE(InfoExtractor):
|
||||
_VALID_URL = r'(?:http://)?(?:www\.)?(?P<url>cinemassacre\.com/(?P<date_Y>[0-9]{4})/(?P<date_m>[0-9]{2})/(?P<date_d>[0-9]{2})/.+?)(?:[/?].*)?'
|
||||
_TESTS = [{
|
||||
u'url': u'http://cinemassacre.com/2012/11/10/avgn-the-movie-trailer/',
|
||||
u'file': u'19911.flv',
|
||||
u'info_dict': {
|
||||
u'upload_date': u'20121110',
|
||||
u'title': u'“Angry Video Game Nerd: The Movie” – Trailer',
|
||||
u'description': u'md5:fb87405fcb42a331742a0dce2708560b',
|
||||
_VALID_URL = r'http://(?:www\.)?cinemassacre\.com/(?P<date_Y>[0-9]{4})/(?P<date_m>[0-9]{2})/(?P<date_d>[0-9]{2})/.+?'
|
||||
_TESTS = [
|
||||
{
|
||||
'url': 'http://cinemassacre.com/2012/11/10/avgn-the-movie-trailer/',
|
||||
'file': '19911.mp4',
|
||||
'md5': 'fde81fbafaee331785f58cd6c0d46190',
|
||||
'info_dict': {
|
||||
'upload_date': '20121110',
|
||||
'title': '“Angry Video Game Nerd: The Movie” – Trailer',
|
||||
'description': 'md5:fb87405fcb42a331742a0dce2708560b',
|
||||
},
|
||||
},
|
||||
u'params': {
|
||||
# rtmp download
|
||||
u'skip_download': True,
|
||||
},
|
||||
},
|
||||
{
|
||||
u'url': u'http://cinemassacre.com/2013/10/02/the-mummys-hand-1940',
|
||||
u'file': u'521be8ef82b16.flv',
|
||||
u'info_dict': {
|
||||
u'upload_date': u'20131002',
|
||||
u'title': u'The Mummy’s Hand (1940)',
|
||||
},
|
||||
u'params': {
|
||||
# rtmp download
|
||||
u'skip_download': True,
|
||||
},
|
||||
}]
|
||||
{
|
||||
'url': 'http://cinemassacre.com/2013/10/02/the-mummys-hand-1940',
|
||||
'file': '521be8ef82b16.mp4',
|
||||
'md5': 'd72f10cd39eac4215048f62ab477a511',
|
||||
'info_dict': {
|
||||
'upload_date': '20131002',
|
||||
'title': 'The Mummy’s Hand (1940)',
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
|
||||
webpage_url = u'http://' + mobj.group('url')
|
||||
webpage = self._download_webpage(webpage_url, None) # Don't know video id yet
|
||||
webpage = self._download_webpage(url, None) # Don't know video id yet
|
||||
video_date = mobj.group('date_Y') + mobj.group('date_m') + mobj.group('date_d')
|
||||
mobj = re.search(r'src="(?P<embed_url>http://player\.screenwavemedia\.com/play/[a-zA-Z]+\.php\?id=(?:Cinemassacre-)?(?P<video_id>.+?))"', webpage)
|
||||
if not mobj:
|
||||
raise ExtractorError(u'Can\'t extract embed url and video id')
|
||||
playerdata_url = mobj.group(u'embed_url')
|
||||
video_id = mobj.group(u'video_id')
|
||||
raise ExtractorError('Can\'t extract embed url and video id')
|
||||
playerdata_url = mobj.group('embed_url')
|
||||
video_id = mobj.group('video_id')
|
||||
|
||||
video_title = self._html_search_regex(r'<title>(?P<title>.+?)\|',
|
||||
webpage, u'title')
|
||||
webpage, 'title')
|
||||
video_description = self._html_search_regex(r'<div class="entry-content">(?P<description>.+?)</div>',
|
||||
webpage, u'description', flags=re.DOTALL, fatal=False)
|
||||
webpage, 'description', flags=re.DOTALL, fatal=False)
|
||||
if len(video_description) == 0:
|
||||
video_description = None
|
||||
|
||||
playerdata = self._download_webpage(playerdata_url, video_id)
|
||||
url = self._html_search_regex(r'\'streamer\': \'(?P<url>[^\']+)\'', playerdata, u'url')
|
||||
|
||||
sd_file = self._html_search_regex(r'\'file\': \'(?P<sd_file>[^\']+)\'', playerdata, u'sd_file')
|
||||
hd_file = self._html_search_regex(r'\'?file\'?: "(?P<hd_file>[^"]+)"', playerdata, u'hd_file')
|
||||
video_thumbnail = self._html_search_regex(r'\'image\': \'(?P<thumbnail>[^\']+)\'', playerdata, u'thumbnail', fatal=False)
|
||||
sd_url = self._html_search_regex(r'file: \'(?P<sd_file>[^\']+)\', label: \'SD\'', playerdata, 'sd_file')
|
||||
hd_url = self._html_search_regex(r'file: \'(?P<hd_file>[^\']+)\', label: \'HD\'', playerdata, 'hd_file')
|
||||
video_thumbnail = self._html_search_regex(r'image: \'(?P<thumbnail>[^\']+)\'', playerdata, 'thumbnail', fatal=False)
|
||||
|
||||
formats = [
|
||||
{
|
||||
'url': url,
|
||||
'play_path': 'mp4:' + sd_file,
|
||||
'rtmp_live': True, # workaround
|
||||
'ext': 'flv',
|
||||
'url': sd_url,
|
||||
'ext': 'mp4',
|
||||
'format': 'sd',
|
||||
'format_id': 'sd',
|
||||
},
|
||||
{
|
||||
'url': url,
|
||||
'play_path': 'mp4:' + hd_file,
|
||||
'rtmp_live': True, # workaround
|
||||
'ext': 'flv',
|
||||
'url': hd_url,
|
||||
'ext': 'mp4',
|
||||
'format': 'hd',
|
||||
'format_id': 'hd',
|
||||
},
|
||||
|
@ -40,7 +40,7 @@ class CollegeHumorIE(InfoExtractor):
|
||||
'id': 'W5gMp3ZjYg4',
|
||||
'ext': 'mp4',
|
||||
'title': 'Funny Dogs Protecting Babies Compilation 2014 [NEW HD]',
|
||||
'uploader': 'Funnyplox TV',
|
||||
'uploader': 'FunnyPlox TV',
|
||||
'uploader_id': 'funnyploxtv',
|
||||
'description': 'md5:7ded37421526d54afdf005e25bc2b7a3',
|
||||
'upload_date': '20140128',
|
||||
|
@ -88,6 +88,10 @@ class InfoExtractor(object):
|
||||
|
||||
The following fields are optional:
|
||||
|
||||
display_id An alternative identifier for the video, not necessarily
|
||||
unique, but available before title. Typically, id is
|
||||
something like "4234987", title "Dancing naked mole rats",
|
||||
and display_id "dancing-naked-mole-rats"
|
||||
thumbnails: A list of dictionaries (with the entries "resolution" and
|
||||
"url") for the varying thumbnails
|
||||
thumbnail: Full URL to a video thumbnail image.
|
||||
@ -432,14 +436,14 @@ class InfoExtractor(object):
|
||||
if secure: regexes = self._og_regexes('video:secure_url') + regexes
|
||||
return self._html_search_regex(regexes, html, name, **kargs)
|
||||
|
||||
def _html_search_meta(self, name, html, display_name=None):
|
||||
def _html_search_meta(self, name, html, display_name=None, fatal=False):
|
||||
if display_name is None:
|
||||
display_name = name
|
||||
return self._html_search_regex(
|
||||
r'''(?ix)<meta
|
||||
(?=[^>]+(?:itemprop|name|property)=["\']%s["\'])
|
||||
[^>]+content=["\']([^"\']+)["\']''' % re.escape(name),
|
||||
html, display_name, fatal=False)
|
||||
html, display_name, fatal=fatal)
|
||||
|
||||
def _dc_search_uploader(self, html):
|
||||
return self._html_search_meta('dc.creator', html, 'uploader')
|
||||
|
@ -1,7 +1,11 @@
|
||||
# encoding: utf-8
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re, base64, zlib
|
||||
import re
|
||||
import json
|
||||
import base64
|
||||
import zlib
|
||||
|
||||
from hashlib import sha1
|
||||
from math import pow, sqrt, floor
|
||||
from .common import InfoExtractor
|
||||
@ -19,13 +23,15 @@ from ..aes import (
|
||||
inc,
|
||||
)
|
||||
|
||||
|
||||
class CrunchyrollIE(InfoExtractor):
|
||||
_VALID_URL = r'(?:https?://)?(?:(?P<prefix>www|m)\.)?(?P<url>crunchyroll\.com/(?:[^/]*/[^/?&]*?|media/\?id=)(?P<video_id>[0-9]+))(?:[/?&]|$)'
|
||||
_TESTS = [{
|
||||
_VALID_URL = r'https?://(?:(?P<prefix>www|m)\.)?(?P<url>crunchyroll\.com/(?:[^/]*/[^/?&]*?|media/\?id=)(?P<video_id>[0-9]+))(?:[/?&]|$)'
|
||||
_TEST = {
|
||||
'url': 'http://www.crunchyroll.com/wanna-be-the-strongest-in-the-world/episode-1-an-idol-wrestler-is-born-645513',
|
||||
'file': '645513.flv',
|
||||
#'md5': 'b1639fd6ddfaa43788c85f6d1dddd412',
|
||||
'info_dict': {
|
||||
'id': '645513',
|
||||
'ext': 'flv',
|
||||
'title': 'Wanna be the Strongest in the World Episode 1 – An Idol-Wrestler is Born!',
|
||||
'description': 'md5:2d17137920c64f2f49981a7797d275ef',
|
||||
'thumbnail': 'http://img1.ak.crunchyroll.com/i/spire1-tmb/20c6b5e10f1a47b10516877d3c039cae1380951166_full.jpg',
|
||||
@ -36,7 +42,7 @@ class CrunchyrollIE(InfoExtractor):
|
||||
# rtmp
|
||||
'skip_download': True,
|
||||
},
|
||||
}]
|
||||
}
|
||||
|
||||
_FORMAT_IDS = {
|
||||
'360': ('60', '106'),
|
||||
@ -80,9 +86,8 @@ class CrunchyrollIE(InfoExtractor):
|
||||
return zlib.decompress(decrypted_data)
|
||||
|
||||
def _convert_subtitles_to_srt(self, subtitles):
|
||||
i=1
|
||||
output = ''
|
||||
for start, end, text in re.findall(r'<event [^>]*?start="([^"]+)" [^>]*?end="([^"]+)" [^>]*?text="([^"]+)"[^>]*?>', subtitles):
|
||||
for i, (start, end, text) in enumerate(re.findall(r'<event [^>]*?start="([^"]+)" [^>]*?end="([^"]+)" [^>]*?text="([^"]+)"[^>]*?>', subtitles), 1):
|
||||
start = start.replace('.', ',')
|
||||
end = end.replace('.', ',')
|
||||
text = clean_html(text)
|
||||
@ -90,7 +95,6 @@ class CrunchyrollIE(InfoExtractor):
|
||||
if not text:
|
||||
continue
|
||||
output += '%d\n%s --> %s\n%s\n\n' % (i, start, end, text)
|
||||
i+=1
|
||||
return output
|
||||
|
||||
def _real_extract(self,url):
|
||||
@ -108,6 +112,12 @@ class CrunchyrollIE(InfoExtractor):
|
||||
if note_m:
|
||||
raise ExtractorError(note_m)
|
||||
|
||||
mobj = re.search(r'Page\.messaging_box_controller\.addItems\(\[(?P<msg>{.+?})\]\)', webpage)
|
||||
if mobj:
|
||||
msg = json.loads(mobj.group('msg'))
|
||||
if msg.get('type') == 'error':
|
||||
raise ExtractorError('crunchyroll returned error: %s' % msg['message_body'], expected=True)
|
||||
|
||||
video_title = self._html_search_regex(r'<h1[^>]*>(.+?)</h1>', webpage, 'video_title', flags=re.DOTALL)
|
||||
video_title = re.sub(r' {2,}', ' ', video_title)
|
||||
video_description = self._html_search_regex(r'"description":"([^"]+)', webpage, 'video_description', default='')
|
||||
@ -161,7 +171,7 @@ class CrunchyrollIE(InfoExtractor):
|
||||
data = base64.b64decode(data)
|
||||
|
||||
subtitle = self._decrypt_subtitles(data, iv, id).decode('utf-8')
|
||||
lang_code = self._search_regex(r'lang_code=\'([^\']+)', subtitle, 'subtitle_lang_code', fatal=False)
|
||||
lang_code = self._search_regex(r'lang_code=["\']([^"\']+)', subtitle, 'subtitle_lang_code', fatal=False)
|
||||
if not lang_code:
|
||||
continue
|
||||
subtitles[lang_code] = self._convert_subtitles_to_srt(subtitle)
|
||||
|
95
youtube_dl/extractor/fourtube.py
Normal file
95
youtube_dl/extractor/fourtube.py
Normal file
@ -0,0 +1,95 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import (
|
||||
compat_urllib_request,
|
||||
unified_strdate,
|
||||
str_to_int,
|
||||
parse_duration,
|
||||
clean_html,
|
||||
)
|
||||
|
||||
|
||||
class FourTubeIE(InfoExtractor):
|
||||
IE_NAME = '4tube'
|
||||
_VALID_URL = r'https?://(?:www\.)?4tube\.com/videos/(?P<id>\d+)'
|
||||
|
||||
_TEST = {
|
||||
'url': 'http://www.4tube.com/videos/209733/hot-babe-holly-michaels-gets-her-ass-stuffed-by-black',
|
||||
'md5': '6516c8ac63b03de06bc8eac14362db4f',
|
||||
'info_dict': {
|
||||
'id': '209733',
|
||||
'ext': 'mp4',
|
||||
'title': 'Hot Babe Holly Michaels gets her ass stuffed by black',
|
||||
'uploader': 'WCP Club',
|
||||
'uploader_id': 'wcp-club',
|
||||
'upload_date': '20131031',
|
||||
'duration': 583,
|
||||
}
|
||||
}
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
|
||||
video_id = mobj.group('id')
|
||||
webpage_url = 'http://www.4tube.com/videos/' + video_id
|
||||
webpage = self._download_webpage(webpage_url, video_id)
|
||||
|
||||
self.report_extraction(video_id)
|
||||
|
||||
playlist_json = self._html_search_regex(r'var playerConfigPlaylist\s+=\s+([^;]+)', webpage, 'Playlist')
|
||||
media_id = self._search_regex(r'idMedia:\s*(\d+)', playlist_json, 'Media Id')
|
||||
sources = self._search_regex(r'sources:\s*\[([^\]]*)\]', playlist_json, 'Sources').split(',')
|
||||
title = self._search_regex(r'title:\s*"([^"]*)', playlist_json, 'Title')
|
||||
thumbnail_url = self._search_regex(r'image:\s*"([^"]*)', playlist_json, 'Thumbnail', fatal=False)
|
||||
|
||||
uploader_str = self._search_regex(r'<span>Uploaded by</span>(.*?)<span>', webpage, 'uploader', fatal=False)
|
||||
mobj = re.search(r'<a href="/sites/(?P<id>[^"]+)"><strong>(?P<name>[^<]+)</strong></a>', uploader_str)
|
||||
(uploader, uploader_id) = (mobj.group('name'), mobj.group('id')) if mobj else (clean_html(uploader_str), None)
|
||||
|
||||
upload_date = None
|
||||
view_count = None
|
||||
duration = None
|
||||
description = self._html_search_meta('description', webpage, 'description')
|
||||
if description:
|
||||
upload_date = self._search_regex(r'Published Date: (\d{2} [a-zA-Z]{3} \d{4})', description, 'upload date',
|
||||
fatal=False)
|
||||
if upload_date:
|
||||
upload_date = unified_strdate(upload_date)
|
||||
view_count = self._search_regex(r'Views: ([\d,\.]+)', description, 'view count', fatal=False)
|
||||
if view_count:
|
||||
view_count = str_to_int(view_count)
|
||||
duration = parse_duration(self._search_regex(r'Length: (\d+m\d+s)', description, 'duration', fatal=False))
|
||||
|
||||
token_url = "http://tkn.4tube.com/{0}/desktop/{1}".format(media_id, "+".join(sources))
|
||||
headers = {
|
||||
b'Content-Type': b'application/x-www-form-urlencoded',
|
||||
b'Origin': b'http://www.4tube.com',
|
||||
}
|
||||
token_req = compat_urllib_request.Request(token_url, b'{}', headers)
|
||||
tokens = self._download_json(token_req, video_id)
|
||||
|
||||
formats = [{
|
||||
'url': tokens[format]['token'],
|
||||
'format_id': format + 'p',
|
||||
'resolution': format + 'p',
|
||||
'quality': int(format),
|
||||
} for format in sources]
|
||||
|
||||
self._sort_formats(formats)
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
'title': title,
|
||||
'formats': formats,
|
||||
'thumbnail': thumbnail_url,
|
||||
'uploader': uploader,
|
||||
'uploader_id': uploader_id,
|
||||
'upload_date': upload_date,
|
||||
'view_count': view_count,
|
||||
'duration': duration,
|
||||
'age_limit': 18,
|
||||
'webpage_url': webpage_url,
|
||||
}
|
@ -184,6 +184,7 @@ class GenerationQuoiIE(InfoExtractor):
|
||||
# It uses Dailymotion
|
||||
'skip_download': True,
|
||||
},
|
||||
'skip': 'Only available from France',
|
||||
}
|
||||
|
||||
def _real_extract(self, url):
|
||||
|
134
youtube_dl/extractor/gdcvault.py
Normal file
134
youtube_dl/extractor/gdcvault.py
Normal file
@ -0,0 +1,134 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import (
|
||||
compat_urllib_parse,
|
||||
compat_urllib_request,
|
||||
)
|
||||
|
||||
class GDCVaultIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://(?:www\.)?gdcvault\.com/play/(?P<id>\d+)/(?P<name>(\w|-)+)'
|
||||
_TESTS = [
|
||||
{
|
||||
'url': 'http://www.gdcvault.com/play/1019721/Doki-Doki-Universe-Sweet-Simple',
|
||||
'md5': '7ce8388f544c88b7ac11c7ab1b593704',
|
||||
'info_dict': {
|
||||
'id': '1019721',
|
||||
'ext': 'mp4',
|
||||
'title': 'Doki-Doki Universe: Sweet, Simple and Genuine (GDC Next 10)'
|
||||
}
|
||||
},
|
||||
{
|
||||
'url': 'http://www.gdcvault.com/play/1015683/Embracing-the-Dark-Art-of',
|
||||
'info_dict': {
|
||||
'id': '1015683',
|
||||
'ext': 'flv',
|
||||
'title': 'Embracing the Dark Art of Mathematical Modeling in AI'
|
||||
},
|
||||
'params': {
|
||||
'skip_download': True, # Requires rtmpdump
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
def _parse_mp4(self, xml_description):
|
||||
video_formats = []
|
||||
mp4_video = xml_description.find('./metadata/mp4video')
|
||||
if mp4_video is None:
|
||||
return None
|
||||
|
||||
mobj = re.match(r'(?P<root>https?://.*?/).*', mp4_video.text)
|
||||
video_root = mobj.group('root')
|
||||
formats = xml_description.findall('./metadata/MBRVideos/MBRVideo')
|
||||
for format in formats:
|
||||
mobj = re.match(r'mp4\:(?P<path>.*)', format.find('streamName').text)
|
||||
url = video_root + mobj.group('path')
|
||||
vbr = format.find('bitrate').text
|
||||
video_formats.append({
|
||||
'url': url,
|
||||
'vbr': int(vbr),
|
||||
})
|
||||
return video_formats
|
||||
|
||||
def _parse_flv(self, xml_description):
|
||||
video_formats = []
|
||||
akami_url = xml_description.find('./metadata/akamaiHost').text
|
||||
slide_video_path = xml_description.find('./metadata/slideVideo').text
|
||||
video_formats.append({
|
||||
'url': 'rtmp://' + akami_url + '/' + slide_video_path,
|
||||
'format_note': 'slide deck video',
|
||||
'quality': -2,
|
||||
'preference': -2,
|
||||
'format_id': 'slides',
|
||||
})
|
||||
speaker_video_path = xml_description.find('./metadata/speakerVideo').text
|
||||
video_formats.append({
|
||||
'url': 'rtmp://' + akami_url + '/' + speaker_video_path,
|
||||
'format_note': 'speaker video',
|
||||
'quality': -1,
|
||||
'preference': -1,
|
||||
'format_id': 'speaker',
|
||||
})
|
||||
return video_formats
|
||||
|
||||
def _login(self, webpage_url, video_id):
|
||||
(username, password) = self._get_login_info()
|
||||
if username is None or password is None:
|
||||
self.report_warning('It looks like ' + webpage_url + ' requires a login. Try specifying a username and password and try again.')
|
||||
return None
|
||||
|
||||
mobj = re.match(r'(?P<root_url>https?://.*?/).*', webpage_url)
|
||||
login_url = mobj.group('root_url') + 'api/login.php'
|
||||
logout_url = mobj.group('root_url') + 'logout'
|
||||
|
||||
login_form = {
|
||||
'email': username,
|
||||
'password': password,
|
||||
}
|
||||
|
||||
request = compat_urllib_request.Request(login_url, compat_urllib_parse.urlencode(login_form))
|
||||
request.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
||||
self._download_webpage(request, video_id, 'Logging in')
|
||||
start_page = self._download_webpage(webpage_url, video_id, 'Getting authenticated video page')
|
||||
self._download_webpage(logout_url, video_id, 'Logging out')
|
||||
|
||||
return start_page
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
|
||||
video_id = mobj.group('id')
|
||||
webpage_url = 'http://www.gdcvault.com/play/' + video_id
|
||||
start_page = self._download_webpage(webpage_url, video_id)
|
||||
|
||||
xml_root = self._html_search_regex(r'<iframe src="(?P<xml_root>.*?)player.html.*?".*?</iframe>', start_page, 'xml root', None, False)
|
||||
|
||||
if xml_root is None:
|
||||
# Probably need to authenticate
|
||||
start_page = self._login(webpage_url, video_id)
|
||||
if start_page is None:
|
||||
self.report_warning('Could not login.')
|
||||
else:
|
||||
# Grab the url from the authenticated page
|
||||
xml_root = self._html_search_regex(r'<iframe src="(?P<xml_root>.*?)player.html.*?".*?</iframe>', start_page, 'xml root')
|
||||
|
||||
xml_name = self._html_search_regex(r'<iframe src=".*?\?xml=(?P<xml_file>.+?\.xml).*?".*?</iframe>', start_page, 'xml filename', None, False)
|
||||
if xml_name is None:
|
||||
# Fallback to the older format
|
||||
xml_name = self._html_search_regex(r'<iframe src=".*?\?xmlURL=xml/(?P<xml_file>.+?\.xml).*?".*?</iframe>', start_page, 'xml filename')
|
||||
|
||||
xml_decription_url = xml_root + 'xml/' + xml_name
|
||||
xml_description = self._download_xml(xml_decription_url, video_id)
|
||||
|
||||
video_title = xml_description.find('./metadata/title').text
|
||||
video_formats = self._parse_mp4(xml_description)
|
||||
if video_formats is None:
|
||||
video_formats = self._parse_flv(xml_description)
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
'title': video_title,
|
||||
'formats': video_formats,
|
||||
}
|
@ -4,6 +4,7 @@ from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import re
|
||||
import xml.etree.ElementTree
|
||||
|
||||
from .common import InfoExtractor
|
||||
from .youtube import YoutubeIE
|
||||
@ -12,6 +13,7 @@ from ..utils import (
|
||||
compat_urllib_parse,
|
||||
compat_urllib_request,
|
||||
compat_urlparse,
|
||||
compat_xml_parse_error,
|
||||
|
||||
ExtractorError,
|
||||
HEADRequest,
|
||||
@ -81,10 +83,10 @@ class GenericIE(InfoExtractor):
|
||||
# Direct link to a video
|
||||
{
|
||||
'url': 'http://media.w3.org/2010/05/sintel/trailer.mp4',
|
||||
'file': 'trailer.mp4',
|
||||
'md5': '67d406c2bcb6af27fa886f31aa934bbe',
|
||||
'info_dict': {
|
||||
'id': 'trailer',
|
||||
'ext': 'mp4',
|
||||
'title': 'trailer',
|
||||
'upload_date': '20100513',
|
||||
}
|
||||
@ -92,7 +94,6 @@ class GenericIE(InfoExtractor):
|
||||
# ooyala video
|
||||
{
|
||||
'url': 'http://www.rollingstone.com/music/videos/norwegian-dj-cashmere-cat-goes-spartan-on-with-me-premiere-20131219',
|
||||
'file': 'BwY2RxaTrTkslxOfcan0UCf0YqyvWysJ.mp4',
|
||||
'md5': '5644c6ca5d5782c1d0d350dad9bd840c',
|
||||
'info_dict': {
|
||||
'id': 'BwY2RxaTrTkslxOfcan0UCf0YqyvWysJ',
|
||||
@ -100,6 +101,22 @@ class GenericIE(InfoExtractor):
|
||||
'title': '2cc213299525360.mov', # that's what we get
|
||||
},
|
||||
},
|
||||
# google redirect
|
||||
{
|
||||
'url': 'http://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&ved=0CCUQtwIwAA&url=http%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DcmQHVoWB5FY&ei=F-sNU-LLCaXk4QT52ICQBQ&usg=AFQjCNEw4hL29zgOohLXvpJ-Bdh2bils1Q&bvm=bv.61965928,d.bGE',
|
||||
'info_dict': {
|
||||
'id': 'cmQHVoWB5FY',
|
||||
'ext': 'mp4',
|
||||
'upload_date': '20130224',
|
||||
'uploader_id': 'TheVerge',
|
||||
'description': 'Chris Ziegler takes a look at the Alcatel OneTouch Fire and the ZTE Open; two of the first Firefox OS handsets to be officially announced.',
|
||||
'uploader': 'The Verge',
|
||||
'title': 'First Firefox OS phones side-by-side',
|
||||
},
|
||||
'params': {
|
||||
'skip_download': False,
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
def report_download_webpage(self, video_id):
|
||||
@ -159,6 +176,25 @@ class GenericIE(InfoExtractor):
|
||||
raise ExtractorError('Invalid URL protocol')
|
||||
return response
|
||||
|
||||
def _extract_rss(self, url, video_id, doc):
|
||||
playlist_title = doc.find('./channel/title').text
|
||||
playlist_desc_el = doc.find('./channel/description')
|
||||
playlist_desc = None if playlist_desc_el is None else playlist_desc_el.text
|
||||
|
||||
entries = [{
|
||||
'_type': 'url',
|
||||
'url': e.find('link').text,
|
||||
'title': e.find('title').text,
|
||||
} for e in doc.findall('./channel/item')]
|
||||
|
||||
return {
|
||||
'_type': 'playlist',
|
||||
'id': url,
|
||||
'title': playlist_title,
|
||||
'description': playlist_desc,
|
||||
'entries': entries,
|
||||
}
|
||||
|
||||
def _real_extract(self, url):
|
||||
parsed_url = compat_urlparse.urlparse(url)
|
||||
if not parsed_url.scheme:
|
||||
@ -219,6 +255,14 @@ class GenericIE(InfoExtractor):
|
||||
|
||||
self.report_extraction(video_id)
|
||||
|
||||
# Is it an RSS feed?
|
||||
try:
|
||||
doc = xml.etree.ElementTree.fromstring(webpage.encode('utf-8'))
|
||||
if doc.tag == 'rss':
|
||||
return self._extract_rss(url, video_id, doc)
|
||||
except compat_xml_parse_error:
|
||||
pass
|
||||
|
||||
# it's tempting to parse this further, but you would
|
||||
# have to take into account all the variations like
|
||||
# Video Title - Site Name
|
||||
@ -334,11 +378,17 @@ class GenericIE(InfoExtractor):
|
||||
if mobj is not None:
|
||||
return self.url_result(mobj.group(1), 'Mpora')
|
||||
|
||||
# Look for embedded Novamov player
|
||||
# Look for embedded NovaMov player
|
||||
mobj = re.search(
|
||||
r'<iframe[^>]+?src=(["\'])(?P<url>http://(?:(?:embed|www)\.)?novamov\.com/embed\.php.+?)\1', webpage)
|
||||
if mobj is not None:
|
||||
return self.url_result(mobj.group('url'), 'Novamov')
|
||||
return self.url_result(mobj.group('url'), 'NovaMov')
|
||||
|
||||
# Look for embedded NowVideo player
|
||||
mobj = re.search(
|
||||
r'<iframe[^>]+?src=(["\'])(?P<url>http://(?:(?:embed|www)\.)?nowvideo\.(?:ch|sx|eu)/embed\.php.+?)\1', webpage)
|
||||
if mobj is not None:
|
||||
return self.url_result(mobj.group('url'), 'NowVideo')
|
||||
|
||||
# Look for embedded Facebook player
|
||||
mobj = re.search(
|
||||
@ -346,6 +396,11 @@ class GenericIE(InfoExtractor):
|
||||
if mobj is not None:
|
||||
return self.url_result(mobj.group('url'), 'Facebook')
|
||||
|
||||
# Look for embedded VK player
|
||||
mobj = re.search(r'<iframe[^>]+?src=(["\'])(?P<url>https?://vk\.com/video_ext\.php.+?)\1', webpage)
|
||||
if mobj is not None:
|
||||
return self.url_result(mobj.group('url'), 'VK')
|
||||
|
||||
# Look for embedded Huffington Post player
|
||||
mobj = re.search(
|
||||
r'<iframe[^>]+?src=(["\'])(?P<url>https?://embed\.live\.huffingtonpost\.com/.+?)\1', webpage)
|
||||
@ -376,6 +431,18 @@ class GenericIE(InfoExtractor):
|
||||
if mobj is None:
|
||||
# HTML5 video
|
||||
mobj = re.search(r'<video[^<]*(?:>.*?<source.*?)? src="([^"]+)"', webpage, flags=re.DOTALL)
|
||||
if mobj is None:
|
||||
mobj = re.search(
|
||||
r'(?i)<meta\s+(?=(?:[a-z-]+="[^"]+"\s+)*http-equiv="refresh")'
|
||||
r'(?:[a-z-]+="[^"]+"\s+)*?content="[0-9]{,2};url=\'([^\']+)\'"',
|
||||
webpage)
|
||||
if mobj:
|
||||
new_url = mobj.group(1)
|
||||
self.report_following_redirect(new_url)
|
||||
return {
|
||||
'_type': 'url',
|
||||
'url': new_url,
|
||||
}
|
||||
if mobj is None:
|
||||
raise ExtractorError('Unsupported URL: %s' % url)
|
||||
|
||||
|
62
youtube_dl/extractor/helsinki.py
Normal file
62
youtube_dl/extractor/helsinki.py
Normal file
@ -0,0 +1,62 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
|
||||
|
||||
class HelsinkiIE(InfoExtractor):
|
||||
IE_DESC = 'helsinki.fi'
|
||||
_VALID_URL = r'https?://video\.helsinki\.fi/Arkisto/flash\.php\?id=(?P<id>\d+)'
|
||||
_TEST = {
|
||||
'url': 'http://video.helsinki.fi/Arkisto/flash.php?id=20258',
|
||||
'info_dict': {
|
||||
'id': '20258',
|
||||
'ext': 'mp4',
|
||||
'title': 'Tietotekniikkafoorumi-iltapäivä',
|
||||
'description': 'md5:f5c904224d43c133225130fe156a5ee0',
|
||||
},
|
||||
'params': {
|
||||
'skip_download': True, # RTMP
|
||||
}
|
||||
}
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
video_id = mobj.group('id')
|
||||
webpage = self._download_webpage(url, video_id)
|
||||
formats = []
|
||||
|
||||
mobj = re.search(r'file=((\w+):[^&]+)', webpage)
|
||||
if mobj:
|
||||
formats.append({
|
||||
'ext': mobj.group(2),
|
||||
'play_path': mobj.group(1),
|
||||
'url': 'rtmp://flashvideo.it.helsinki.fi/vod/',
|
||||
'player_url': 'http://video.helsinki.fi/player.swf',
|
||||
'format_note': 'sd',
|
||||
'quality': 0,
|
||||
})
|
||||
|
||||
mobj = re.search(r'hd\.file=((\w+):[^&]+)', webpage)
|
||||
if mobj:
|
||||
formats.append({
|
||||
'ext': mobj.group(2),
|
||||
'play_path': mobj.group(1),
|
||||
'url': 'rtmp://flashvideo.it.helsinki.fi/vod/',
|
||||
'player_url': 'http://video.helsinki.fi/player.swf',
|
||||
'format_note': 'hd',
|
||||
'quality': 1,
|
||||
})
|
||||
|
||||
self._sort_formats(formats)
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
'title': self._og_search_title(webpage).replace('Video: ', ''),
|
||||
'description': self._og_search_description(webpage),
|
||||
'thumbnail': self._og_search_thumbnail(webpage),
|
||||
'formats': formats,
|
||||
}
|
@ -10,7 +10,7 @@ from ..utils import compat_urllib_request
|
||||
|
||||
|
||||
class IPrimaIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://play\.iprima\.cz/(?P<videogroup>.+)/(?P<videoid>.+)'
|
||||
_VALID_URL = r'https?://play\.iprima\.cz/[^?#]+/(?P<id>[^?#]+)'
|
||||
|
||||
_TESTS = [{
|
||||
'url': 'http://play.iprima.cz/particka/particka-92',
|
||||
@ -22,20 +22,32 @@ class IPrimaIE(InfoExtractor):
|
||||
'thumbnail': 'http://play.iprima.cz/sites/default/files/image_crops/image_620x349/3/491483_particka-92_image_620x349.jpg',
|
||||
},
|
||||
'params': {
|
||||
'skip_download': True,
|
||||
'skip_download': True, # requires rtmpdump
|
||||
},
|
||||
},
|
||||
]
|
||||
}, {
|
||||
'url': 'http://play.iprima.cz/particka/tchibo-particka-jarni-moda',
|
||||
'info_dict': {
|
||||
'id': '9718337',
|
||||
'ext': 'flv',
|
||||
'title': 'Tchibo Partička - Jarní móda',
|
||||
'description': 'md5:589f8f59f414220621ff8882eb3ce7be',
|
||||
'thumbnail': 're:^http:.*\.jpg$',
|
||||
},
|
||||
'params': {
|
||||
'skip_download': True, # requires rtmpdump
|
||||
},
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
video_id = mobj.group('videoid')
|
||||
video_id = mobj.group('id')
|
||||
|
||||
webpage = self._download_webpage(url, video_id)
|
||||
|
||||
player_url = 'http://embed.livebox.cz/iprimaplay/player-embed-v2.js?__tok%s__=%s' % (
|
||||
floor(random()*1073741824),
|
||||
floor(random()*1073741824))
|
||||
player_url = (
|
||||
'http://embed.livebox.cz/iprimaplay/player-embed-v2.js?__tok%s__=%s' %
|
||||
(floor(random()*1073741824), floor(random()*1073741824))
|
||||
)
|
||||
|
||||
req = compat_urllib_request.Request(player_url)
|
||||
req.add_header('Referer', url)
|
||||
@ -44,18 +56,20 @@ class IPrimaIE(InfoExtractor):
|
||||
base_url = ''.join(re.findall(r"embed\['stream'\] = '(.+?)'.+'(\?auth=)'.+'(.+?)';", playerpage)[1])
|
||||
|
||||
zoneGEO = self._html_search_regex(r'"zoneGEO":(.+?),', webpage, 'zoneGEO')
|
||||
|
||||
if zoneGEO != '0':
|
||||
base_url = base_url.replace('token', 'token_'+zoneGEO)
|
||||
base_url = base_url.replace('token', 'token_' + zoneGEO)
|
||||
|
||||
formats = []
|
||||
for format_id in ['lq', 'hq', 'hd']:
|
||||
filename = self._html_search_regex(r'"%s_id":(.+?),' % format_id, webpage, 'filename')
|
||||
filename = self._html_search_regex(
|
||||
r'"%s_id":(.+?),' % format_id, webpage, 'filename')
|
||||
|
||||
if filename == 'null':
|
||||
continue
|
||||
|
||||
real_id = self._search_regex(r'Prima-[0-9]{10}-([0-9]+)_', filename, 'real video id')
|
||||
real_id = self._search_regex(
|
||||
r'Prima-(?:[0-9]{10}|WEB)-([0-9]+)[-_]',
|
||||
filename, 'real video id')
|
||||
|
||||
if format_id == 'lq':
|
||||
quality = 0
|
||||
@ -63,13 +77,13 @@ class IPrimaIE(InfoExtractor):
|
||||
quality = 1
|
||||
elif format_id == 'hd':
|
||||
quality = 2
|
||||
filename = 'hq/'+filename
|
||||
filename = 'hq/' + filename
|
||||
|
||||
formats.append({
|
||||
'format_id': format_id,
|
||||
'url': base_url,
|
||||
'quality': quality,
|
||||
'play_path': 'mp4:'+filename.replace('"', '')[:-4],
|
||||
'play_path': 'mp4:' + filename.replace('"', '')[:-4],
|
||||
'rtmp_live': True,
|
||||
'ext': 'flv',
|
||||
})
|
||||
|
@ -6,7 +6,8 @@ import re
|
||||
from .common import InfoExtractor
|
||||
from ..utils import (
|
||||
int_or_none,
|
||||
unified_strdate
|
||||
unified_strdate,
|
||||
ExtractorError,
|
||||
)
|
||||
|
||||
|
||||
@ -32,13 +33,11 @@ class LifeNewsIE(InfoExtractor):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
video_id = mobj.group('id')
|
||||
|
||||
webpage = self._download_webpage('http://lifenews.ru/mobile/news/%s' % video_id, video_id, 'Downloading page')
|
||||
webpage = self._download_webpage('http://lifenews.ru/news/%s' % video_id, video_id, 'Downloading page')
|
||||
|
||||
video_url = self._html_search_regex(
|
||||
r'<video.*?src="([^"]+)".*?></video>', webpage, 'video URL')
|
||||
|
||||
thumbnail = self._html_search_regex(
|
||||
r'<video.*?poster="([^"]+)".*?"></video>', webpage, 'video thumbnail')
|
||||
videos = re.findall(r'<video.*?poster="(?P<poster>[^"]+)".*?src="(?P<video>[^"]+)".*?></video>', webpage)
|
||||
if not videos:
|
||||
raise ExtractorError('No media links available for %s' % video_id)
|
||||
|
||||
title = self._og_search_title(webpage)
|
||||
TITLE_SUFFIX = ' - Первый по срочным новостям — LIFE | NEWS'
|
||||
@ -50,20 +49,26 @@ class LifeNewsIE(InfoExtractor):
|
||||
view_count = self._html_search_regex(
|
||||
r'<div class=\'views\'>(\d+)</div>', webpage, 'view count', fatal=False)
|
||||
comment_count = self._html_search_regex(
|
||||
r'<div class=\'comments\'>(\d+)</div>', webpage, 'comment count', fatal=False)
|
||||
r'<div class=\'comments\'>\s*<span class=\'counter\'>(\d+)</span>', webpage, 'comment count', fatal=False)
|
||||
|
||||
upload_date = self._html_search_regex(
|
||||
r'<time datetime=\'([^\']+)\'>', webpage, 'upload date',fatal=False)
|
||||
if upload_date is not None:
|
||||
upload_date = unified_strdate(upload_date)
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
'url': video_url,
|
||||
'thumbnail': thumbnail,
|
||||
'title': title,
|
||||
'description': description,
|
||||
'view_count': int_or_none(view_count),
|
||||
'comment_count': int_or_none(comment_count),
|
||||
'upload_date': upload_date,
|
||||
}
|
||||
def make_entry(video_id, media, video_number=None):
|
||||
return {
|
||||
'id': video_id,
|
||||
'url': media[1],
|
||||
'thumbnail': media[0],
|
||||
'title': title if video_number is None else '%s-video%s' % (title, video_number),
|
||||
'description': description,
|
||||
'view_count': int_or_none(view_count),
|
||||
'comment_count': int_or_none(comment_count),
|
||||
'upload_date': upload_date,
|
||||
}
|
||||
|
||||
if len(videos) == 1:
|
||||
return make_entry(video_id, videos[0])
|
||||
else:
|
||||
return [make_entry(video_id, media, video_number+1) for video_number, media in enumerate(videos)]
|
@ -4,15 +4,17 @@ import json
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import int_or_none
|
||||
|
||||
|
||||
class LiveLeakIE(InfoExtractor):
|
||||
_VALID_URL = r'^(?:http://)?(?:\w+\.)?liveleak\.com/view\?(?:.*?)i=(?P<video_id>[\w_]+)(?:.*)'
|
||||
_TESTS = [{
|
||||
'url': 'http://www.liveleak.com/view?i=757_1364311680',
|
||||
'file': '757_1364311680.mp4',
|
||||
'md5': '0813c2430bea7a46bf13acf3406992f4',
|
||||
'info_dict': {
|
||||
'id': '757_1364311680',
|
||||
'ext': 'mp4',
|
||||
'description': 'extremely bad day for this guy..!',
|
||||
'uploader': 'ljfriel2',
|
||||
'title': 'Most unlucky car accident'
|
||||
@ -20,25 +22,62 @@ class LiveLeakIE(InfoExtractor):
|
||||
},
|
||||
{
|
||||
'url': 'http://www.liveleak.com/view?i=f93_1390833151',
|
||||
'file': 'f93_1390833151.mp4',
|
||||
'md5': 'd3f1367d14cc3c15bf24fbfbe04b9abf',
|
||||
'info_dict': {
|
||||
'id': 'f93_1390833151',
|
||||
'ext': 'mp4',
|
||||
'description': 'German Television Channel NDR does an exclusive interview with Edward Snowden.\r\nUploaded on LiveLeak cause German Television thinks the rest of the world isn\'t intereseted in Edward Snowden.',
|
||||
'uploader': 'ARD_Stinkt',
|
||||
'title': 'German Television does first Edward Snowden Interview (ENGLISH)',
|
||||
}
|
||||
},
|
||||
{
|
||||
'url': 'http://www.liveleak.com/view?i=4f7_1392687779',
|
||||
'md5': '42c6d97d54f1db107958760788c5f48f',
|
||||
'info_dict': {
|
||||
'id': '4f7_1392687779',
|
||||
'ext': 'mp4',
|
||||
'description': "The guy with the cigarette seems amazingly nonchalant about the whole thing... I really hope my friends' reactions would be a bit stronger.\r\n\r\nAction-go to 0:55.",
|
||||
'uploader': 'CapObveus',
|
||||
'title': 'Man is Fatally Struck by Reckless Car While Packing up a Moving Truck',
|
||||
'age_limit': 18,
|
||||
}
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
|
||||
video_id = mobj.group('video_id')
|
||||
webpage = self._download_webpage(url, video_id)
|
||||
|
||||
video_title = self._og_search_title(webpage).replace('LiveLeak.com -', '').strip()
|
||||
video_description = self._og_search_description(webpage)
|
||||
video_uploader = self._html_search_regex(
|
||||
r'By:.*?(\w+)</a>', webpage, 'uploader', fatal=False)
|
||||
age_limit = int_or_none(self._search_regex(
|
||||
r'you confirm that you are ([0-9]+) years and over.',
|
||||
webpage, 'age limit', default=None))
|
||||
|
||||
sources_raw = self._search_regex(
|
||||
r'(?s)sources:\s*(\[.*?\]),', webpage, 'video URLs', default=None)
|
||||
if sources_raw is None:
|
||||
sources_raw = '[{ %s}]' % (
|
||||
self._search_regex(r'(file: ".*?"),', webpage, 'video URL'))
|
||||
alt_source = self._search_regex(
|
||||
r'(file: ".*?"),', webpage, 'video URL', default=None)
|
||||
if alt_source:
|
||||
sources_raw = '[{ %s}]' % alt_source
|
||||
else:
|
||||
# Maybe an embed?
|
||||
embed_url = self._search_regex(
|
||||
r'<iframe[^>]+src="(http://www.prochan.com/embed\?[^"]+)"',
|
||||
webpage, 'embed URL')
|
||||
return {
|
||||
'_type': 'url_transparent',
|
||||
'url': embed_url,
|
||||
'id': video_id,
|
||||
'title': video_title,
|
||||
'description': video_description,
|
||||
'uploader': video_uploader,
|
||||
'age_limit': age_limit,
|
||||
}
|
||||
|
||||
sources_json = re.sub(r'\s([a-z]+):\s', r'"\1": ', sources_raw)
|
||||
sources = json.loads(sources_json)
|
||||
@ -49,15 +88,11 @@ class LiveLeakIE(InfoExtractor):
|
||||
} for s in sources]
|
||||
self._sort_formats(formats)
|
||||
|
||||
video_title = self._og_search_title(webpage).replace('LiveLeak.com -', '').strip()
|
||||
video_description = self._og_search_description(webpage)
|
||||
video_uploader = self._html_search_regex(
|
||||
r'By:.*?(\w+)</a>', webpage, 'uploader', fatal=False)
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
'title': video_title,
|
||||
'description': video_description,
|
||||
'uploader': video_uploader,
|
||||
'formats': formats,
|
||||
'age_limit': age_limit,
|
||||
}
|
||||
|
66
youtube_dl/extractor/mailru.py
Normal file
66
youtube_dl/extractor/mailru.py
Normal file
@ -0,0 +1,66 @@
|
||||
# encoding: utf-8
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
import datetime
|
||||
|
||||
from .common import InfoExtractor
|
||||
|
||||
|
||||
class MailRuIE(InfoExtractor):
|
||||
IE_NAME = 'mailru'
|
||||
IE_DESC = 'Видео@Mail.Ru'
|
||||
_VALID_URL = r'http://(?:www\.)?my\.mail\.ru/video/.*#video=/?(?P<id>[^/]+/[^/]+/[^/]+/\d+)'
|
||||
|
||||
_TEST = {
|
||||
'url': 'http://my.mail.ru/video/top#video=/mail/sonypicturesrus/75/76',
|
||||
'md5': 'dea205f03120046894db4ebb6159879a',
|
||||
'info_dict': {
|
||||
'id': '46301138',
|
||||
'ext': 'mp4',
|
||||
'title': 'Новый Человек-Паук. Высокое напряжение. Восстание Электро',
|
||||
'upload_date': '20140224',
|
||||
'uploader': 'sonypicturesrus',
|
||||
'uploader_id': 'sonypicturesrus@mail.ru',
|
||||
'duration': 184,
|
||||
}
|
||||
}
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
video_id = mobj.group('id')
|
||||
|
||||
video_data = self._download_json(
|
||||
'http://videoapi.my.mail.ru/videos/%s.json?new=1' % video_id, video_id, 'Downloading video JSON')
|
||||
|
||||
author = video_data['author']
|
||||
uploader = author['name']
|
||||
uploader_id = author['id']
|
||||
|
||||
movie = video_data['movie']
|
||||
content_id = str(movie['contentId'])
|
||||
title = movie['title']
|
||||
thumbnail = movie['poster']
|
||||
duration = movie['duration']
|
||||
|
||||
upload_date = datetime.datetime.fromtimestamp(video_data['timestamp']).strftime('%Y%m%d')
|
||||
view_count = video_data['views_count']
|
||||
|
||||
formats = [
|
||||
{
|
||||
'url': video['url'],
|
||||
'format_id': video['name'],
|
||||
} for video in video_data['videos']
|
||||
]
|
||||
|
||||
return {
|
||||
'id': content_id,
|
||||
'title': title,
|
||||
'thumbnail': thumbnail,
|
||||
'upload_date': upload_date,
|
||||
'uploader': uploader,
|
||||
'uploader_id': uploader_id,
|
||||
'duration': duration,
|
||||
'view_count': view_count,
|
||||
'formats': formats,
|
||||
}
|
@ -166,6 +166,7 @@ class MetacafeIE(InfoExtractor):
|
||||
|
||||
video_title = self._html_search_regex(r'(?im)<title>(.*) - Video</title>', webpage, u'title')
|
||||
description = self._og_search_description(webpage)
|
||||
thumbnail = self._og_search_thumbnail(webpage)
|
||||
video_uploader = self._html_search_regex(
|
||||
r'submitter=(.*?);|googletag\.pubads\(\)\.setTargeting\("(?:channel|submiter)","([^"]+)"\);',
|
||||
webpage, u'uploader nickname', fatal=False)
|
||||
@ -183,6 +184,7 @@ class MetacafeIE(InfoExtractor):
|
||||
'uploader': video_uploader,
|
||||
'upload_date': None,
|
||||
'title': video_title,
|
||||
'thumbnail':thumbnail,
|
||||
'ext': video_ext,
|
||||
'age_limit': age_limit,
|
||||
}
|
||||
|
@ -1,24 +1,30 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
import json
|
||||
|
||||
from .common import InfoExtractor
|
||||
from .youtube import YoutubeIE
|
||||
from ..utils import (
|
||||
compat_urlparse,
|
||||
clean_html,
|
||||
ExtractorError,
|
||||
get_element_by_id,
|
||||
)
|
||||
|
||||
|
||||
class TechTVMITIE(InfoExtractor):
|
||||
IE_NAME = u'techtv.mit.edu'
|
||||
IE_NAME = 'techtv.mit.edu'
|
||||
_VALID_URL = r'https?://techtv\.mit\.edu/(videos|embeds)/(?P<id>\d+)'
|
||||
|
||||
_TEST = {
|
||||
u'url': u'http://techtv.mit.edu/videos/25418-mit-dna-learning-center-set',
|
||||
u'file': u'25418.mp4',
|
||||
u'md5': u'1f8cb3e170d41fd74add04d3c9330e5f',
|
||||
u'info_dict': {
|
||||
u'title': u'MIT DNA Learning Center Set',
|
||||
u'description': u'md5:82313335e8a8a3f243351ba55bc1b474',
|
||||
'url': 'http://techtv.mit.edu/videos/25418-mit-dna-learning-center-set',
|
||||
'md5': '1f8cb3e170d41fd74add04d3c9330e5f',
|
||||
'info_dict': {
|
||||
'id': '25418',
|
||||
'ext': 'mp4',
|
||||
'title': 'MIT DNA Learning Center Set',
|
||||
'description': 'md5:82313335e8a8a3f243351ba55bc1b474',
|
||||
},
|
||||
}
|
||||
|
||||
@ -27,12 +33,12 @@ class TechTVMITIE(InfoExtractor):
|
||||
video_id = mobj.group('id')
|
||||
raw_page = self._download_webpage(
|
||||
'http://techtv.mit.edu/videos/%s' % video_id, video_id)
|
||||
clean_page = re.compile(u'<!--.*?-->', re.S).sub(u'', raw_page)
|
||||
clean_page = re.compile(r'<!--.*?-->', re.S).sub('', raw_page)
|
||||
|
||||
base_url = self._search_regex(r'ipadUrl: \'(.+?cloudfront.net/)',
|
||||
raw_page, u'base url')
|
||||
formats_json = self._search_regex(r'bitrates: (\[.+?\])', raw_page,
|
||||
u'video formats')
|
||||
base_url = self._search_regex(
|
||||
r'ipadUrl: \'(.+?cloudfront.net/)', raw_page, 'base url')
|
||||
formats_json = self._search_regex(
|
||||
r'bitrates: (\[.+?\])', raw_page, 'video formats')
|
||||
formats_mit = json.loads(formats_json)
|
||||
formats = [
|
||||
{
|
||||
@ -48,28 +54,31 @@ class TechTVMITIE(InfoExtractor):
|
||||
|
||||
title = get_element_by_id('edit-title', clean_page)
|
||||
description = clean_html(get_element_by_id('edit-description', clean_page))
|
||||
thumbnail = self._search_regex(r'playlist:.*?url: \'(.+?)\'',
|
||||
raw_page, u'thumbnail', flags=re.DOTALL)
|
||||
thumbnail = self._search_regex(
|
||||
r'playlist:.*?url: \'(.+?)\'',
|
||||
raw_page, 'thumbnail', flags=re.DOTALL)
|
||||
|
||||
return {'id': video_id,
|
||||
'title': title,
|
||||
'formats': formats,
|
||||
'description': description,
|
||||
'thumbnail': thumbnail,
|
||||
}
|
||||
return {
|
||||
'id': video_id,
|
||||
'title': title,
|
||||
'formats': formats,
|
||||
'description': description,
|
||||
'thumbnail': thumbnail,
|
||||
}
|
||||
|
||||
|
||||
class MITIE(TechTVMITIE):
|
||||
IE_NAME = u'video.mit.edu'
|
||||
IE_NAME = 'video.mit.edu'
|
||||
_VALID_URL = r'https?://video\.mit\.edu/watch/(?P<title>[^/]+)'
|
||||
|
||||
_TEST = {
|
||||
u'url': u'http://video.mit.edu/watch/the-government-is-profiling-you-13222/',
|
||||
u'file': u'21783.mp4',
|
||||
u'md5': u'7db01d5ccc1895fc5010e9c9e13648da',
|
||||
u'info_dict': {
|
||||
u'title': u'The Government is Profiling You',
|
||||
u'description': u'md5:ad5795fe1e1623b73620dbfd47df9afd',
|
||||
'url': 'http://video.mit.edu/watch/the-government-is-profiling-you-13222/',
|
||||
'md5': '7db01d5ccc1895fc5010e9c9e13648da',
|
||||
'info_dict': {
|
||||
'id': '21783',
|
||||
'ext': 'mp4',
|
||||
'title': 'The Government is Profiling You',
|
||||
'description': 'md5:ad5795fe1e1623b73620dbfd47df9afd',
|
||||
},
|
||||
}
|
||||
|
||||
@ -77,7 +86,73 @@ class MITIE(TechTVMITIE):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
page_title = mobj.group('title')
|
||||
webpage = self._download_webpage(url, page_title)
|
||||
self.to_screen('%s: Extracting %s url' % (page_title, TechTVMITIE.IE_NAME))
|
||||
embed_url = self._search_regex(r'<iframe .*?src="(.+?)"', webpage,
|
||||
u'embed url')
|
||||
embed_url = self._search_regex(
|
||||
r'<iframe .*?src="(.+?)"', webpage, 'embed url')
|
||||
return self.url_result(embed_url, ie='TechTVMIT')
|
||||
|
||||
|
||||
class OCWMITIE(InfoExtractor):
|
||||
IE_NAME = 'ocw.mit.edu'
|
||||
_VALID_URL = r'^http://ocw\.mit\.edu/courses/(?P<topic>[a-z0-9\-]+)'
|
||||
_BASE_URL = 'http://ocw.mit.edu/'
|
||||
|
||||
_TESTS = [
|
||||
{
|
||||
'url': 'http://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-041-probabilistic-systems-analysis-and-applied-probability-fall-2010/video-lectures/lecture-7-multiple-variables-expectations-independence/',
|
||||
'info_dict': {
|
||||
'id': 'EObHWIEKGjA',
|
||||
'ext': 'mp4',
|
||||
'title': 'Lecture 7: Multiple Discrete Random Variables: Expectations, Conditioning, Independence',
|
||||
'description': 'In this lecture, the professor discussed multiple random variables, expectations, and binomial distribution.',
|
||||
#'subtitles': 'http://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-041-probabilistic-systems-analysis-and-applied-probability-fall-2010/video-lectures/lecture-7-multiple-variables-expectations-independence/MIT6_041F11_lec07_300k.mp4.srt'
|
||||
}
|
||||
},
|
||||
{
|
||||
'url': 'http://ocw.mit.edu/courses/mathematics/18-01sc-single-variable-calculus-fall-2010/1.-differentiation/part-a-definition-and-basic-rules/session-1-introduction-to-derivatives/',
|
||||
'info_dict': {
|
||||
'id': '7K1sB05pE0A',
|
||||
'ext': 'mp4',
|
||||
'title': 'Session 1: Introduction to Derivatives',
|
||||
'description': 'This section contains lecture video excerpts, lecture notes, an interactive mathlet with supporting documents, and problem solving videos.',
|
||||
#'subtitles': 'http://ocw.mit.edu//courses/mathematics/18-01sc-single-variable-calculus-fall-2010/ocw-18.01-f07-lec01_300k.SRT'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
topic = mobj.group('topic')
|
||||
|
||||
webpage = self._download_webpage(url, topic)
|
||||
title = self._html_search_meta('WT.cg_s', webpage)
|
||||
description = self._html_search_meta('Description', webpage)
|
||||
|
||||
# search for call to ocw_embed_chapter_media(container_id, media_url, provider, page_url, image_url, start, stop, captions_file)
|
||||
embed_chapter_media = re.search(r'ocw_embed_chapter_media\((.+?)\)', webpage)
|
||||
if embed_chapter_media:
|
||||
metadata = re.sub(r'[\'"]', '', embed_chapter_media.group(1))
|
||||
metadata = re.split(r', ?', metadata)
|
||||
yt = metadata[1]
|
||||
subs = compat_urlparse.urljoin(self._BASE_URL, metadata[7])
|
||||
else:
|
||||
# search for call to ocw_embed_chapter_media(container_id, media_url, provider, page_url, image_url, captions_file)
|
||||
embed_media = re.search(r'ocw_embed_media\((.+?)\)', webpage)
|
||||
if embed_media:
|
||||
metadata = re.sub(r'[\'"]', '', embed_media.group(1))
|
||||
metadata = re.split(r', ?', metadata)
|
||||
yt = metadata[1]
|
||||
subs = compat_urlparse.urljoin(self._BASE_URL, metadata[5])
|
||||
else:
|
||||
raise ExtractorError('Unable to find embedded YouTube video.')
|
||||
video_id = YoutubeIE.extract_id(yt)
|
||||
|
||||
return {
|
||||
'_type': 'url_transparent',
|
||||
'id': video_id,
|
||||
'title': title,
|
||||
'description': description,
|
||||
'url': yt,
|
||||
'url_transparent'
|
||||
'subtitles': subs,
|
||||
'ie_key': 'Youtube',
|
||||
}
|
||||
|
@ -5,18 +5,20 @@ import re
|
||||
from .common import InfoExtractor
|
||||
from ..utils import (
|
||||
unified_strdate,
|
||||
compat_urllib_parse,
|
||||
ExtractorError,
|
||||
)
|
||||
|
||||
|
||||
class MixcloudIE(InfoExtractor):
|
||||
_VALID_URL = r'^(?:https?://)?(?:www\.)?mixcloud\.com/([\w\d-]+)/([\w\d-]+)'
|
||||
_VALID_URL = r'^(?:https?://)?(?:www\.)?mixcloud\.com/([^/]+)/([^/]+)'
|
||||
IE_NAME = 'mixcloud'
|
||||
|
||||
_TEST = {
|
||||
'url': 'http://www.mixcloud.com/dholbach/cryptkeeper/',
|
||||
'file': 'dholbach-cryptkeeper.mp3',
|
||||
'info_dict': {
|
||||
'id': 'dholbach-cryptkeeper',
|
||||
'ext': 'mp3',
|
||||
'title': 'Cryptkeeper',
|
||||
'description': 'After quite a long silence from myself, finally another Drum\'n\'Bass mix with my favourite current dance floor bangers.',
|
||||
'uploader': 'Daniel Holbach',
|
||||
@ -45,7 +47,7 @@ class MixcloudIE(InfoExtractor):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
uploader = mobj.group(1)
|
||||
cloudcast_name = mobj.group(2)
|
||||
track_id = '-'.join((uploader, cloudcast_name))
|
||||
track_id = compat_urllib_parse.unquote('-'.join((uploader, cloudcast_name)))
|
||||
|
||||
webpage = self._download_webpage(url, track_id)
|
||||
|
||||
|
@ -1,19 +1,46 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import find_xpath_attr, compat_str
|
||||
|
||||
|
||||
class NBCIE(InfoExtractor):
|
||||
_VALID_URL = r'http://www\.nbc\.com/[^/]+/video/[^/]+/(?P<id>n?\d+)'
|
||||
|
||||
_TEST = {
|
||||
'url': 'http://www.nbc.com/chicago-fire/video/i-am-a-firefighter/2734188',
|
||||
'md5': '54d0fbc33e0b853a65d7b4de5c06d64e',
|
||||
'info_dict': {
|
||||
'id': 'u1RInQZRN7QJ',
|
||||
'ext': 'flv',
|
||||
'title': 'I Am a Firefighter',
|
||||
'description': 'An emergency puts Dawson\'sf irefighter skills to the ultimate test in this four-part digital series.',
|
||||
},
|
||||
}
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
video_id = mobj.group('id')
|
||||
webpage = self._download_webpage(url, video_id)
|
||||
theplatform_url = self._search_regex('class="video-player video-player-full" data-mpx-url="(.*?)"', webpage, 'theplatform url')
|
||||
if theplatform_url.startswith('//'):
|
||||
theplatform_url = 'http:' + theplatform_url
|
||||
return self.url_result(theplatform_url)
|
||||
|
||||
|
||||
class NBCNewsIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://www\.nbcnews\.com/video/.+?/(?P<id>\d+)'
|
||||
|
||||
_TEST = {
|
||||
u'url': u'http://www.nbcnews.com/video/nbc-news/52753292',
|
||||
u'file': u'52753292.flv',
|
||||
u'md5': u'47abaac93c6eaf9ad37ee6c4463a5179',
|
||||
u'info_dict': {
|
||||
u'title': u'Crew emerges after four-month Mars food study',
|
||||
u'description': u'md5:24e632ffac72b35f8b67a12d1b6ddfc1',
|
||||
'url': 'http://www.nbcnews.com/video/nbc-news/52753292',
|
||||
'md5': '47abaac93c6eaf9ad37ee6c4463a5179',
|
||||
'info_dict': {
|
||||
'id': '52753292',
|
||||
'ext': 'flv',
|
||||
'title': 'Crew emerges after four-month Mars food study',
|
||||
'description': 'md5:24e632ffac72b35f8b67a12d1b6ddfc1',
|
||||
},
|
||||
}
|
||||
|
||||
@ -23,10 +50,11 @@ class NBCNewsIE(InfoExtractor):
|
||||
all_info = self._download_xml('http://www.nbcnews.com/id/%s/displaymode/1219' % video_id, video_id)
|
||||
info = all_info.find('video')
|
||||
|
||||
return {'id': video_id,
|
||||
'title': info.find('headline').text,
|
||||
'ext': 'flv',
|
||||
'url': find_xpath_attr(info, 'media', 'type', 'flashVideo').text,
|
||||
'description': compat_str(info.find('caption').text),
|
||||
'thumbnail': find_xpath_attr(info, 'media', 'type', 'thumbnail').text,
|
||||
}
|
||||
return {
|
||||
'id': video_id,
|
||||
'title': info.find('headline').text,
|
||||
'ext': 'flv',
|
||||
'url': find_xpath_attr(info, 'media', 'type', 'flashVideo').text,
|
||||
'description': compat_str(info.find('caption').text),
|
||||
'thumbnail': find_xpath_attr(info, 'media', 'type', 'thumbnail').text,
|
||||
}
|
||||
|
@ -26,15 +26,15 @@ class NDRIE(InfoExtractor):
|
||||
},
|
||||
},
|
||||
{
|
||||
'url': 'http://www.ndr.de/903/audio191719.html',
|
||||
'md5': '41ed601768534dd18a9ae34d84798129',
|
||||
'url': 'http://www.ndr.de/info/audio51535.html',
|
||||
'md5': 'bb3cd38e24fbcc866d13b50ca59307b8',
|
||||
'note': 'Audio file',
|
||||
'info_dict': {
|
||||
'id': '191719',
|
||||
'id': '51535',
|
||||
'ext': 'mp3',
|
||||
'title': '"Es war schockierend"',
|
||||
'description': 'md5:ed7ff8364793545021a6355b97e95f10',
|
||||
'duration': 112,
|
||||
'title': 'La Valette entgeht der Hinrichtung',
|
||||
'description': 'md5:22f9541913a40fe50091d5cdd7c9f536',
|
||||
'duration': 884,
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -74,7 +74,8 @@ class NFBIE(InfoExtractor):
|
||||
description = media.find('description').text
|
||||
# It seems assets always go from lower to better quality, so no need to sort
|
||||
formats = [{
|
||||
'url': x.find('default/streamerURI').text + '/',
|
||||
'url': x.find('default/streamerURI').text,
|
||||
'app': x.find('default/streamerURI').text.split('/', 3)[3],
|
||||
'play_path': x.find('default/url').text,
|
||||
'rtmp_live': False,
|
||||
'ext': 'mp4',
|
||||
|
@ -1,61 +1,51 @@
|
||||
# encoding: utf-8
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
|
||||
from ..utils import (
|
||||
ExtractorError,
|
||||
unified_strdate,
|
||||
)
|
||||
|
||||
|
||||
class NormalbootsIE(InfoExtractor):
|
||||
_VALID_URL = r'(?:http://)?(?:www\.)?normalboots\.com/video/(?P<videoid>[0-9a-z-]*)/?$'
|
||||
_VALID_URL = r'http://(?:www\.)?normalboots\.com/video/(?P<videoid>[0-9a-z-]*)/?$'
|
||||
_TEST = {
|
||||
u'url': u'http://normalboots.com/video/home-alone-games-jontron/',
|
||||
u'file': u'home-alone-games-jontron.mp4',
|
||||
u'md5': u'8bf6de238915dd501105b44ef5f1e0f6',
|
||||
u'info_dict': {
|
||||
u'title': u'Home Alone Games - JonTron - NormalBoots',
|
||||
u'description': u'Jon is late for Christmas. Typical. Thanks to: Paul Ritchey for Co-Writing/Filming: http://www.youtube.com/user/ContinueShow Michael Azzi for Christmas Intro Animation: http://michafrar.tumblr.com/ Jerrod Waters for Christmas Intro Music: http://www.youtube.com/user/xXJerryTerryXx Casey Ormond for \u2018Tense Battle Theme\u2019:\xa0http://www.youtube.com/Kiamet/',
|
||||
u'uploader': u'JonTron',
|
||||
u'upload_date': u'20140125',
|
||||
'url': 'http://normalboots.com/video/home-alone-games-jontron/',
|
||||
'md5': '8bf6de238915dd501105b44ef5f1e0f6',
|
||||
'info_dict': {
|
||||
'id': 'home-alone-games-jontron',
|
||||
'ext': 'mp4',
|
||||
'title': 'Home Alone Games - JonTron - NormalBoots',
|
||||
'description': 'Jon is late for Christmas. Typical. Thanks to: Paul Ritchey for Co-Writing/Filming: http://www.youtube.com/user/ContinueShow Michael Azzi for Christmas Intro Animation: http://michafrar.tumblr.com/ Jerrod Waters for Christmas Intro Music: http://www.youtube.com/user/xXJerryTerryXx Casey Ormond for ‘Tense Battle Theme’:\xa0http://www.youtube.com/Kiamet/',
|
||||
'uploader': 'JonTron',
|
||||
'upload_date': '20140125',
|
||||
}
|
||||
}
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
if mobj is None:
|
||||
raise ExtractorError(u'Invalid URL: %s' % url)
|
||||
video_id = mobj.group('videoid')
|
||||
|
||||
info = {
|
||||
'id': video_id,
|
||||
'uploader': None,
|
||||
'upload_date': None,
|
||||
}
|
||||
|
||||
if url[:4] != 'http':
|
||||
url = 'http://' + url
|
||||
|
||||
webpage = self._download_webpage(url, video_id)
|
||||
video_title = self._og_search_title(webpage)
|
||||
video_description = self._og_search_description(webpage)
|
||||
video_thumbnail = self._og_search_thumbnail(webpage)
|
||||
video_uploader = self._html_search_regex(r'Posted\sby\s<a\shref="[A-Za-z0-9/]*">(?P<uploader>[A-Za-z]*)\s</a>',
|
||||
webpage, 'uploader')
|
||||
raw_upload_date = self._html_search_regex('<span style="text-transform:uppercase; font-size:inherit;">[A-Za-z]+, (?P<date>.*)</span>',
|
||||
webpage, 'date')
|
||||
video_upload_date = unified_strdate(raw_upload_date)
|
||||
video_upload_date = unified_strdate(raw_upload_date)
|
||||
|
||||
player_url = self._html_search_regex(r'<iframe\swidth="[0-9]+"\sheight="[0-9]+"\ssrc="(?P<url>[\S]+)"', webpage, 'url')
|
||||
player_page = self._download_webpage(player_url, video_id)
|
||||
video_url = u'http://player.screenwavemedia.com/' + self._html_search_regex(r"'file':\s'(?P<file>[0-9A-Za-z-_\.]+)'", player_page, 'file')
|
||||
video_url = self._html_search_regex(r"file:\s'(?P<file>[^']+\.mp4)'", player_page, 'file')
|
||||
|
||||
info['url'] = video_url
|
||||
info['title'] = video_title
|
||||
info['description'] = video_description
|
||||
info['thumbnail'] = video_thumbnail
|
||||
info['uploader'] = video_uploader
|
||||
info['upload_date'] = video_upload_date
|
||||
|
||||
return info
|
||||
return {
|
||||
'id': video_id,
|
||||
'url': video_url,
|
||||
'title': self._og_search_title(webpage),
|
||||
'description': self._og_search_description(webpage),
|
||||
'thumbnail': self._og_search_thumbnail(webpage),
|
||||
'uploader': video_uploader,
|
||||
'upload_date': video_upload_date,
|
||||
}
|
||||
|
@ -9,14 +9,25 @@ from ..utils import (
|
||||
)
|
||||
|
||||
|
||||
class NovamovIE(InfoExtractor):
|
||||
_VALID_URL = r'http://(?:(?:www\.)?novamov\.com/video/|(?:(?:embed|www)\.)novamov\.com/embed\.php\?v=)(?P<videoid>[a-z\d]{13})'
|
||||
class NovaMovIE(InfoExtractor):
|
||||
IE_NAME = 'novamov'
|
||||
IE_DESC = 'NovaMov'
|
||||
|
||||
_VALID_URL = r'http://(?:(?:www\.)?%(host)s/video/|(?:(?:embed|www)\.)%(host)s/embed\.php\?(?:.*?&)?v=)(?P<videoid>[a-z\d]{13})' % {'host': 'novamov\.com'}
|
||||
|
||||
_HOST = 'www.novamov.com'
|
||||
|
||||
_FILE_DELETED_REGEX = r'This file no longer exists on our servers!</h2>'
|
||||
_FILEKEY_REGEX = r'flashvars\.filekey="(?P<filekey>[^"]+)";'
|
||||
_TITLE_REGEX = r'(?s)<div class="v_tab blockborder rounded5" id="v_tab1">\s*<h3>([^<]+)</h3>'
|
||||
_DESCRIPTION_REGEX = r'(?s)<div class="v_tab blockborder rounded5" id="v_tab1">\s*<h3>[^<]+</h3><p>([^<]+)</p>'
|
||||
|
||||
_TEST = {
|
||||
'url': 'http://www.novamov.com/video/4rurhn9x446jj',
|
||||
'file': '4rurhn9x446jj.flv',
|
||||
'md5': '7205f346a52bbeba427603ba10d4b935',
|
||||
'info_dict': {
|
||||
'id': '4rurhn9x446jj',
|
||||
'ext': 'flv',
|
||||
'title': 'search engine optimization',
|
||||
'description': 'search engine optimization is used to rank the web page in the google search engine'
|
||||
},
|
||||
@ -27,31 +38,26 @@ class NovamovIE(InfoExtractor):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
video_id = mobj.group('videoid')
|
||||
|
||||
page = self._download_webpage('http://www.novamov.com/video/%s' % video_id,
|
||||
video_id, 'Downloading video page')
|
||||
page = self._download_webpage(
|
||||
'http://%s/video/%s' % (self._HOST, video_id), video_id, 'Downloading video page')
|
||||
|
||||
if re.search(r'This file no longer exists on our servers!</h2>', page) is not None:
|
||||
if re.search(self._FILE_DELETED_REGEX, page) is not None:
|
||||
raise ExtractorError(u'Video %s does not exist' % video_id, expected=True)
|
||||
|
||||
filekey = self._search_regex(
|
||||
r'flashvars\.filekey="(?P<filekey>[^"]+)";', page, 'filekey')
|
||||
filekey = self._search_regex(self._FILEKEY_REGEX, page, 'filekey')
|
||||
|
||||
title = self._html_search_regex(
|
||||
r'(?s)<div class="v_tab blockborder rounded5" id="v_tab1">\s*<h3>([^<]+)</h3>',
|
||||
page, 'title', fatal=False)
|
||||
title = self._html_search_regex(self._TITLE_REGEX, page, 'title', fatal=False)
|
||||
|
||||
description = self._html_search_regex(
|
||||
r'(?s)<div class="v_tab blockborder rounded5" id="v_tab1">\s*<h3>[^<]+</h3><p>([^<]+)</p>',
|
||||
page, 'description', fatal=False)
|
||||
description = self._html_search_regex(self._DESCRIPTION_REGEX, page, 'description', default='', fatal=False)
|
||||
|
||||
api_response = self._download_webpage(
|
||||
'http://www.novamov.com/api/player.api.php?key=%s&file=%s' % (filekey, video_id),
|
||||
video_id, 'Downloading video api response')
|
||||
'http://%s/api/player.api.php?key=%s&file=%s' % (self._HOST, filekey, video_id), video_id,
|
||||
'Downloading video api response')
|
||||
|
||||
response = compat_urlparse.parse_qs(api_response)
|
||||
|
||||
if 'error_msg' in response:
|
||||
raise ExtractorError('novamov returned error: %s' % response['error_msg'][0], expected=True)
|
||||
raise ExtractorError('%s returned error: %s' % (self.IE_NAME, response['error_msg'][0]), expected=True)
|
||||
|
||||
video_url = response['url'][0]
|
||||
|
||||
|
@ -1,46 +1,28 @@
|
||||
import re
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import compat_urlparse
|
||||
from .novamov import NovaMovIE
|
||||
|
||||
|
||||
class NowVideoIE(InfoExtractor):
|
||||
_VALID_URL = r'(?:https?://)?(?:www\.)?nowvideo\.(?:ch|sx)/video/(?P<id>\w+)'
|
||||
class NowVideoIE(NovaMovIE):
|
||||
IE_NAME = 'nowvideo'
|
||||
IE_DESC = 'NowVideo'
|
||||
|
||||
_VALID_URL = r'http://(?:(?:www\.)?%(host)s/video/|(?:(?:embed|www)\.)%(host)s/embed\.php\?(?:.*?&)?v=)(?P<videoid>[a-z\d]{13})' % {'host': 'nowvideo\.(?:ch|sx|eu)'}
|
||||
|
||||
_HOST = 'www.nowvideo.ch'
|
||||
|
||||
_FILE_DELETED_REGEX = r'>This file no longer exists on our servers.<'
|
||||
_FILEKEY_REGEX = r'var fkzd="([^"]+)";'
|
||||
_TITLE_REGEX = r'<h4>([^<]+)</h4>'
|
||||
_DESCRIPTION_REGEX = r'</h4>\s*<p>([^<]+)</p>'
|
||||
|
||||
_TEST = {
|
||||
u'url': u'http://www.nowvideo.ch/video/0mw0yow7b6dxa',
|
||||
u'file': u'0mw0yow7b6dxa.flv',
|
||||
u'md5': u'f8fbbc8add72bd95b7850c6a02fc8817',
|
||||
u'info_dict': {
|
||||
u"title": u"youtubedl test video _BaW_jenozKc.mp4"
|
||||
'url': 'http://www.nowvideo.ch/video/0mw0yow7b6dxa',
|
||||
'md5': 'f8fbbc8add72bd95b7850c6a02fc8817',
|
||||
'info_dict': {
|
||||
'id': '0mw0yow7b6dxa',
|
||||
'ext': 'flv',
|
||||
'title': 'youtubedl test video _BaW_jenozKc.mp4',
|
||||
'description': 'Description',
|
||||
}
|
||||
}
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
|
||||
video_id = mobj.group('id')
|
||||
webpage_url = 'http://www.nowvideo.ch/video/' + video_id
|
||||
embed_url = 'http://embed.nowvideo.ch/embed.php?v=' + video_id
|
||||
webpage = self._download_webpage(webpage_url, video_id)
|
||||
embed_page = self._download_webpage(embed_url, video_id,
|
||||
u'Downloading embed page')
|
||||
|
||||
self.report_extraction(video_id)
|
||||
|
||||
video_title = self._html_search_regex(r'<h4>(.*)</h4>',
|
||||
webpage, u'video title')
|
||||
|
||||
video_key = self._search_regex(r'var fkzd="(.*)";',
|
||||
embed_page, u'video key')
|
||||
|
||||
api_call = "http://www.nowvideo.ch/api/player.api.php?file={0}&numOfErrors=0&cid=1&key={1}".format(video_id, video_key)
|
||||
api_response = self._download_webpage(api_call, video_id,
|
||||
u'Downloading API page')
|
||||
video_url = compat_urlparse.parse_qs(api_response)[u'url'][0]
|
||||
|
||||
return [{
|
||||
'id': video_id,
|
||||
'url': video_url,
|
||||
'ext': 'flv',
|
||||
'title': video_title,
|
||||
}]
|
||||
|
@ -8,6 +8,7 @@ from .common import InfoExtractor
|
||||
from ..utils import (
|
||||
HEADRequest,
|
||||
unified_strdate,
|
||||
ExtractorError,
|
||||
)
|
||||
|
||||
|
||||
@ -35,7 +36,15 @@ class ORFIE(InfoExtractor):
|
||||
data_json = self._search_regex(
|
||||
r'initializeAdworx\((.+?)\);\n', webpage, 'video info')
|
||||
all_data = json.loads(data_json)
|
||||
sdata = all_data[0]['values']['segments']
|
||||
|
||||
def get_segments(all_data):
|
||||
for data in all_data:
|
||||
if data['name'] == 'Tracker::EPISODE_DETAIL_PAGE_OVER_PROGRAM':
|
||||
return data['values']['segments']
|
||||
|
||||
sdata = get_segments(all_data)
|
||||
if not sdata:
|
||||
raise ExtractorError('Unable to extract segments')
|
||||
|
||||
def quality_to_int(s):
|
||||
m = re.search('([0-9]+)', s)
|
||||
|
@ -1,7 +1,10 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import json
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import int_or_none
|
||||
|
||||
|
||||
class PodomaticIE(InfoExtractor):
|
||||
@ -9,14 +12,14 @@ class PodomaticIE(InfoExtractor):
|
||||
_VALID_URL = r'^(?P<proto>https?)://(?P<channel>[^.]+)\.podomatic\.com/entry/(?P<id>[^?]+)'
|
||||
|
||||
_TEST = {
|
||||
u"url": u"http://scienceteachingtips.podomatic.com/entry/2009-01-02T16_03_35-08_00",
|
||||
u"file": u"2009-01-02T16_03_35-08_00.mp3",
|
||||
u"md5": u"84bb855fcf3429e6bf72460e1eed782d",
|
||||
u"info_dict": {
|
||||
u"uploader": u"Science Teaching Tips",
|
||||
u"uploader_id": u"scienceteachingtips",
|
||||
u"title": u"64. When the Moon Hits Your Eye",
|
||||
u"duration": 446,
|
||||
"url": "http://scienceteachingtips.podomatic.com/entry/2009-01-02T16_03_35-08_00",
|
||||
"file": "2009-01-02T16_03_35-08_00.mp3",
|
||||
"md5": "84bb855fcf3429e6bf72460e1eed782d",
|
||||
"info_dict": {
|
||||
"uploader": "Science Teaching Tips",
|
||||
"uploader_id": "scienceteachingtips",
|
||||
"title": "64. When the Moon Hits Your Eye",
|
||||
"duration": 446,
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,7 +39,7 @@ class PodomaticIE(InfoExtractor):
|
||||
uploader = data['podcast']
|
||||
title = data['title']
|
||||
thumbnail = data['imageLocation']
|
||||
duration = int(data['length'] / 1000.0)
|
||||
duration = int_or_none(data.get('length'), 1000)
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
|
297
youtube_dl/extractor/prosiebensat1.py
Normal file
297
youtube_dl/extractor/prosiebensat1.py
Normal file
@ -0,0 +1,297 @@
|
||||
# encoding: utf-8
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
|
||||
from hashlib import sha1
|
||||
from .common import InfoExtractor
|
||||
from ..utils import (
|
||||
compat_urllib_parse,
|
||||
unified_strdate,
|
||||
clean_html,
|
||||
RegexNotFoundError,
|
||||
)
|
||||
|
||||
|
||||
class ProSiebenSat1IE(InfoExtractor):
|
||||
IE_NAME = 'prosiebensat1'
|
||||
IE_DESC = 'ProSiebenSat.1 Digital'
|
||||
_VALID_URL = r'https?://(?:www\.)?(?:(?:prosieben|prosiebenmaxx|sixx|sat1|kabeleins|ran|the-voice-of-germany)\.de|fem\.com)/(?P<id>.+)'
|
||||
|
||||
_TESTS = [
|
||||
{
|
||||
'url': 'http://www.prosieben.de/tv/circus-halligalli/videos/218-staffel-2-episode-18-jahresrueckblick-ganze-folge',
|
||||
'info_dict': {
|
||||
'id': '2104602',
|
||||
'ext': 'mp4',
|
||||
'title': 'Staffel 2, Episode 18 - Jahresrückblick',
|
||||
'description': 'md5:8733c81b702ea472e069bc48bb658fc1',
|
||||
'upload_date': '20131231',
|
||||
'duration': 5845.04,
|
||||
},
|
||||
'params': {
|
||||
# rtmp download
|
||||
'skip_download': True,
|
||||
},
|
||||
},
|
||||
{
|
||||
'url': 'http://www.prosieben.de/videokatalog/Gesellschaft/Leben/Trends/video-Lady-Umstyling-f%C3%BCr-Audrina-Rebekka-Audrina-Fergen-billig-aussehen-Battal-Modica-700544.html',
|
||||
'info_dict': {
|
||||
'id': '2570327',
|
||||
'ext': 'mp4',
|
||||
'title': 'Lady-Umstyling für Audrina',
|
||||
'description': 'md5:4c16d0c17a3461a0d43ea4084e96319d',
|
||||
'upload_date': '20131014',
|
||||
'duration': 606.76,
|
||||
},
|
||||
'params': {
|
||||
# rtmp download
|
||||
'skip_download': True,
|
||||
},
|
||||
'skip': 'Seems to be broken',
|
||||
},
|
||||
{
|
||||
'url': 'http://www.prosiebenmaxx.de/yep/one-piece/video/148-folge-48-gold-rogers-heimat-ganze-folge',
|
||||
'info_dict': {
|
||||
'id': '2437108',
|
||||
'ext': 'mp4',
|
||||
'title': 'Folge 48: Gold Rogers Heimat',
|
||||
'description': 'Ruffy erreicht die Insel, auf der der berühmte Gold Roger lebte und hingerichtet wurde.',
|
||||
'upload_date': '20140226',
|
||||
'duration': 1401.48,
|
||||
},
|
||||
'params': {
|
||||
# rtmp download
|
||||
'skip_download': True,
|
||||
},
|
||||
},
|
||||
{
|
||||
'url': 'http://www.sixx.de/stars-style/video/sexy-laufen-in-ugg-boots-clip',
|
||||
'info_dict': {
|
||||
'id': '2904997',
|
||||
'ext': 'mp4',
|
||||
'title': 'Sexy laufen in Ugg Boots',
|
||||
'description': 'md5:edf42b8bd5bc4e5da4db4222c5acb7d6',
|
||||
'upload_date': '20140122',
|
||||
'duration': 245.32,
|
||||
},
|
||||
'params': {
|
||||
# rtmp download
|
||||
'skip_download': True,
|
||||
},
|
||||
},
|
||||
{
|
||||
'url': 'http://www.sat1.de/film/der-ruecktritt/video/im-interview-kai-wiesinger-clip',
|
||||
'info_dict': {
|
||||
'id': '2906572',
|
||||
'ext': 'mp4',
|
||||
'title': 'Im Interview: Kai Wiesinger',
|
||||
'description': 'md5:e4e5370652ec63b95023e914190b4eb9',
|
||||
'upload_date': '20140225',
|
||||
'duration': 522.56,
|
||||
},
|
||||
'params': {
|
||||
# rtmp download
|
||||
'skip_download': True,
|
||||
},
|
||||
},
|
||||
{
|
||||
'url': 'http://www.kabeleins.de/tv/rosins-restaurants/videos/jagd-auf-fertigkost-im-elsthal-teil-2-ganze-folge',
|
||||
'info_dict': {
|
||||
'id': '2992323',
|
||||
'ext': 'mp4',
|
||||
'title': 'Jagd auf Fertigkost im Elsthal - Teil 2',
|
||||
'description': 'md5:2669cde3febe9bce13904f701e774eb6',
|
||||
'upload_date': '20140225',
|
||||
'duration': 2410.44,
|
||||
},
|
||||
'params': {
|
||||
# rtmp download
|
||||
'skip_download': True,
|
||||
},
|
||||
},
|
||||
{
|
||||
'url': 'http://www.ran.de/fussball/bundesliga/video/schalke-toennies-moechte-raul-zurueck-ganze-folge',
|
||||
'info_dict': {
|
||||
'id': '3004256',
|
||||
'ext': 'mp4',
|
||||
'title': 'Schalke: Tönnies möchte Raul zurück',
|
||||
'description': 'md5:4b5b271d9bcde223b54390754c8ece3f',
|
||||
'upload_date': '20140226',
|
||||
'duration': 228.96,
|
||||
},
|
||||
'params': {
|
||||
# rtmp download
|
||||
'skip_download': True,
|
||||
},
|
||||
},
|
||||
{
|
||||
'url': 'http://www.the-voice-of-germany.de/video/31-andreas-kuemmert-rocket-man-clip',
|
||||
'info_dict': {
|
||||
'id': '2572814',
|
||||
'ext': 'mp4',
|
||||
'title': 'Andreas Kümmert: Rocket Man',
|
||||
'description': 'md5:6ddb02b0781c6adf778afea606652e38',
|
||||
'upload_date': '20131017',
|
||||
'duration': 469.88,
|
||||
},
|
||||
'params': {
|
||||
# rtmp download
|
||||
'skip_download': True,
|
||||
},
|
||||
},
|
||||
{
|
||||
'url': 'http://www.fem.com/wellness/videos/wellness-video-clip-kurztripps-zum-valentinstag.html',
|
||||
'info_dict': {
|
||||
'id': '2156342',
|
||||
'ext': 'mp4',
|
||||
'title': 'Kurztrips zum Valentinstag',
|
||||
'description': 'md5:8ba6301e70351ae0bedf8da00f7ba528',
|
||||
'upload_date': '20130206',
|
||||
'duration': 307.24,
|
||||
},
|
||||
'params': {
|
||||
# rtmp download
|
||||
'skip_download': True,
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
_CLIPID_REGEXES = [
|
||||
r'"clip_id"\s*:\s+"(\d+)"',
|
||||
r'clipid: "(\d+)"',
|
||||
]
|
||||
_TITLE_REGEXES = [
|
||||
r'<h2 class="subtitle" itemprop="name">\s*(.+?)</h2>',
|
||||
r'<header class="clearfix">\s*<h3>(.+?)</h3>',
|
||||
r'<!-- start video -->\s*<h1>(.+?)</h1>',
|
||||
r'<div class="ep-femvideos-pi4-video-txt">\s*<h2>(.+?)</h2>',
|
||||
]
|
||||
_DESCRIPTION_REGEXES = [
|
||||
r'<p itemprop="description">\s*(.+?)</p>',
|
||||
r'<div class="videoDecription">\s*<p><strong>Beschreibung</strong>: (.+?)</p>',
|
||||
r'<div class="g-plusone" data-size="medium"></div>\s*</div>\s*</header>\s*(.+?)\s*<footer>',
|
||||
r'<p>(.+?)</p>\s*<div class="ep-femvideos-pi4-video-footer">',
|
||||
]
|
||||
_UPLOAD_DATE_REGEXES = [
|
||||
r'<meta property="og:published_time" content="(.+?)">',
|
||||
r'<span>\s*(\d{2}\.\d{2}\.\d{4} \d{2}:\d{2}) \|\s*<span itemprop="duration"',
|
||||
r'<footer>\s*(\d{2}\.\d{2}\.\d{4}) \d{2}:\d{2} Uhr',
|
||||
r'<span style="padding-left: 4px;line-height:20px; color:#404040">(\d{2}\.\d{2}\.\d{4})</span>',
|
||||
r'(\d{2}\.\d{2}\.\d{4}) \| \d{2}:\d{2} Min<br/>',
|
||||
]
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
video_id = mobj.group('id')
|
||||
|
||||
page = self._download_webpage(url, video_id, 'Downloading page')
|
||||
|
||||
def extract(patterns, name, page, fatal=False):
|
||||
for pattern in patterns:
|
||||
mobj = re.search(pattern, page)
|
||||
if mobj:
|
||||
return clean_html(mobj.group(1))
|
||||
if fatal:
|
||||
raise RegexNotFoundError(u'Unable to extract %s' % name)
|
||||
return None
|
||||
|
||||
clip_id = extract(self._CLIPID_REGEXES, 'clip id', page, fatal=True)
|
||||
|
||||
access_token = 'testclient'
|
||||
client_name = 'kolibri-1.2.5'
|
||||
client_location = url
|
||||
|
||||
videos_api_url = 'http://vas.sim-technik.de/vas/live/v2/videos?%s' % compat_urllib_parse.urlencode({
|
||||
'access_token': access_token,
|
||||
'client_location': client_location,
|
||||
'client_name': client_name,
|
||||
'ids': clip_id,
|
||||
})
|
||||
|
||||
videos = self._download_json(videos_api_url, clip_id, 'Downloading videos JSON')
|
||||
|
||||
duration = float(videos[0]['duration'])
|
||||
source_ids = [source['id'] for source in videos[0]['sources']]
|
||||
source_ids_str = ','.join(map(str, source_ids))
|
||||
|
||||
g = '01!8d8F_)r9]4s[qeuXfP%'
|
||||
|
||||
client_id = g[:2] + sha1(''.join([clip_id, g, access_token, client_location, g, client_name])
|
||||
.encode('utf-8')).hexdigest()
|
||||
|
||||
sources_api_url = 'http://vas.sim-technik.de/vas/live/v2/videos/%s/sources?%s' % (clip_id, compat_urllib_parse.urlencode({
|
||||
'access_token': access_token,
|
||||
'client_id': client_id,
|
||||
'client_location': client_location,
|
||||
'client_name': client_name,
|
||||
}))
|
||||
|
||||
sources = self._download_json(sources_api_url, clip_id, 'Downloading sources JSON')
|
||||
server_id = sources['server_id']
|
||||
|
||||
client_id = g[:2] + sha1(''.join([g, clip_id, access_token, server_id,
|
||||
client_location, source_ids_str, g, client_name])
|
||||
.encode('utf-8')).hexdigest()
|
||||
|
||||
url_api_url = 'http://vas.sim-technik.de/vas/live/v2/videos/%s/sources/url?%s' % (clip_id, compat_urllib_parse.urlencode({
|
||||
'access_token': access_token,
|
||||
'client_id': client_id,
|
||||
'client_location': client_location,
|
||||
'client_name': client_name,
|
||||
'server_id': server_id,
|
||||
'source_ids': source_ids_str,
|
||||
}))
|
||||
|
||||
urls = self._download_json(url_api_url, clip_id, 'Downloading urls JSON')
|
||||
|
||||
title = extract(self._TITLE_REGEXES, 'title', page, fatal=True)
|
||||
description = extract(self._DESCRIPTION_REGEXES, 'description', page)
|
||||
thumbnail = self._og_search_thumbnail(page)
|
||||
|
||||
upload_date = extract(self._UPLOAD_DATE_REGEXES, 'upload date', page)
|
||||
if upload_date:
|
||||
upload_date = unified_strdate(upload_date)
|
||||
|
||||
formats = []
|
||||
|
||||
urls_sources = urls['sources']
|
||||
if isinstance(urls_sources, dict):
|
||||
urls_sources = urls_sources.values()
|
||||
|
||||
def fix_bitrate(bitrate):
|
||||
return bitrate / 1000 if bitrate % 1000 == 0 else bitrate
|
||||
|
||||
for source in urls_sources:
|
||||
protocol = source['protocol']
|
||||
if protocol == 'rtmp' or protocol == 'rtmpe':
|
||||
mobj = re.search(r'^(?P<url>rtmpe?://[^/]+/(?P<app>[^/]+))/(?P<playpath>.+)$', source['url'])
|
||||
if not mobj:
|
||||
continue
|
||||
formats.append({
|
||||
'url': mobj.group('url'),
|
||||
'app': mobj.group('app'),
|
||||
'play_path': mobj.group('playpath'),
|
||||
'player_url': 'http://livepassdl.conviva.com/hf/ver/2.79.0.17083/LivePassModuleMain.swf',
|
||||
'page_url': 'http://www.prosieben.de',
|
||||
'vbr': fix_bitrate(source['bitrate']),
|
||||
'ext': 'mp4',
|
||||
'format_id': '%s_%s' % (source['cdn'], source['bitrate']),
|
||||
})
|
||||
else:
|
||||
formats.append({
|
||||
'url': source['url'],
|
||||
'vbr': fix_bitrate(source['bitrate']),
|
||||
})
|
||||
|
||||
self._sort_formats(formats)
|
||||
|
||||
return {
|
||||
'id': clip_id,
|
||||
'title': title,
|
||||
'description': description,
|
||||
'thumbnail': thumbnail,
|
||||
'upload_date': upload_date,
|
||||
'duration': duration,
|
||||
'formats': formats,
|
||||
}
|
@ -1,148 +1,165 @@
|
||||
# encoding: utf-8
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import (
|
||||
clean_html,
|
||||
ExtractorError,
|
||||
clean_html,
|
||||
unified_strdate,
|
||||
int_or_none,
|
||||
)
|
||||
|
||||
|
||||
class RTLnowIE(InfoExtractor):
|
||||
"""Information Extractor for RTL NOW, RTL2 NOW, RTL NITRO, SUPER RTL NOW, VOX NOW and n-tv NOW"""
|
||||
_VALID_URL = r'(?:http://)?(?P<url>(?P<domain>rtl-now\.rtl\.de|rtl2now\.rtl2\.de|(?:www\.)?voxnow\.de|(?:www\.)?rtlnitronow\.de|(?:www\.)?superrtlnow\.de|(?:www\.)?n-tvnow\.de)/+[a-zA-Z0-9-]+/[a-zA-Z0-9-]+\.php\?(?:container_id|film_id)=(?P<video_id>[0-9]+)&player=1(?:&season=[0-9]+)?(?:&.*)?)'
|
||||
_TESTS = [{
|
||||
'url': 'http://rtl-now.rtl.de/ahornallee/folge-1.php?film_id=90419&player=1&season=1',
|
||||
'file': '90419.flv',
|
||||
'info_dict': {
|
||||
'upload_date': '20070416',
|
||||
'title': 'Ahornallee - Folge 1 - Der Einzug',
|
||||
'description': 'Folge 1 - Der Einzug',
|
||||
_VALID_URL = r'''(?x)
|
||||
(?:https?://)?
|
||||
(?P<url>
|
||||
(?P<domain>
|
||||
rtl-now\.rtl\.de|
|
||||
rtl2now\.rtl2\.de|
|
||||
(?:www\.)?voxnow\.de|
|
||||
(?:www\.)?rtlnitronow\.de|
|
||||
(?:www\.)?superrtlnow\.de|
|
||||
(?:www\.)?n-tvnow\.de)
|
||||
/+[a-zA-Z0-9-]+/[a-zA-Z0-9-]+\.php\?
|
||||
(?:container_id|film_id)=(?P<video_id>[0-9]+)&
|
||||
player=1(?:&season=[0-9]+)?(?:&.*)?
|
||||
)'''
|
||||
|
||||
_TESTS = [
|
||||
{
|
||||
'url': 'http://rtl-now.rtl.de/ahornallee/folge-1.php?film_id=90419&player=1&season=1',
|
||||
'info_dict': {
|
||||
'id': '90419',
|
||||
'ext': 'flv',
|
||||
'title': 'Ahornallee - Folge 1 - Der Einzug',
|
||||
'description': 'md5:ce843b6b5901d9a7f7d04d1bbcdb12de',
|
||||
'upload_date': '20070416',
|
||||
'duration': 1685,
|
||||
},
|
||||
'params': {
|
||||
'skip_download': True,
|
||||
},
|
||||
'skip': 'Only works from Germany',
|
||||
},
|
||||
'params': {
|
||||
'skip_download': True,
|
||||
{
|
||||
'url': 'http://rtl2now.rtl2.de/aerger-im-revier/episode-15-teil-1.php?film_id=69756&player=1&season=2&index=5',
|
||||
'info_dict': {
|
||||
'id': '69756',
|
||||
'ext': 'flv',
|
||||
'title': 'Ärger im Revier - Ein junger Ladendieb, ein handfester Streit u.a.',
|
||||
'description': 'md5:3fb247005ed21a935ffc82b7dfa70cf0',
|
||||
'thumbnail': 'http://autoimg.static-fra.de/rtl2now/219850/1500x1500/image2.jpg',
|
||||
'upload_date': '20120519',
|
||||
'duration': 1245,
|
||||
},
|
||||
'params': {
|
||||
'skip_download': True,
|
||||
},
|
||||
'skip': 'Only works from Germany',
|
||||
},
|
||||
'skip': 'Only works from Germany',
|
||||
},
|
||||
{
|
||||
'url': 'http://rtl2now.rtl2.de/aerger-im-revier/episode-15-teil-1.php?film_id=69756&player=1&season=2&index=5',
|
||||
'file': '69756.flv',
|
||||
'info_dict': {
|
||||
'upload_date': '20120519',
|
||||
'title': 'Ärger im Revier - Ein junger Ladendieb, ein handfester Streit...',
|
||||
'description': 'Ärger im Revier - Ein junger Ladendieb, ein handfester Streit u.a.',
|
||||
'thumbnail': 'http://autoimg.static-fra.de/rtl2now/219850/1500x1500/image2.jpg',
|
||||
{
|
||||
'url': 'http://www.voxnow.de/voxtours/suedafrika-reporter-ii.php?film_id=13883&player=1&season=17',
|
||||
'info_dict': {
|
||||
'id': '13883',
|
||||
'ext': 'flv',
|
||||
'title': 'Voxtours - Südafrika-Reporter II',
|
||||
'description': 'md5:de7f8d56be6fd4fed10f10f57786db00',
|
||||
'upload_date': '20090627',
|
||||
'duration': 1800,
|
||||
},
|
||||
'params': {
|
||||
'skip_download': True,
|
||||
},
|
||||
},
|
||||
'params': {
|
||||
'skip_download': True,
|
||||
{
|
||||
'url': 'http://superrtlnow.de/medicopter-117/angst.php?film_id=99205&player=1',
|
||||
'info_dict': {
|
||||
'id': '99205',
|
||||
'ext': 'flv',
|
||||
'title': 'Medicopter 117 - Angst!',
|
||||
'description': 'md5:895b1df01639b5f61a04fc305a5cb94d',
|
||||
'thumbnail': 'http://autoimg.static-fra.de/superrtlnow/287529/1500x1500/image2.jpg',
|
||||
'upload_date': '20080928',
|
||||
'duration': 2691,
|
||||
},
|
||||
'params': {
|
||||
'skip_download': True,
|
||||
},
|
||||
},
|
||||
'skip': 'Only works from Germany',
|
||||
},
|
||||
{
|
||||
'url': 'http://www.voxnow.de/voxtours/suedafrika-reporter-ii.php?film_id=13883&player=1&season=17',
|
||||
'file': '13883.flv',
|
||||
'info_dict': {
|
||||
'upload_date': '20090627',
|
||||
'title': 'Voxtours - Südafrika-Reporter II',
|
||||
'description': 'Südafrika-Reporter II',
|
||||
{
|
||||
'url': 'http://www.n-tvnow.de/deluxe-alles-was-spass-macht/thema-ua-luxushotel-fuer-vierbeiner.php?container_id=153819&player=1&season=0',
|
||||
'info_dict': {
|
||||
'id': '153819',
|
||||
'ext': 'flv',
|
||||
'title': 'Deluxe - Alles was Spaß macht - Thema u.a.: Luxushotel für Vierbeiner',
|
||||
'description': 'md5:c3705e1bb32e1a5b2bcd634fc065c631',
|
||||
'thumbnail': 'http://autoimg.static-fra.de/ntvnow/383157/1500x1500/image2.jpg',
|
||||
'upload_date': '20140221',
|
||||
'duration': 2429,
|
||||
},
|
||||
'skip': 'Only works from Germany',
|
||||
},
|
||||
'params': {
|
||||
'skip_download': True,
|
||||
},
|
||||
},
|
||||
{
|
||||
'url': 'http://superrtlnow.de/medicopter-117/angst.php?film_id=99205&player=1',
|
||||
'file': '99205.flv',
|
||||
'info_dict': {
|
||||
'upload_date': '20080928',
|
||||
'title': 'Medicopter 117 - Angst!',
|
||||
'description': 'Angst!',
|
||||
'thumbnail': 'http://autoimg.static-fra.de/superrtlnow/287529/1500x1500/image2.jpg'
|
||||
},
|
||||
'params': {
|
||||
'skip_download': True,
|
||||
},
|
||||
},
|
||||
{
|
||||
'url': 'http://www.n-tvnow.de/top-gear/episode-1-2013-01-01-00-00-00.php?film_id=124903&player=1&season=10',
|
||||
'file': '124903.flv',
|
||||
'info_dict': {
|
||||
'upload_date': '20130101',
|
||||
'title': 'Top Gear vom 01.01.2013',
|
||||
'description': 'Episode 1',
|
||||
},
|
||||
'params': {
|
||||
'skip_download': True,
|
||||
},
|
||||
'skip': 'Only works from Germany',
|
||||
}]
|
||||
]
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
|
||||
webpage_url = 'http://' + mobj.group('url')
|
||||
video_page_url = 'http://' + mobj.group('domain') + '/'
|
||||
video_page_url = 'http://%s/' % mobj.group('domain')
|
||||
video_id = mobj.group('video_id')
|
||||
|
||||
webpage = self._download_webpage(webpage_url, video_id)
|
||||
webpage = self._download_webpage('http://' + mobj.group('url'), video_id)
|
||||
|
||||
note_m = re.search(r'''(?sx)
|
||||
<div[ ]style="margin-left:[ ]20px;[ ]font-size:[ ]13px;">(.*?)
|
||||
<div[ ]id="playerteaser">''', webpage)
|
||||
if note_m:
|
||||
msg = clean_html(note_m.group(1))
|
||||
raise ExtractorError(msg)
|
||||
mobj = re.search(r'(?s)<div style="margin-left: 20px; font-size: 13px;">(.*?)<div id="playerteaser">', webpage)
|
||||
if mobj:
|
||||
raise ExtractorError(clean_html(mobj.group(1)), expected=True)
|
||||
|
||||
title = self._og_search_title(webpage)
|
||||
description = self._og_search_description(webpage)
|
||||
thumbnail = self._og_search_thumbnail(webpage, default=None)
|
||||
|
||||
upload_date = unified_strdate(self._html_search_meta('uploadDate', webpage, 'upload date'))
|
||||
|
||||
mobj = re.search(r'<meta itemprop="duration" content="PT(?P<seconds>\d+)S" />', webpage)
|
||||
duration = int(mobj.group('seconds')) if mobj else None
|
||||
|
||||
video_title = self._html_search_regex(
|
||||
r'<title>(?P<title>[^<]+?)( \| [^<]*)?</title>',
|
||||
webpage, 'title')
|
||||
playerdata_url = self._html_search_regex(
|
||||
r'\'playerdata\': \'(?P<playerdata_url>[^\']+)\'',
|
||||
webpage, 'playerdata_url')
|
||||
r"'playerdata': '(?P<playerdata_url>[^']+)'", webpage, 'playerdata_url')
|
||||
|
||||
playerdata = self._download_webpage(playerdata_url, video_id)
|
||||
mobj = re.search(r'<title><!\[CDATA\[(?P<description>.+?)(?:\s+- (?:Sendung )?vom (?P<upload_date_d>[0-9]{2})\.(?P<upload_date_m>[0-9]{2})\.(?:(?P<upload_date_Y>[0-9]{4})|(?P<upload_date_y>[0-9]{2})) [0-9]{2}:[0-9]{2} Uhr)?\]\]></title>', playerdata)
|
||||
if mobj:
|
||||
video_description = mobj.group('description')
|
||||
if mobj.group('upload_date_Y'):
|
||||
video_upload_date = mobj.group('upload_date_Y')
|
||||
elif mobj.group('upload_date_y'):
|
||||
video_upload_date = '20' + mobj.group('upload_date_y')
|
||||
playerdata = self._download_xml(playerdata_url, video_id, 'Downloading player data XML')
|
||||
|
||||
videoinfo = playerdata.find('./playlist/videoinfo')
|
||||
|
||||
formats = []
|
||||
for filename in videoinfo.findall('filename'):
|
||||
mobj = re.search(r'(?P<url>rtmpe://(?:[^/]+/){2})(?P<play_path>.+)', filename.text)
|
||||
if mobj:
|
||||
fmt = {
|
||||
'url': mobj.group('url'),
|
||||
'play_path': 'mp4:' + mobj.group('play_path'),
|
||||
'page_url': video_page_url,
|
||||
'player_url': video_page_url + 'includes/vodplayer.swf',
|
||||
}
|
||||
else:
|
||||
video_upload_date = None
|
||||
if video_upload_date:
|
||||
video_upload_date += mobj.group('upload_date_m') + mobj.group('upload_date_d')
|
||||
else:
|
||||
video_description = None
|
||||
video_upload_date = None
|
||||
self._downloader.report_warning('Unable to extract description and upload date')
|
||||
|
||||
# Thumbnail: not every video has an thumbnail
|
||||
mobj = re.search(r'<meta property="og:image" content="(?P<thumbnail>[^"]+)">', webpage)
|
||||
if mobj:
|
||||
video_thumbnail = mobj.group('thumbnail')
|
||||
else:
|
||||
video_thumbnail = None
|
||||
|
||||
mobj = re.search(r'<filename [^>]+><!\[CDATA\[(?P<url>rtmpe://(?:[^/]+/){2})(?P<play_path>[^\]]+)\]\]></filename>', playerdata)
|
||||
if mobj is None:
|
||||
raise ExtractorError('Unable to extract media URL')
|
||||
video_url = mobj.group('url')
|
||||
video_play_path = 'mp4:' + mobj.group('play_path')
|
||||
video_player_url = video_page_url + 'includes/vodplayer.swf'
|
||||
fmt = {
|
||||
'url': filename.text,
|
||||
}
|
||||
fmt.update({
|
||||
'width': int_or_none(filename.get('width')),
|
||||
'height': int_or_none(filename.get('height')),
|
||||
'vbr': int_or_none(filename.get('bitrate')),
|
||||
'ext': 'flv',
|
||||
})
|
||||
formats.append(fmt)
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
'url': video_url,
|
||||
'play_path': video_play_path,
|
||||
'page_url': video_page_url,
|
||||
'player_url': video_player_url,
|
||||
'ext': 'flv',
|
||||
'title': video_title,
|
||||
'description': video_description,
|
||||
'upload_date': video_upload_date,
|
||||
'thumbnail': video_thumbnail,
|
||||
'title': title,
|
||||
'description': description,
|
||||
'thumbnail': thumbnail,
|
||||
'upload_date': upload_date,
|
||||
'duration': duration,
|
||||
'formats': formats,
|
||||
}
|
37
youtube_dl/extractor/savefrom.py
Normal file
37
youtube_dl/extractor/savefrom.py
Normal file
@ -0,0 +1,37 @@
|
||||
# coding: utf-8
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os.path
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
|
||||
|
||||
class SaveFromIE(InfoExtractor):
|
||||
IE_NAME = 'savefrom.net'
|
||||
_VALID_URL = r'https?://[^.]+\.savefrom\.net/\#url=(?P<url>.*)$'
|
||||
|
||||
_TEST = {
|
||||
'url': 'http://en.savefrom.net/#url=http://youtube.com/watch?v=UlVRAPW2WJY&utm_source=youtube.com&utm_medium=short_domains&utm_campaign=ssyoutube.com',
|
||||
'info_dict': {
|
||||
'id': 'UlVRAPW2WJY',
|
||||
'ext': 'mp4',
|
||||
'title': 'About Team Radical MMA | MMA Fighting',
|
||||
'upload_date': '20120816',
|
||||
'uploader': 'Howcast',
|
||||
'uploader_id': 'Howcast',
|
||||
'description': 'md5:4f0aac94361a12e1ce57d74f85265175',
|
||||
},
|
||||
'params': {
|
||||
'skip_download': True
|
||||
}
|
||||
}
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
video_id = os.path.splitext(url.split('/')[-1])[0]
|
||||
return {
|
||||
'_type': 'url',
|
||||
'id': video_id,
|
||||
'url': mobj.group('url'),
|
||||
}
|
@ -20,6 +20,7 @@ class SmotriIE(InfoExtractor):
|
||||
IE_DESC = 'Smotri.com'
|
||||
IE_NAME = 'smotri'
|
||||
_VALID_URL = r'^https?://(?:www\.)?(?P<url>smotri\.com/video/view/\?id=(?P<videoid>v(?P<realvideoid>[0-9]+)[a-z0-9]{4}))'
|
||||
_NETRC_MACHINE = 'smotri'
|
||||
|
||||
_TESTS = [
|
||||
# real video id 2610366
|
||||
|
@ -17,6 +17,7 @@ class SohuIE(InfoExtractor):
|
||||
u'info_dict': {
|
||||
u'title': u'MV:Far East Movement《The Illest》',
|
||||
},
|
||||
u'skip': u'Only available from China',
|
||||
}
|
||||
|
||||
def _real_extract(self, url):
|
||||
|
@ -1,3 +1,5 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
@ -8,14 +10,14 @@ from ..utils import RegexNotFoundError, ExtractorError
|
||||
class SpaceIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://(?:(?:www|m)\.)?space\.com/\d+-(?P<title>[^/\.\?]*?)-video\.html'
|
||||
_TEST = {
|
||||
u'add_ie': ['Brightcove'],
|
||||
u'url': u'http://www.space.com/23373-huge-martian-landforms-detail-revealed-by-european-probe-video.html',
|
||||
u'info_dict': {
|
||||
u'id': u'2780937028001',
|
||||
u'ext': u'mp4',
|
||||
u'title': u'Huge Martian Landforms\' Detail Revealed By European Probe | Video',
|
||||
u'description': u'md5:db81cf7f3122f95ed234b631a6ea1e61',
|
||||
u'uploader': u'TechMedia Networks',
|
||||
'add_ie': ['Brightcove'],
|
||||
'url': 'http://www.space.com/23373-huge-martian-landforms-detail-revealed-by-european-probe-video.html',
|
||||
'info_dict': {
|
||||
'id': '2780937028001',
|
||||
'ext': 'mp4',
|
||||
'title': 'Huge Martian Landforms\' Detail Revealed By European Probe | Video',
|
||||
'description': 'md5:db81cf7f3122f95ed234b631a6ea1e61',
|
||||
'uploader': 'TechMedia Networks',
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
@ -8,23 +7,27 @@ from ..utils import (
|
||||
compat_urllib_parse_urlparse,
|
||||
compat_urllib_request,
|
||||
compat_urllib_parse,
|
||||
unified_strdate,
|
||||
str_to_int,
|
||||
int_or_none,
|
||||
)
|
||||
from ..aes import (
|
||||
aes_decrypt_text
|
||||
)
|
||||
from ..aes import aes_decrypt_text
|
||||
|
||||
|
||||
class SpankwireIE(InfoExtractor):
|
||||
_VALID_URL = r'^(?:https?://)?(?:www\.)?(?P<url>spankwire\.com/[^/]*/video(?P<videoid>[0-9]+)/?)'
|
||||
_VALID_URL = r'https?://(?:www\.)?(?P<url>spankwire\.com/[^/]*/video(?P<videoid>[0-9]+)/?)'
|
||||
_TEST = {
|
||||
'url': 'http://www.spankwire.com/Buckcherry-s-X-Rated-Music-Video-Crazy-Bitch/video103545/',
|
||||
'file': '103545.mp4',
|
||||
'md5': '1b3f55e345500552dbc252a3e9c1af43',
|
||||
'md5': '8bbfde12b101204b39e4b9fe7eb67095',
|
||||
'info_dict': {
|
||||
"uploader": "oreusz",
|
||||
"title": "Buckcherry`s X Rated Music Video Crazy Bitch",
|
||||
"description": "Crazy Bitch X rated music video.",
|
||||
"age_limit": 18,
|
||||
'id': '103545',
|
||||
'ext': 'mp4',
|
||||
'title': 'Buckcherry`s X Rated Music Video Crazy Bitch',
|
||||
'description': 'Crazy Bitch X rated music video.',
|
||||
'uploader': 'oreusz',
|
||||
'uploader_id': '124697',
|
||||
'upload_date': '20070508',
|
||||
'age_limit': 18,
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,13 +40,26 @@ class SpankwireIE(InfoExtractor):
|
||||
req.add_header('Cookie', 'age_verified=1')
|
||||
webpage = self._download_webpage(req, video_id)
|
||||
|
||||
video_title = self._html_search_regex(r'<h1>([^<]+)', webpage, 'title')
|
||||
video_uploader = self._html_search_regex(
|
||||
r'by:\s*<a [^>]*>(.+?)</a>', webpage, 'uploader', fatal=False)
|
||||
thumbnail = self._html_search_regex(
|
||||
r'flashvars\.image_url = "([^"]+)', webpage, 'thumbnail', fatal=False)
|
||||
title = self._html_search_regex(r'<h1>([^<]+)', webpage, 'title')
|
||||
description = self._html_search_regex(
|
||||
r'<div\s+id="descriptionContent">([^<]+)<', webpage, 'description', fatal=False)
|
||||
thumbnail = self._html_search_regex(
|
||||
r'flashvars\.image_url = "([^"]+)', webpage, 'thumbnail', fatal=False)
|
||||
|
||||
uploader = self._html_search_regex(
|
||||
r'by:\s*<a [^>]*>(.+?)</a>', webpage, 'uploader', fatal=False)
|
||||
uploader_id = self._html_search_regex(
|
||||
r'by:\s*<a href="/Profile\.aspx\?.*?UserId=(\d+).*?"', webpage, 'uploader id', fatal=False)
|
||||
upload_date = self._html_search_regex(r'</a> on (.+?) at \d+:\d+', webpage, 'upload date', fatal=False)
|
||||
if upload_date:
|
||||
upload_date = unified_strdate(upload_date)
|
||||
|
||||
view_count = self._html_search_regex(
|
||||
r'<div id="viewsCounter"><span>([^<]+)</span> views</div>', webpage, 'view count', fatal=False)
|
||||
if view_count:
|
||||
view_count = str_to_int(view_count)
|
||||
comment_count = int_or_none(self._html_search_regex(
|
||||
r'<span id="spCommentCount">\s*(\d+)</span> Comments</div>', webpage, 'comment count', fatal=False))
|
||||
|
||||
video_urls = list(map(compat_urllib_parse.unquote , re.findall(r'flashvars\.quality_[0-9]{3}p = "([^"]+)', webpage)))
|
||||
if webpage.find('flashvars\.encrypted = "true"') != -1:
|
||||
@ -53,16 +69,13 @@ class SpankwireIE(InfoExtractor):
|
||||
formats = []
|
||||
for video_url in video_urls:
|
||||
path = compat_urllib_parse_urlparse(video_url).path
|
||||
extension = os.path.splitext(path)[1][1:]
|
||||
format = path.split('/')[4].split('_')[:2]
|
||||
resolution, bitrate_str = format
|
||||
format = "-".join(format)
|
||||
height = int(resolution.rstrip('P'))
|
||||
tbr = int(bitrate_str.rstrip('K'))
|
||||
|
||||
height = int(resolution.rstrip('Pp'))
|
||||
tbr = int(bitrate_str.rstrip('Kk'))
|
||||
formats.append({
|
||||
'url': video_url,
|
||||
'ext': extension,
|
||||
'resolution': resolution,
|
||||
'format': format,
|
||||
'tbr': tbr,
|
||||
@ -75,10 +88,14 @@ class SpankwireIE(InfoExtractor):
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
'uploader': video_uploader,
|
||||
'title': video_title,
|
||||
'thumbnail': thumbnail,
|
||||
'title': title,
|
||||
'description': description,
|
||||
'thumbnail': thumbnail,
|
||||
'uploader': uploader,
|
||||
'uploader_id': uploader_id,
|
||||
'upload_date': upload_date,
|
||||
'view_count': view_count,
|
||||
'comment_count': comment_count,
|
||||
'formats': formats,
|
||||
'age_limit': age_limit,
|
||||
}
|
||||
|
27
youtube_dl/extractor/syfy.py
Normal file
27
youtube_dl/extractor/syfy.py
Normal file
@ -0,0 +1,27 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
|
||||
|
||||
class SyfyIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://www\.syfy\.com/videos/.+?vid:(?P<id>\d+)'
|
||||
|
||||
_TEST = {
|
||||
'url': 'http://www.syfy.com/videos/Robot%20Combat%20League/Behind%20the%20Scenes/vid:2631458',
|
||||
'md5': 'e07de1d52c7278adbb9b9b1c93a66849',
|
||||
'info_dict': {
|
||||
'id': 'NmqMrGnXvmO1',
|
||||
'ext': 'flv',
|
||||
'title': 'George Lucas has Advice for his Daughter',
|
||||
'description': 'Listen to what insights George Lucas give his daughter Amanda.',
|
||||
},
|
||||
'add_ie': ['ThePlatform'],
|
||||
}
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
video_id = mobj.group('id')
|
||||
webpage = self._download_webpage(url, video_id)
|
||||
return self.url_result(self._og_search_video_url(webpage))
|
68
youtube_dl/extractor/testurl.py
Normal file
68
youtube_dl/extractor/testurl.py
Normal file
@ -0,0 +1,68 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import ExtractorError
|
||||
|
||||
|
||||
class TestURLIE(InfoExtractor):
|
||||
""" Allows adressing of the test cases as test:yout.*be_1 """
|
||||
|
||||
IE_DESC = False # Do not list
|
||||
_VALID_URL = r'test(?:url)?:(?P<id>(?P<extractor>.+?)(?:_(?P<num>[0-9]+))?)$'
|
||||
|
||||
def _real_extract(self, url):
|
||||
from ..extractor import gen_extractors
|
||||
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
video_id = mobj.group('id')
|
||||
extractor_id = mobj.group('extractor')
|
||||
all_extractors = gen_extractors()
|
||||
|
||||
rex = re.compile(extractor_id, flags=re.IGNORECASE)
|
||||
matching_extractors = [
|
||||
e for e in all_extractors if rex.search(e.IE_NAME)]
|
||||
|
||||
if len(matching_extractors) == 0:
|
||||
raise ExtractorError(
|
||||
'No extractors matching %r found' % extractor_id,
|
||||
expected=True)
|
||||
elif len(matching_extractors) > 1:
|
||||
# Is it obvious which one to pick?
|
||||
try:
|
||||
extractor = next(
|
||||
ie for ie in matching_extractors
|
||||
if ie.IE_NAME.lower() == extractor_id.lower())
|
||||
except StopIteration:
|
||||
raise ExtractorError(
|
||||
('Found multiple matching extractors: %s' %
|
||||
' '.join(ie.IE_NAME for ie in matching_extractors)),
|
||||
expected=True)
|
||||
else:
|
||||
extractor = matching_extractors[0]
|
||||
|
||||
num_str = mobj.group('num')
|
||||
num = int(num_str) if num_str else 0
|
||||
|
||||
testcases = []
|
||||
t = getattr(extractor, '_TEST', None)
|
||||
if t:
|
||||
testcases.append(t)
|
||||
testcases.extend(getattr(extractor, '_TESTS', []))
|
||||
|
||||
try:
|
||||
tc = testcases[num]
|
||||
except IndexError:
|
||||
raise ExtractorError(
|
||||
('Test case %d not found, got only %d tests' %
|
||||
(num, len(testcases))),
|
||||
expected=True)
|
||||
|
||||
self.to_screen('Test URL: %s' % tc['url'])
|
||||
|
||||
return {
|
||||
'_type': 'url',
|
||||
'url': tc['url'],
|
||||
'id': video_id,
|
||||
}
|
@ -11,7 +11,10 @@ _x = lambda p: xpath_with_ns(p, {'smil': 'http://www.w3.org/2005/SMIL21/Language
|
||||
|
||||
|
||||
class ThePlatformIE(InfoExtractor):
|
||||
_VALID_URL = r'(?:https?://link\.theplatform\.com/s/[^/]+/|theplatform:)(?P<id>[^/\?]+)'
|
||||
_VALID_URL = r'''(?x)
|
||||
(?:https?://(?:link|player)\.theplatform\.com/[sp]/[^/]+/
|
||||
(?P<config>(?:[^/\?]+/(?:swf|config)|onsite)/select/)?
|
||||
|theplatform:)(?P<id>[^/\?&]+)'''
|
||||
|
||||
_TEST = {
|
||||
# from http://www.metacafe.com/watch/cb-e9I_cZgTgIPd/blackberrys_big_bold_z30/
|
||||
@ -29,9 +32,7 @@ class ThePlatformIE(InfoExtractor):
|
||||
},
|
||||
}
|
||||
|
||||
def _get_info(self, video_id):
|
||||
smil_url = ('http://link.theplatform.com/s/dJ5BDC/{0}/meta.smil?'
|
||||
'format=smil&mbr=true'.format(video_id))
|
||||
def _get_info(self, video_id, smil_url):
|
||||
meta = self._download_xml(smil_url, video_id)
|
||||
|
||||
try:
|
||||
@ -50,26 +51,39 @@ class ThePlatformIE(InfoExtractor):
|
||||
|
||||
head = meta.find(_x('smil:head'))
|
||||
body = meta.find(_x('smil:body'))
|
||||
base_url = head.find(_x('smil:meta')).attrib['base']
|
||||
switch = body.find(_x('smil:switch'))
|
||||
formats = []
|
||||
for f in switch.findall(_x('smil:video')):
|
||||
attr = f.attrib
|
||||
width = int(attr['width'])
|
||||
height = int(attr['height'])
|
||||
vbr = int(attr['system-bitrate']) // 1000
|
||||
format_id = '%dx%d_%dk' % (width, height, vbr)
|
||||
formats.append({
|
||||
'format_id': format_id,
|
||||
'url': base_url,
|
||||
'play_path': 'mp4:' + attr['src'],
|
||||
'ext': 'flv',
|
||||
'width': width,
|
||||
'height': height,
|
||||
'vbr': vbr,
|
||||
})
|
||||
|
||||
self._sort_formats(formats)
|
||||
f4m_node = body.find(_x('smil:seq/smil:video'))
|
||||
if f4m_node is not None:
|
||||
f4m_url = f4m_node.attrib['src']
|
||||
if 'manifest.f4m?' not in f4m_url:
|
||||
f4m_url += '?'
|
||||
# the parameters are from syfy.com, other sites may use others,
|
||||
# they also work for nbc.com
|
||||
f4m_url += '&g=UXWGVKRWHFSP&hdcore=3.0.3'
|
||||
formats = [{
|
||||
'ext': 'flv',
|
||||
'url': f4m_url,
|
||||
}]
|
||||
else:
|
||||
base_url = head.find(_x('smil:meta')).attrib['base']
|
||||
switch = body.find(_x('smil:switch'))
|
||||
formats = []
|
||||
for f in switch.findall(_x('smil:video')):
|
||||
attr = f.attrib
|
||||
width = int(attr['width'])
|
||||
height = int(attr['height'])
|
||||
vbr = int(attr['system-bitrate']) // 1000
|
||||
format_id = '%dx%d_%dk' % (width, height, vbr)
|
||||
formats.append({
|
||||
'format_id': format_id,
|
||||
'url': base_url,
|
||||
'play_path': 'mp4:' + attr['src'],
|
||||
'ext': 'flv',
|
||||
'width': width,
|
||||
'height': height,
|
||||
'vbr': vbr,
|
||||
})
|
||||
self._sort_formats(formats)
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
@ -83,4 +97,14 @@ class ThePlatformIE(InfoExtractor):
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
video_id = mobj.group('id')
|
||||
return self._get_info(video_id)
|
||||
if mobj.group('config'):
|
||||
config_url = url+ '&form=json'
|
||||
config_url = config_url.replace('swf/', 'config/')
|
||||
config_url = config_url.replace('onsite/', 'onsite/config/')
|
||||
config_json = self._download_webpage(config_url, video_id, u'Downloading config')
|
||||
config = json.loads(config_json)
|
||||
smil_url = config['releaseUrl'] + '&format=SMIL&formats=MPEG4&manifest=f4m'
|
||||
else:
|
||||
smil_url = ('http://link.theplatform.com/s/dJ5BDC/{0}/meta.smil?'
|
||||
'format=smil&mbr=true'.format(video_id))
|
||||
return self._get_info(video_id, smil_url)
|
||||
|
@ -3,7 +3,7 @@ from __future__ import unicode_literals
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
from youtube_dl.utils import ExtractorError
|
||||
from ..utils import ExtractorError
|
||||
|
||||
|
||||
class TinyPicIE(InfoExtractor):
|
||||
|
44
youtube_dl/extractor/trutube.py
Normal file
44
youtube_dl/extractor/trutube.py
Normal file
@ -0,0 +1,44 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
|
||||
|
||||
class TruTubeIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://(?:www\.)?trutube\.tv/video/(?P<id>[0-9]+)/.*'
|
||||
_TEST = {
|
||||
'url': 'http://trutube.tv/video/14880/Ramses-II-Proven-To-Be-A-Red-Headed-Caucasoid-',
|
||||
'md5': 'c5b6e301b0a2040b074746cbeaa26ca1',
|
||||
'info_dict': {
|
||||
'id': '14880',
|
||||
'ext': 'flv',
|
||||
'title': 'Ramses II - Proven To Be A Red Headed Caucasoid',
|
||||
'thumbnail': 're:^http:.*\.jpg$',
|
||||
}
|
||||
}
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
video_id = mobj.group('id')
|
||||
|
||||
webpage = self._download_webpage(url, video_id)
|
||||
video_title = self._og_search_title(webpage).strip()
|
||||
thumbnail = self._search_regex(
|
||||
r"var splash_img = '([^']+)';", webpage, 'thumbnail', fatal=False)
|
||||
|
||||
all_formats = re.finditer(
|
||||
r"var (?P<key>[a-z]+)_video_file\s*=\s*'(?P<url>[^']+)';", webpage)
|
||||
formats = [{
|
||||
'format_id': m.group('key'),
|
||||
'quality': -i,
|
||||
'url': m.group('url'),
|
||||
} for i, m in enumerate(all_formats)]
|
||||
self._sort_formats(formats)
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
'title': video_title,
|
||||
'formats': formats,
|
||||
'thumbnail': thumbnail,
|
||||
}
|
71
youtube_dl/extractor/tvigle.py
Normal file
71
youtube_dl/extractor/tvigle.py
Normal file
@ -0,0 +1,71 @@
|
||||
# encoding: utf-8
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import (
|
||||
unified_strdate,
|
||||
clean_html,
|
||||
int_or_none,
|
||||
)
|
||||
|
||||
|
||||
class TvigleIE(InfoExtractor):
|
||||
IE_NAME = 'tvigle'
|
||||
IE_DESC = 'Интернет-телевидение Tvigle.ru'
|
||||
_VALID_URL = r'http://(?:www\.)?tvigle\.ru/category/.+?video=(?P<id>\d+)'
|
||||
|
||||
_TEST = {
|
||||
'url': 'http://www.tvigle.ru/category/cinema/1608/?video=503081',
|
||||
'md5': '09afba4616666249f087efc6dcf83cb3',
|
||||
'info_dict': {
|
||||
'id': '503081',
|
||||
'ext': 'flv',
|
||||
'title': 'Брат 2 ',
|
||||
'description': 'md5:f5a42970f50648cee3d7ad740f3ae769',
|
||||
'upload_date': '20110919',
|
||||
}
|
||||
}
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
video_id = mobj.group('id')
|
||||
|
||||
video_data = self._download_xml(
|
||||
'http://www.tvigle.ru/xml/single.php?obj=%s' % video_id, video_id, 'Downloading video XML')
|
||||
|
||||
video = video_data.find('./video')
|
||||
|
||||
title = video.get('name')
|
||||
description = video.get('anons')
|
||||
if description:
|
||||
description = clean_html(description)
|
||||
thumbnail = video_data.get('img')
|
||||
upload_date = unified_strdate(video.get('date'))
|
||||
like_count = int_or_none(video.get('vtp'))
|
||||
|
||||
formats = []
|
||||
for num, (format_id, format_note) in enumerate([['low_file', 'SQ'], ['file', 'HQ'], ['hd', 'HD 720']]):
|
||||
video_url = video.get(format_id)
|
||||
if not video_url:
|
||||
continue
|
||||
formats.append({
|
||||
'url': video_url,
|
||||
'format_id': format_id,
|
||||
'format_note': format_note,
|
||||
'quality': num,
|
||||
})
|
||||
|
||||
self._sort_formats(formats)
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
'title': title,
|
||||
'description': description,
|
||||
'thumbnail': thumbnail,
|
||||
'upload_date': upload_date,
|
||||
'like_count': like_count,
|
||||
'age_limit': 18,
|
||||
'formats': formats,
|
||||
}
|
@ -4,6 +4,7 @@ import re
|
||||
import json
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import compat_urllib_request
|
||||
|
||||
|
||||
class VeohIE(InfoExtractor):
|
||||
@ -24,6 +25,13 @@ class VeohIE(InfoExtractor):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
video_id = mobj.group('id')
|
||||
webpage = self._download_webpage(url, video_id)
|
||||
age_limit = 0
|
||||
if 'class="adultwarning-container"' in webpage:
|
||||
self.report_age_confirmation()
|
||||
age_limit = 18
|
||||
request = compat_urllib_request.Request(url)
|
||||
request.add_header('Cookie', 'confirmedAdult=true')
|
||||
webpage = self._download_webpage(request, video_id)
|
||||
|
||||
m_youtube = re.search(r'http://www\.youtube\.com/v/(.*?)(\&|")', webpage)
|
||||
if m_youtube is not None:
|
||||
@ -44,4 +52,5 @@ class VeohIE(InfoExtractor):
|
||||
'thumbnail': info.get('highResImage') or info.get('medResImage'),
|
||||
'description': info['description'],
|
||||
'view_count': info['views'],
|
||||
'age_limit': age_limit,
|
||||
}
|
||||
|
@ -57,6 +57,7 @@ class VestiIE(InfoExtractor):
|
||||
# m3u8 download
|
||||
'skip_download': True,
|
||||
},
|
||||
'skip': 'Blocked outside Russia'
|
||||
},
|
||||
{
|
||||
'url': 'http://sochi2014.vesti.ru/live/play/live_id/301',
|
||||
@ -86,8 +87,7 @@ class VestiIE(InfoExtractor):
|
||||
video_id = mobj.group('id')
|
||||
else:
|
||||
mobj = re.search(
|
||||
r'<div.+?id="current-video-holder".*?>\s*<iframe src="http://player\.rutv\.ru/iframe/(?P<type>[^/]+)/id/(?P<id>\d+)[^"]*"',
|
||||
page)
|
||||
r'<iframe.+?src="http://player\.rutv\.ru/iframe/(?P<type>[^/]+)/id/(?P<id>\d+)[^"]*".*?></iframe>', page)
|
||||
|
||||
if not mobj:
|
||||
raise ExtractorError('No media found')
|
||||
|
@ -24,9 +24,10 @@ class VevoIE(InfoExtractor):
|
||||
(?P<id>[^&?#]+)'''
|
||||
_TESTS = [{
|
||||
'url': 'http://www.vevo.com/watch/hurts/somebody-to-die-for/GB1101300280',
|
||||
'file': 'GB1101300280.mp4',
|
||||
"md5": "06bea460acb744eab74a9d7dcb4bfd61",
|
||||
'info_dict': {
|
||||
'id': 'GB1101300280',
|
||||
'ext': 'mp4',
|
||||
"upload_date": "20130624",
|
||||
"uploader": "Hurts",
|
||||
"title": "Somebody to Die For",
|
||||
@ -34,6 +35,33 @@ class VevoIE(InfoExtractor):
|
||||
"width": 1920,
|
||||
"height": 1080,
|
||||
}
|
||||
}, {
|
||||
'note': 'v3 SMIL format',
|
||||
'url': 'http://www.vevo.com/watch/cassadee-pope/i-wish-i-could-break-your-heart/USUV71302923',
|
||||
'md5': '893ec0e0d4426a1d96c01de8f2bdff58',
|
||||
'info_dict': {
|
||||
'id': 'USUV71302923',
|
||||
'ext': 'mp4',
|
||||
'upload_date': '20140219',
|
||||
'uploader': 'Cassadee Pope',
|
||||
'title': 'I Wish I Could Break Your Heart',
|
||||
'duration': 226.101,
|
||||
'age_limit': 0,
|
||||
}
|
||||
}, {
|
||||
'note': 'Age-limited video',
|
||||
'url': 'https://www.vevo.com/watch/justin-timberlake/tunnel-vision-explicit/USRV81300282',
|
||||
'info_dict': {
|
||||
'id': 'USRV81300282',
|
||||
'ext': 'mp4',
|
||||
'age_limit': 18,
|
||||
'title': 'Tunnel Vision (Explicit)',
|
||||
'uploader': 'Justin Timberlake',
|
||||
'upload_date': '20130704',
|
||||
},
|
||||
'params': {
|
||||
'skip_download': 'true',
|
||||
}
|
||||
}]
|
||||
_SMIL_BASE_URL = 'http://smil.lvl3.vevo.com/'
|
||||
|
||||
@ -105,9 +133,31 @@ class VevoIE(InfoExtractor):
|
||||
video_info = self._download_json(json_url, video_id)['video']
|
||||
|
||||
formats = self._formats_from_json(video_info)
|
||||
|
||||
is_explicit = video_info.get('isExplicit')
|
||||
if is_explicit is True:
|
||||
age_limit = 18
|
||||
elif is_explicit is False:
|
||||
age_limit = 0
|
||||
else:
|
||||
age_limit = None
|
||||
|
||||
# Download SMIL
|
||||
smil_blocks = sorted((
|
||||
f for f in video_info['videoVersions']
|
||||
if f['sourceType'] == 13),
|
||||
key=lambda f: f['version'])
|
||||
|
||||
smil_url = '%s/Video/V2/VFILE/%s/%sr.smil' % (
|
||||
self._SMIL_BASE_URL, video_id, video_id.lower())
|
||||
if smil_blocks:
|
||||
smil_url_m = self._search_regex(
|
||||
r'url="([^"]+)"', smil_blocks[-1]['data'], 'SMIL URL',
|
||||
fatal=False)
|
||||
if smil_url_m is not None:
|
||||
smil_url = smil_url_m
|
||||
|
||||
try:
|
||||
smil_url = '%s/Video/V2/VFILE/%s/%sr.smil' % (
|
||||
self._SMIL_BASE_URL, video_id, video_id.lower())
|
||||
smil_xml = self._download_webpage(smil_url, video_id,
|
||||
'Downloading SMIL info')
|
||||
formats.extend(self._formats_from_smil(smil_xml))
|
||||
@ -128,4 +178,5 @@ class VevoIE(InfoExtractor):
|
||||
'upload_date': upload_date.strftime('%Y%m%d'),
|
||||
'uploader': video_info['mainArtists'][0]['artistName'],
|
||||
'duration': video_info['duration'],
|
||||
'age_limit': age_limit,
|
||||
}
|
||||
|
80
youtube_dl/extractor/videobam.py
Normal file
80
youtube_dl/extractor/videobam.py
Normal file
@ -0,0 +1,80 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
import json
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import int_or_none
|
||||
|
||||
|
||||
class VideoBamIE(InfoExtractor):
|
||||
_VALID_URL = r'http://(?:www\.)?videobam\.com/(?:videos/download/)?(?P<id>[a-zA-Z]+)'
|
||||
|
||||
_TESTS = [
|
||||
{
|
||||
'url': 'http://videobam.com/OiJQM',
|
||||
'md5': 'db471f27763a531f10416a0c58b5a1e0',
|
||||
'info_dict': {
|
||||
'id': 'OiJQM',
|
||||
'ext': 'mp4',
|
||||
'title': 'Is Alcohol Worse Than Ecstasy?',
|
||||
'description': 'md5:d25b96151515c91debc42bfbb3eb2683',
|
||||
'uploader': 'frihetsvinge',
|
||||
},
|
||||
},
|
||||
{
|
||||
'url': 'http://videobam.com/pqLvq',
|
||||
'md5': 'd9a565b5379a99126ef94e1d7f9a383e',
|
||||
'note': 'HD video',
|
||||
'info_dict': {
|
||||
'id': 'pqLvq',
|
||||
'ext': 'mp4',
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
video_id = mobj.group('id')
|
||||
|
||||
page = self._download_webpage('http://videobam.com/%s' % video_id, video_id, 'Downloading page')
|
||||
|
||||
formats = []
|
||||
|
||||
for preference, format_id in enumerate(['low', 'high']):
|
||||
mobj = re.search(r"%s: '(?P<url>[^']+)'" % format_id, page)
|
||||
if not mobj:
|
||||
continue
|
||||
formats.append({
|
||||
'url': mobj.group('url'),
|
||||
'ext': 'mp4',
|
||||
'format_id': format_id,
|
||||
'preference': preference,
|
||||
})
|
||||
|
||||
if not formats:
|
||||
player_config = json.loads(self._html_search_regex(r'var player_config = ({.+?});', page, 'player config'))
|
||||
formats = [{
|
||||
'url': item['url'],
|
||||
'ext': 'mp4',
|
||||
} for item in player_config['playlist'] if 'autoPlay' in item]
|
||||
|
||||
self._sort_formats(formats)
|
||||
|
||||
title = self._og_search_title(page, default='VideoBam', fatal=False)
|
||||
description = self._og_search_description(page, default=None)
|
||||
thumbnail = self._og_search_thumbnail(page)
|
||||
uploader = self._html_search_regex(r'Upload by ([^<]+)</a>', page, 'uploader', fatal=False, default=None)
|
||||
view_count = int_or_none(
|
||||
self._html_search_regex(r'<strong>Views:</strong> (\d+) ', page, 'view count', fatal=False))
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
'title': title,
|
||||
'description': description,
|
||||
'thumbnail': thumbnail,
|
||||
'uploader': uploader,
|
||||
'view_count': view_count,
|
||||
'formats': formats,
|
||||
'age_limit': 18,
|
||||
}
|
@ -37,9 +37,10 @@ class VimeoIE(SubtitlesInfoExtractor):
|
||||
_TESTS = [
|
||||
{
|
||||
'url': 'http://vimeo.com/56015672#at=0',
|
||||
'file': '56015672.mp4',
|
||||
'md5': '8879b6cc097e987f02484baf890129e5',
|
||||
'info_dict': {
|
||||
'id': '56015672',
|
||||
'ext': 'mp4',
|
||||
"upload_date": "20121220",
|
||||
"description": "This is a test case for youtube-dl.\nFor more information, see github.com/rg3/youtube-dl\nTest chars: \u2605 \" ' \u5e78 / \\ \u00e4 \u21ad \U0001d550",
|
||||
"uploader_id": "user7108434",
|
||||
@ -220,7 +221,9 @@ class VimeoIE(SubtitlesInfoExtractor):
|
||||
# Extract video thumbnail
|
||||
video_thumbnail = config["video"].get("thumbnail")
|
||||
if video_thumbnail is None:
|
||||
_, video_thumbnail = sorted((int(width), t_url) for (width, t_url) in config["video"]["thumbs"].items())[-1]
|
||||
video_thumbs = config["video"].get("thumbs")
|
||||
if video_thumbs and isinstance(video_thumbs, dict):
|
||||
_, video_thumbnail = sorted((int(width), t_url) for (width, t_url) in video_thumbs.items())[-1]
|
||||
|
||||
# Extract video description
|
||||
video_description = None
|
||||
|
@ -1,8 +1,10 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
import json
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import unified_strdate
|
||||
|
||||
|
||||
class VineIE(InfoExtractor):
|
||||
@ -13,31 +15,46 @@ class VineIE(InfoExtractor):
|
||||
'info_dict': {
|
||||
'id': 'b9KOOWX7HUx',
|
||||
'ext': 'mp4',
|
||||
'uploader': 'Jack Dorsey',
|
||||
'title': 'Chicken.',
|
||||
'description': 'Chicken.',
|
||||
'upload_date': '20130519',
|
||||
'uploader': 'Jack Dorsey',
|
||||
'uploader_id': '76',
|
||||
},
|
||||
}
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
|
||||
video_id = mobj.group('id')
|
||||
webpage_url = 'https://vine.co/v/' + video_id
|
||||
webpage = self._download_webpage(webpage_url, video_id)
|
||||
|
||||
self.report_extraction(video_id)
|
||||
webpage = self._download_webpage('https://vine.co/v/' + video_id, video_id)
|
||||
|
||||
video_url = self._html_search_meta('twitter:player:stream', webpage,
|
||||
'video URL')
|
||||
data = json.loads(self._html_search_regex(
|
||||
r'window\.POST_DATA = { %s: ({.+?}) }' % video_id, webpage, 'vine data'))
|
||||
|
||||
uploader = self._html_search_regex(r'<p class="username">(.*?)</p>',
|
||||
webpage, 'uploader', fatal=False, flags=re.DOTALL)
|
||||
formats = [
|
||||
{
|
||||
'url': data['videoLowURL'],
|
||||
'ext': 'mp4',
|
||||
'format_id': 'low',
|
||||
},
|
||||
{
|
||||
'url': data['videoUrl'],
|
||||
'ext': 'mp4',
|
||||
'format_id': 'standard',
|
||||
}
|
||||
]
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
'url': video_url,
|
||||
'ext': 'mp4',
|
||||
'title': self._og_search_title(webpage),
|
||||
'thumbnail': self._og_search_thumbnail(webpage),
|
||||
'uploader': uploader,
|
||||
'description': data['description'],
|
||||
'thumbnail': data['thumbnailUrl'],
|
||||
'upload_date': unified_strdate(data['created']),
|
||||
'uploader': data['username'],
|
||||
'uploader_id': data['userIdStr'],
|
||||
'like_count': data['likes']['count'],
|
||||
'comment_count': data['comments']['count'],
|
||||
'repost_count': data['reposts']['count'],
|
||||
'formats': formats,
|
||||
}
|
@ -6,6 +6,9 @@ import json
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import (
|
||||
ExtractorError,
|
||||
compat_urllib_request,
|
||||
compat_urllib_parse,
|
||||
compat_str,
|
||||
unescapeHTML,
|
||||
)
|
||||
@ -13,32 +16,96 @@ from ..utils import (
|
||||
|
||||
class VKIE(InfoExtractor):
|
||||
IE_NAME = 'vk.com'
|
||||
_VALID_URL = r'https?://vk\.com/(?:videos.*?\?.*?z=)?video(?P<id>.*?)(?:\?|%2F|$)'
|
||||
_VALID_URL = r'https?://vk\.com/(?:video_ext\.php\?.*?\boid=(?P<oid>\d+).*?\bid=(?P<id>\d+)|(?:videos.*?\?.*?z=)?video(?P<videoid>.*?)(?:\?|%2F|$))'
|
||||
_NETRC_MACHINE = 'vk'
|
||||
|
||||
_TESTS = [{
|
||||
'url': 'http://vk.com/videos-77521?z=video-77521_162222515%2Fclub77521',
|
||||
'file': '162222515.flv',
|
||||
'md5': '0deae91935c54e00003c2a00646315f0',
|
||||
'info_dict': {
|
||||
'title': 'ProtivoGunz - Хуёвая песня',
|
||||
'uploader': 'Noize MC',
|
||||
_TESTS = [
|
||||
{
|
||||
'url': 'http://vk.com/videos-77521?z=video-77521_162222515%2Fclub77521',
|
||||
'md5': '0deae91935c54e00003c2a00646315f0',
|
||||
'info_dict': {
|
||||
'id': '162222515',
|
||||
'ext': 'flv',
|
||||
'title': 'ProtivoGunz - Хуёвая песня',
|
||||
'uploader': 'Noize MC',
|
||||
'duration': 195,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
'url': 'http://vk.com/video4643923_163339118',
|
||||
'file': '163339118.mp4',
|
||||
'md5': 'f79bccb5cd182b1f43502ca5685b2b36',
|
||||
'info_dict': {
|
||||
'uploader': 'Elvira Dzhonik',
|
||||
'title': 'Dream Theater - Hollow Years Live at Budokan 720*',
|
||||
{
|
||||
'url': 'http://vk.com/video4643923_163339118',
|
||||
'md5': 'f79bccb5cd182b1f43502ca5685b2b36',
|
||||
'info_dict': {
|
||||
'id': '163339118',
|
||||
'ext': 'mp4',
|
||||
'uploader': 'Elvira Dzhonik',
|
||||
'title': 'Dream Theater - Hollow Years Live at Budokan 720*',
|
||||
'duration': 558,
|
||||
}
|
||||
},
|
||||
{
|
||||
'note': 'Embedded video',
|
||||
'url': 'http://vk.com/video_ext.php?oid=32194266&id=162925554&hash=7d8c2e0d5e05aeaa&hd=1',
|
||||
'md5': 'c7ce8f1f87bec05b3de07fdeafe21a0a',
|
||||
'info_dict': {
|
||||
'id': '162925554',
|
||||
'ext': 'mp4',
|
||||
'uploader': 'Vladimir Gavrin',
|
||||
'title': 'Lin Dan',
|
||||
'duration': 101,
|
||||
}
|
||||
},
|
||||
{
|
||||
'url': 'http://vk.com/video-8871596_164049491',
|
||||
'md5': 'a590bcaf3d543576c9bd162812387666',
|
||||
'note': 'Only available for registered users',
|
||||
'info_dict': {
|
||||
'id': '164049491',
|
||||
'ext': 'mp4',
|
||||
'uploader': 'Триллеры',
|
||||
'title': '► Бойцовский клуб / Fight Club 1999 [HD 720]\u00a0',
|
||||
'duration': 8352,
|
||||
},
|
||||
'skip': 'Requires vk account credentials',
|
||||
},
|
||||
]
|
||||
|
||||
def _login(self):
|
||||
(username, password) = self._get_login_info()
|
||||
if username is None:
|
||||
return
|
||||
|
||||
login_form = {
|
||||
'act': 'login',
|
||||
'role': 'al_frame',
|
||||
'expire': '1',
|
||||
'email': username,
|
||||
'pass': password,
|
||||
}
|
||||
}]
|
||||
|
||||
request = compat_urllib_request.Request('https://login.vk.com/?act=login',
|
||||
compat_urllib_parse.urlencode(login_form).encode('utf-8'))
|
||||
login_page = self._download_webpage(request, None, note='Logging in as %s' % username)
|
||||
|
||||
if re.search(r'onLoginFailed', login_page):
|
||||
raise ExtractorError('Unable to login, incorrect username and/or password', expected=True)
|
||||
|
||||
def _real_initialize(self):
|
||||
self._login()
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
video_id = mobj.group('id')
|
||||
video_id = mobj.group('videoid')
|
||||
|
||||
if not video_id:
|
||||
video_id = '%s_%s' % (mobj.group('oid'), mobj.group('id'))
|
||||
|
||||
info_url = 'http://vk.com/al_video.php?act=show&al=1&video=%s' % video_id
|
||||
info_page = self._download_webpage(info_url, video_id)
|
||||
|
||||
if re.search(r'<!>Please log in or <', info_page):
|
||||
raise ExtractorError('This video is only available for registered users, '
|
||||
'use --username and --password options to provide account credentials.', expected=True)
|
||||
|
||||
m_yt = re.search(r'src="(http://www.youtube.com/.*?)"', info_page)
|
||||
if m_yt is not None:
|
||||
self.to_screen(u'Youtube video detected')
|
||||
@ -60,4 +127,5 @@ class VKIE(InfoExtractor):
|
||||
'title': unescapeHTML(data['md_title']),
|
||||
'thumbnail': data.get('jpg'),
|
||||
'uploader': data.get('md_author'),
|
||||
'duration': data.get('duration')
|
||||
}
|
||||
|
@ -6,14 +6,15 @@ from .common import InfoExtractor
|
||||
|
||||
|
||||
class WimpIE(InfoExtractor):
|
||||
_VALID_URL = r'(?:http://)?(?:www\.)?wimp\.com/([^/]+)/'
|
||||
_VALID_URL = r'http://(?:www\.)?wimp\.com/([^/]+)/'
|
||||
_TEST = {
|
||||
'url': 'http://www.wimp.com/deerfence/',
|
||||
'file': 'deerfence.flv',
|
||||
'md5': '8b215e2e0168c6081a1cf84b2846a2b5',
|
||||
'url': 'http://www.wimp.com/maruexhausted/',
|
||||
'md5': 'f1acced123ecb28d9bb79f2479f2b6a1',
|
||||
'info_dict': {
|
||||
"title": "Watch Till End: Herd of deer jump over a fence.",
|
||||
"description": "These deer look as fluid as running water when they jump over this fence as a herd. This video is one that needs to be watched until the very end for the true majesty to be witnessed, but once it comes, it's sure to take your breath away.",
|
||||
'id': 'maruexhausted',
|
||||
'ext': 'flv',
|
||||
'title': 'Maru is exhausted.',
|
||||
'description': 'md5:57e099e857c0a4ea312542b684a869b8',
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ class WorldStarHipHopIE(InfoExtractor):
|
||||
webpage_src = self._download_webpage(url, video_id)
|
||||
|
||||
m_vevo_id = re.search(r'videoId=(.*?)&?',
|
||||
webpage_src)
|
||||
webpage_src)
|
||||
|
||||
if m_vevo_id is not None:
|
||||
self.to_screen(u'Vevo video detected:')
|
||||
|
@ -4,51 +4,51 @@ import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import (
|
||||
compat_urllib_parse,
|
||||
ExtractorError,
|
||||
unified_strdate,
|
||||
str_to_int,
|
||||
int_or_none,
|
||||
parse_duration,
|
||||
)
|
||||
|
||||
|
||||
class XHamsterIE(InfoExtractor):
|
||||
"""Information Extractor for xHamster"""
|
||||
_VALID_URL = r'(?:http://)?(?:www\.)?xhamster\.com/movies/(?P<id>[0-9]+)/(?P<seo>.+?)\.html(?:\?.*)?'
|
||||
_TESTS = [{
|
||||
'url': 'http://xhamster.com/movies/1509445/femaleagent_shy_beauty_takes_the_bait.html',
|
||||
'file': '1509445.mp4',
|
||||
'md5': '8281348b8d3c53d39fffb377d24eac4e',
|
||||
'info_dict': {
|
||||
"upload_date": "20121014",
|
||||
"uploader_id": "Ruseful2011",
|
||||
"title": "FemaleAgent Shy beauty takes the bait",
|
||||
"age_limit": 18,
|
||||
_VALID_URL = r'http://(?:www\.)?xhamster\.com/movies/(?P<id>[0-9]+)/(?P<seo>.+?)\.html(?:\?.*)?'
|
||||
_TESTS = [
|
||||
{
|
||||
'url': 'http://xhamster.com/movies/1509445/femaleagent_shy_beauty_takes_the_bait.html',
|
||||
'md5': '8281348b8d3c53d39fffb377d24eac4e',
|
||||
'info_dict': {
|
||||
'id': '1509445',
|
||||
'ext': 'mp4',
|
||||
'title': 'FemaleAgent Shy beauty takes the bait',
|
||||
'upload_date': '20121014',
|
||||
'uploader_id': 'Ruseful2011',
|
||||
'duration': 893,
|
||||
'age_limit': 18,
|
||||
}
|
||||
},
|
||||
{
|
||||
'url': 'http://xhamster.com/movies/2221348/britney_spears_sexy_booty.html?hd',
|
||||
'md5': '4cbd8d56708ecb4fb4124c23e4acb81a',
|
||||
'info_dict': {
|
||||
'id': '2221348',
|
||||
'ext': 'mp4',
|
||||
'title': 'Britney Spears Sexy Booty',
|
||||
'upload_date': '20130914',
|
||||
'uploader_id': 'jojo747400',
|
||||
'duration': 200,
|
||||
'age_limit': 18,
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
'url': 'http://xhamster.com/movies/2221348/britney_spears_sexy_booty.html?hd',
|
||||
'file': '2221348.flv',
|
||||
'md5': 'e767b9475de189320f691f49c679c4c7',
|
||||
'info_dict': {
|
||||
"upload_date": "20130914",
|
||||
"uploader_id": "jojo747400",
|
||||
"title": "Britney Spears Sexy Booty",
|
||||
"age_limit": 18,
|
||||
}
|
||||
}]
|
||||
]
|
||||
|
||||
def _real_extract(self,url):
|
||||
def extract_video_url(webpage):
|
||||
mobj = re.search(r'\'srv\': \'(?P<server>[^\']*)\',\s*\'file\': \'(?P<file>[^\']+)\',', webpage)
|
||||
if mobj is None:
|
||||
raise ExtractorError('Unable to extract media URL')
|
||||
if len(mobj.group('server')) == 0:
|
||||
return compat_urllib_parse.unquote(mobj.group('file'))
|
||||
else:
|
||||
return mobj.group('server')+'/key='+mobj.group('file')
|
||||
|
||||
def extract_mp4_video_url(webpage):
|
||||
mp4 = re.search(r'<a href=\"(.+?)\" class=\"mp4Play\"',webpage)
|
||||
mp4 = re.search(r'<video\s+.*?file="([^"]+)".*?>', webpage)
|
||||
if mp4 is None:
|
||||
return None
|
||||
raise ExtractorError('Unable to extract media URL')
|
||||
else:
|
||||
return mp4.group(1)
|
||||
|
||||
@ -62,50 +62,49 @@ class XHamsterIE(InfoExtractor):
|
||||
mrss_url = 'http://xhamster.com/movies/%s/%s.html' % (video_id, seo)
|
||||
webpage = self._download_webpage(mrss_url, video_id)
|
||||
|
||||
video_title = self._html_search_regex(
|
||||
r'<title>(?P<title>.+?) - xHamster\.com</title>', webpage, 'title')
|
||||
title = self._html_search_regex(r'<title>(?P<title>.+?) - xHamster\.com</title>', webpage, 'title')
|
||||
|
||||
# Only a few videos have an description
|
||||
mobj = re.search(r'<span>Description: </span>([^<]+)', webpage)
|
||||
video_description = mobj.group(1) if mobj else None
|
||||
description = mobj.group(1) if mobj else None
|
||||
|
||||
mobj = re.search(r'hint=\'(?P<upload_date_Y>[0-9]{4})-(?P<upload_date_m>[0-9]{2})-(?P<upload_date_d>[0-9]{2}) [0-9]{2}:[0-9]{2}:[0-9]{2} [A-Z]{3,4}\'', webpage)
|
||||
if mobj:
|
||||
video_upload_date = mobj.group('upload_date_Y')+mobj.group('upload_date_m')+mobj.group('upload_date_d')
|
||||
else:
|
||||
video_upload_date = None
|
||||
self._downloader.report_warning('Unable to extract upload date')
|
||||
upload_date = self._html_search_regex(r'hint=\'(\d{4}-\d{2}-\d{2}) \d{2}:\d{2}:\d{2} [A-Z]{3,4}\'',
|
||||
webpage, 'upload date', fatal=False)
|
||||
if upload_date:
|
||||
upload_date = unified_strdate(upload_date)
|
||||
|
||||
video_uploader_id = self._html_search_regex(
|
||||
r'<a href=\'/user/[^>]+>(?P<uploader_id>[^<]+)',
|
||||
uploader_id = self._html_search_regex(r'<a href=\'/user/[^>]+>(?P<uploader_id>[^<]+)',
|
||||
webpage, 'uploader id', default='anonymous')
|
||||
|
||||
video_thumbnail = self._search_regex(
|
||||
r'\'image\':\'(?P<thumbnail>[^\']+)\'',
|
||||
webpage, 'thumbnail', fatal=False)
|
||||
thumbnail = self._html_search_regex(r'<video\s+.*?poster="([^"]+)".*?>', webpage, 'thumbnail', fatal=False)
|
||||
|
||||
duration = parse_duration(self._html_search_regex(r'<span>Runtime:</span> (\d+:\d+)</div>',
|
||||
webpage, 'duration', fatal=False))
|
||||
|
||||
view_count = self._html_search_regex(r'<span>Views:</span> ([^<]+)</div>', webpage, 'view count', fatal=False)
|
||||
if view_count:
|
||||
view_count = str_to_int(view_count)
|
||||
|
||||
mobj = re.search(r"hint='(?P<likecount>\d+) Likes / (?P<dislikecount>\d+) Dislikes'", webpage)
|
||||
(like_count, dislike_count) = (mobj.group('likecount'), mobj.group('dislikecount')) if mobj else (None, None)
|
||||
|
||||
mobj = re.search(r'</label>Comments \((?P<commentcount>\d+)\)</div>', webpage)
|
||||
comment_count = mobj.group('commentcount') if mobj else 0
|
||||
|
||||
age_limit = self._rta_search(webpage)
|
||||
|
||||
hd = is_hd(webpage)
|
||||
|
||||
video_url = extract_video_url(webpage)
|
||||
formats = [{
|
||||
'url': video_url,
|
||||
'format_id': 'hd' if hd else 'sd',
|
||||
'preference': 0,
|
||||
'preference': 1,
|
||||
}]
|
||||
|
||||
video_mp4_url = extract_mp4_video_url(webpage)
|
||||
if video_mp4_url is not None:
|
||||
formats.append({
|
||||
'url': video_mp4_url,
|
||||
'ext': 'mp4',
|
||||
'format_id': 'mp4-hd' if hd else 'mp4-sd',
|
||||
'preference': 1,
|
||||
})
|
||||
|
||||
if not hd:
|
||||
webpage = self._download_webpage(
|
||||
mrss_url + '?hd', video_id, note='Downloading HD webpage')
|
||||
mrss_url = self._search_regex(r'<link rel="canonical" href="([^"]+)', webpage, 'mrss_url')
|
||||
webpage = self._download_webpage(mrss_url + '?hd', video_id, note='Downloading HD webpage')
|
||||
if is_hd(webpage):
|
||||
video_url = extract_video_url(webpage)
|
||||
formats.append({
|
||||
@ -118,11 +117,16 @@ class XHamsterIE(InfoExtractor):
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
'title': video_title,
|
||||
'formats': formats,
|
||||
'description': video_description,
|
||||
'upload_date': video_upload_date,
|
||||
'uploader_id': video_uploader_id,
|
||||
'thumbnail': video_thumbnail,
|
||||
'title': title,
|
||||
'description': description,
|
||||
'upload_date': upload_date,
|
||||
'uploader_id': uploader_id,
|
||||
'thumbnail': thumbnail,
|
||||
'duration': duration,
|
||||
'view_count': view_count,
|
||||
'like_count': int_or_none(like_count),
|
||||
'dislike_count': int_or_none(dislike_count),
|
||||
'comment_count': int_or_none(comment_count),
|
||||
'age_limit': age_limit,
|
||||
'formats': formats,
|
||||
}
|
||||
|
@ -29,7 +29,6 @@ from ..utils import (
|
||||
ExtractorError,
|
||||
int_or_none,
|
||||
PagedList,
|
||||
RegexNotFoundError,
|
||||
unescapeHTML,
|
||||
unified_strdate,
|
||||
orderedSet,
|
||||
@ -138,13 +137,14 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):
|
||||
(?:(?:(?:(?:\w+\.)?[yY][oO][uU][tT][uU][bB][eE](?:-nocookie)?\.com/|
|
||||
(?:www\.)?deturl\.com/www\.youtube\.com/|
|
||||
(?:www\.)?pwnyoutube\.com/|
|
||||
(?:www\.)?yourepeat\.com/|
|
||||
tube\.majestyc\.net/|
|
||||
youtube\.googleapis\.com/) # the various hostnames, with wildcard subdomains
|
||||
(?:.*?\#/)? # handle anchor (#/) redirect urls
|
||||
(?: # the various things that can precede the ID:
|
||||
(?:(?:v|embed|e)/) # v/ or embed/ or e/
|
||||
|(?: # or the v= param in all its forms
|
||||
(?:(?:watch|movie)(?:_popup)?(?:\.php)?)? # preceding watch(_popup|.php) or nothing (like /?v=xxxx)
|
||||
(?:(?:watch|movie)(?:_popup)?(?:\.php)?/?)? # preceding watch(_popup|.php) or nothing (like /?v=xxxx)
|
||||
(?:\?|\#!?) # the params delimiter ? or # or #!
|
||||
(?:.*?&)? # any other preceding param (like /?s=tuff&v=xxxx)
|
||||
v=
|
||||
@ -199,9 +199,9 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):
|
||||
'135': {'ext': 'mp4', 'height': 480, 'resolution': '480p', 'format_note': 'DASH video', 'preference': -40},
|
||||
'136': {'ext': 'mp4', 'height': 720, 'resolution': '720p', 'format_note': 'DASH video', 'preference': -40},
|
||||
'137': {'ext': 'mp4', 'height': 1080, 'resolution': '1080p', 'format_note': 'DASH video', 'preference': -40},
|
||||
'138': {'ext': 'mp4', 'height': 1081, 'resolution': '>1080p', 'format_note': 'DASH video', 'preference': -40},
|
||||
'138': {'ext': 'mp4', 'height': 2160, 'resolution': '2160p', 'format_note': 'DASH video', 'preference': -40},
|
||||
'160': {'ext': 'mp4', 'height': 192, 'resolution': '192p', 'format_note': 'DASH video', 'preference': -40},
|
||||
'264': {'ext': 'mp4', 'height': 1080, 'resolution': '1080p', 'format_note': 'DASH video', 'preference': -40},
|
||||
'264': {'ext': 'mp4', 'height': 1440, 'resolution': '1440p', 'format_note': 'DASH video', 'preference': -40},
|
||||
|
||||
# Dash mp4 audio
|
||||
'139': {'ext': 'm4a', 'format_note': 'DASH audio', 'vcodec': 'none', 'abr': 48, 'preference': -50},
|
||||
@ -296,6 +296,23 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):
|
||||
u"format": "141",
|
||||
},
|
||||
},
|
||||
# DASH manifest with encrypted signature
|
||||
{
|
||||
u'url': u'https://www.youtube.com/watch?v=IB3lcPjvWLA',
|
||||
u'info_dict': {
|
||||
u'id': u'IB3lcPjvWLA',
|
||||
u'ext': u'm4a',
|
||||
u'title': u'Afrojack - The Spark ft. Spree Wilson',
|
||||
u'description': u'md5:3199ed45ee8836572865580804d7ac0f',
|
||||
u'uploader': u'AfrojackVEVO',
|
||||
u'uploader_id': u'AfrojackVEVO',
|
||||
u'upload_date': u'20131011',
|
||||
},
|
||||
u"params": {
|
||||
u'youtube_include_dash_manifest': True,
|
||||
u'format': '141',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@ -1271,8 +1288,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):
|
||||
mobj = re.search(r';ytplayer.config = ({.*?});', video_webpage)
|
||||
if not mobj:
|
||||
raise ValueError('Could not find vevo ID')
|
||||
info = json.loads(mobj.group(1))
|
||||
args = info['args']
|
||||
ytplayer_config = json.loads(mobj.group(1))
|
||||
args = ytplayer_config['args']
|
||||
# Easy way to know if the 's' value is in url_encoded_fmt_stream_map
|
||||
# this signatures are encrypted
|
||||
if 'url_encoded_fmt_stream_map' not in args:
|
||||
@ -1365,12 +1382,24 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):
|
||||
raise ExtractorError(u'no conn, hlsvp or url_encoded_fmt_stream_map information found in video info')
|
||||
|
||||
# Look for the DASH manifest
|
||||
dash_manifest_url_lst = video_info.get('dashmpd')
|
||||
if (dash_manifest_url_lst and dash_manifest_url_lst[0] and
|
||||
self._downloader.params.get('youtube_include_dash_manifest', False)):
|
||||
if (self._downloader.params.get('youtube_include_dash_manifest', False)):
|
||||
try:
|
||||
# The DASH manifest used needs to be the one from the original video_webpage.
|
||||
# The one found in get_video_info seems to be using different signatures.
|
||||
# However, in the case of an age restriction there won't be any embedded dashmpd in the video_webpage.
|
||||
# Luckily, it seems, this case uses some kind of default signature (len == 86), so the
|
||||
# combination of get_video_info and the _static_decrypt_signature() decryption fallback will work here.
|
||||
if age_gate:
|
||||
dash_manifest_url = video_info.get('dashmpd')[0]
|
||||
else:
|
||||
dash_manifest_url = ytplayer_config['args']['dashmpd']
|
||||
def decrypt_sig(mobj):
|
||||
s = mobj.group(1)
|
||||
dec_s = self._decrypt_signature(s, video_id, player_url, age_gate)
|
||||
return '/signature/%s' % dec_s
|
||||
dash_manifest_url = re.sub(r'/s/([\w\.]+)', decrypt_sig, dash_manifest_url)
|
||||
dash_doc = self._download_xml(
|
||||
dash_manifest_url_lst[0], video_id,
|
||||
dash_manifest_url, video_id,
|
||||
note=u'Downloading DASH manifest',
|
||||
errnote=u'Could not download DASH manifest')
|
||||
for r in dash_doc.findall(u'.//{urn:mpeg:DASH:schema:MPD:2011}Representation'):
|
||||
@ -1442,9 +1471,9 @@ class YoutubePlaylistIE(YoutubeBaseInfoExtractor):
|
||||
|
|
||||
((?:PL|EC|UU|FL|RD)[0-9A-Za-z-_]{10,})
|
||||
)"""
|
||||
_TEMPLATE_URL = 'https://www.youtube.com/playlist?list=%s&page=%s'
|
||||
_TEMPLATE_URL = 'https://www.youtube.com/playlist?list=%s'
|
||||
_MORE_PAGES_INDICATOR = r'data-link-type="next"'
|
||||
_VIDEO_RE = r'href="/watch\?v=(?P<id>[0-9A-Za-z_-]{11})&[^"]*?index=(?P<index>\d+)'
|
||||
_VIDEO_RE = r'href="\s*/watch\?v=(?P<id>[0-9A-Za-z_-]{11})&[^"]*?index=(?P<index>\d+)'
|
||||
IE_NAME = u'youtube:playlist'
|
||||
|
||||
def _real_initialize(self):
|
||||
@ -1459,11 +1488,15 @@ class YoutubePlaylistIE(YoutubeBaseInfoExtractor):
|
||||
# the id of the playlist is just 'RD' + video_id
|
||||
url = 'https://youtube.com/watch?v=%s&list=%s' % (playlist_id[-11:], playlist_id)
|
||||
webpage = self._download_webpage(url, playlist_id, u'Downloading Youtube mix')
|
||||
title_span = (get_element_by_attribute('class', 'title long-title', webpage) or
|
||||
get_element_by_attribute('class', 'title ', webpage))
|
||||
search_title = lambda class_name: get_element_by_attribute('class', class_name, webpage)
|
||||
title_span = (search_title('playlist-title') or
|
||||
search_title('title long-title') or search_title('title'))
|
||||
title = clean_html(title_span)
|
||||
video_re = r'data-index="\d+".*?href="/watch\?v=([0-9A-Za-z_-]{11})&[^"]*?list=%s' % re.escape(playlist_id)
|
||||
ids = orderedSet(re.findall(video_re, webpage))
|
||||
video_re = r'''(?x)data-video-username="(.*?)".*?
|
||||
href="/watch\?v=([0-9A-Za-z_-]{11})&[^"]*?list=%s''' % re.escape(playlist_id)
|
||||
matches = orderedSet(re.findall(video_re, webpage, flags=re.DOTALL))
|
||||
# Some of the videos may have been deleted, their username field is empty
|
||||
ids = [video_id for (username, video_id) in matches if username]
|
||||
url_results = self._ids_to_results(ids)
|
||||
|
||||
return self.playlist_result(url_results, playlist_id, title)
|
||||
@ -1492,29 +1525,31 @@ class YoutubePlaylistIE(YoutubeBaseInfoExtractor):
|
||||
raise ExtractorError(u'For downloading YouTube.com top lists, use '
|
||||
u'the "yttoplist" keyword, for example "youtube-dl \'yttoplist:music:Top Tracks\'"', expected=True)
|
||||
|
||||
url = self._TEMPLATE_URL % playlist_id
|
||||
page = self._download_webpage(url, playlist_id)
|
||||
more_widget_html = content_html = page
|
||||
|
||||
# Extract the video ids from the playlist pages
|
||||
ids = []
|
||||
|
||||
for page_num in itertools.count(1):
|
||||
url = self._TEMPLATE_URL % (playlist_id, page_num)
|
||||
page = self._download_webpage(url, playlist_id, u'Downloading page #%s' % page_num)
|
||||
matches = re.finditer(self._VIDEO_RE, page)
|
||||
matches = re.finditer(self._VIDEO_RE, content_html)
|
||||
# We remove the duplicates and the link with index 0
|
||||
# (it's not the first video of the playlist)
|
||||
new_ids = orderedSet(m.group('id') for m in matches if m.group('index') != '0')
|
||||
ids.extend(new_ids)
|
||||
|
||||
if re.search(self._MORE_PAGES_INDICATOR, page) is None:
|
||||
mobj = re.search(r'data-uix-load-more-href="/?(?P<more>[^"]+)"', more_widget_html)
|
||||
if not mobj:
|
||||
break
|
||||
|
||||
try:
|
||||
playlist_title = self._og_search_title(page)
|
||||
except RegexNotFoundError:
|
||||
self.report_warning(
|
||||
u'Playlist page is missing OpenGraph title, falling back ...',
|
||||
playlist_id)
|
||||
playlist_title = self._html_search_regex(
|
||||
r'<h1 class="pl-header-title">(.*?)</h1>', page, u'title')
|
||||
more = self._download_json(
|
||||
'https://youtube.com/%s' % mobj.group('more'), playlist_id, 'Downloading page #%s' % page_num)
|
||||
content_html = more['content_html']
|
||||
more_widget_html = more['load_more_widget_html']
|
||||
|
||||
playlist_title = self._html_search_regex(
|
||||
r'<h1 class="pl-header-title">\s*(.*?)\s*</h1>', page, u'title')
|
||||
|
||||
url_results = self._ids_to_results(ids)
|
||||
return self.playlist_result(url_results, playlist_id, playlist_title)
|
||||
@ -1610,7 +1645,7 @@ class YoutubeChannelIE(InfoExtractor):
|
||||
|
||||
class YoutubeUserIE(InfoExtractor):
|
||||
IE_DESC = u'YouTube.com user videos (URL or "ytuser" keyword)'
|
||||
_VALID_URL = r'(?:(?:(?:https?://)?(?:\w+\.)?youtube\.com/(?:user/)?(?!(?:attribution_link|watch)(?:$|[^a-z_A-Z0-9-])))|ytuser:)(?!feed/)([A-Za-z0-9_-]+)'
|
||||
_VALID_URL = r'(?:(?:(?:https?://)?(?:\w+\.)?youtube\.com/(?:user/)?(?!(?:attribution_link|watch|results)(?:$|[^a-z_A-Z0-9-])))|ytuser:)(?!feed/)([A-Za-z0-9_-]+)'
|
||||
_TEMPLATE_URL = 'https://gdata.youtube.com/feeds/api/users/%s'
|
||||
_GDATA_PAGE_SIZE = 50
|
||||
_GDATA_URL = 'https://gdata.youtube.com/feeds/api/users/%s/uploads?max-results=%d&start-index=%d&alt=json'
|
||||
@ -1694,7 +1729,8 @@ class YoutubeSearchIE(SearchInfoExtractor):
|
||||
api_response = data['data']
|
||||
|
||||
if 'items' not in api_response:
|
||||
raise ExtractorError(u'[youtube] No video results')
|
||||
raise ExtractorError(
|
||||
u'[youtube] No video results', expected=True)
|
||||
|
||||
new_ids = list(video['id'] for video in api_response['items'])
|
||||
video_ids += new_ids
|
||||
@ -1708,12 +1744,50 @@ class YoutubeSearchIE(SearchInfoExtractor):
|
||||
for video_id in video_ids]
|
||||
return self.playlist_result(videos, query)
|
||||
|
||||
|
||||
class YoutubeSearchDateIE(YoutubeSearchIE):
|
||||
IE_NAME = YoutubeSearchIE.IE_NAME + ':date'
|
||||
_API_URL = 'https://gdata.youtube.com/feeds/api/videos?q=%s&start-index=%i&max-results=50&v=2&alt=jsonc&orderby=published'
|
||||
_SEARCH_KEY = 'ytsearchdate'
|
||||
IE_DESC = u'YouTube.com searches, newest videos first'
|
||||
|
||||
|
||||
class YoutubeSearchURLIE(InfoExtractor):
|
||||
IE_DESC = u'YouTube.com search URLs'
|
||||
IE_NAME = u'youtube:search_url'
|
||||
_VALID_URL = r'https?://(?:www\.)?youtube\.com/results\?(.*?&)?search_query=(?P<query>[^&]+)(?:[&]|$)'
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
query = compat_urllib_parse.unquote_plus(mobj.group('query'))
|
||||
|
||||
webpage = self._download_webpage(url, query)
|
||||
result_code = self._search_regex(
|
||||
r'(?s)<ol id="search-results"(.*?)</ol>', webpage, u'result HTML')
|
||||
|
||||
part_codes = re.findall(
|
||||
r'(?s)<h3 class="yt-lockup-title">(.*?)</h3>', result_code)
|
||||
entries = []
|
||||
for part_code in part_codes:
|
||||
part_title = self._html_search_regex(
|
||||
r'(?s)title="([^"]+)"', part_code, 'item title', fatal=False)
|
||||
part_url_snippet = self._html_search_regex(
|
||||
r'(?s)href="([^"]+)"', part_code, 'item URL')
|
||||
part_url = compat_urlparse.urljoin(
|
||||
'https://www.youtube.com/', part_url_snippet)
|
||||
entries.append({
|
||||
'_type': 'url',
|
||||
'url': part_url,
|
||||
'title': part_title,
|
||||
})
|
||||
|
||||
return {
|
||||
'_type': 'playlist',
|
||||
'entries': entries,
|
||||
'title': query,
|
||||
}
|
||||
|
||||
|
||||
class YoutubeShowIE(InfoExtractor):
|
||||
IE_DESC = u'YouTube.com (multi-season) shows'
|
||||
_VALID_URL = r'https?://www\.youtube\.com/show/(.*)'
|
||||
@ -1814,7 +1888,7 @@ class YoutubeTruncatedURLIE(InfoExtractor):
|
||||
IE_NAME = 'youtube:truncated_url'
|
||||
IE_DESC = False # Do not list
|
||||
_VALID_URL = r'''(?x)
|
||||
(?:https?://)?[^/]+/watch\?feature=[a-z_]+$|
|
||||
(?:https?://)?[^/]+/watch\?(?:feature=[a-z_]+)?$|
|
||||
(?:https?://)?(?:www\.)?youtube\.com/attribution_link\?a=[^&]+$
|
||||
'''
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
# coding: utf-8
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
|
||||
@ -13,52 +14,42 @@ class ZDFIE(InfoExtractor):
|
||||
_VALID_URL = r'^https?://www\.zdf\.de/ZDFmediathek(?P<hash>#)?/(.*beitrag/(?:video/)?)(?P<video_id>[0-9]+)(?:/[^/?]+)?(?:\?.*)?'
|
||||
|
||||
_TEST = {
|
||||
u"url": u"http://www.zdf.de/ZDFmediathek/beitrag/video/2037704/ZDFspezial---Ende-des-Machtpokers--?bc=sts;stt",
|
||||
u"file": u"2037704.webm",
|
||||
u"info_dict": {
|
||||
u"upload_date": u"20131127",
|
||||
u"description": u"Union und SPD haben sich auf einen Koalitionsvertrag geeinigt. Aber was bedeutet das für die Bürger? Sehen Sie hierzu das ZDFspezial \"Ende des Machtpokers - Große Koalition für Deutschland\".",
|
||||
u"uploader": u"spezial",
|
||||
u"title": u"ZDFspezial - Ende des Machtpokers"
|
||||
'url': 'http://www.zdf.de/ZDFmediathek/beitrag/video/2037704/ZDFspezial---Ende-des-Machtpokers--?bc=sts;stt',
|
||||
'info_dict': {
|
||||
'id': '2037704',
|
||||
'ext': 'webm',
|
||||
'title': 'ZDFspezial - Ende des Machtpokers',
|
||||
'description': 'Union und SPD haben sich auf einen Koalitionsvertrag geeinigt. Aber was bedeutet das für die Bürger? Sehen Sie hierzu das ZDFspezial "Ende des Machtpokers - Große Koalition für Deutschland".',
|
||||
'duration': 1022,
|
||||
'uploader': 'spezial',
|
||||
'uploader_id': '225948',
|
||||
'upload_date': '20131127',
|
||||
},
|
||||
u"skip": u"Videos on ZDF.de are depublicised in short order",
|
||||
'skip': 'Videos on ZDF.de are depublicised in short order',
|
||||
}
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
video_id = mobj.group('video_id')
|
||||
|
||||
xml_url = u'http://www.zdf.de/ZDFmediathek/xmlservice/web/beitragsDetails?ak=web&id=%s' % video_id
|
||||
xml_url = 'http://www.zdf.de/ZDFmediathek/xmlservice/web/beitragsDetails?ak=web&id=%s' % video_id
|
||||
doc = self._download_xml(
|
||||
xml_url, video_id,
|
||||
note=u'Downloading video info',
|
||||
errnote=u'Failed to download video info')
|
||||
note='Downloading video info',
|
||||
errnote='Failed to download video info')
|
||||
|
||||
title = doc.find('.//information/title').text
|
||||
description = doc.find('.//information/detail').text
|
||||
duration = int(doc.find('.//details/lengthSec').text)
|
||||
uploader_node = doc.find('.//details/originChannelTitle')
|
||||
uploader = None if uploader_node is None else uploader_node.text
|
||||
duration_str = doc.find('.//details/length').text
|
||||
duration_m = re.match(r'''(?x)^
|
||||
(?P<hours>[0-9]{2})
|
||||
:(?P<minutes>[0-9]{2})
|
||||
:(?P<seconds>[0-9]{2})
|
||||
(?:\.(?P<ms>[0-9]+)?)
|
||||
''', duration_str)
|
||||
duration = (
|
||||
(
|
||||
(int(duration_m.group('hours')) * 60 * 60) +
|
||||
(int(duration_m.group('minutes')) * 60) +
|
||||
int(duration_m.group('seconds'))
|
||||
)
|
||||
if duration_m
|
||||
else None
|
||||
)
|
||||
uploader_id_node = doc.find('.//details/originChannelId')
|
||||
uploader_id = None if uploader_id_node is None else uploader_id_node.text
|
||||
upload_date = unified_strdate(doc.find('.//details/airtime').text)
|
||||
|
||||
def xml_to_format(fnode):
|
||||
video_url = fnode.find('url').text
|
||||
is_available = u'http://www.metafilegenerator' not in video_url
|
||||
is_available = 'http://www.metafilegenerator' not in video_url
|
||||
|
||||
format_id = fnode.attrib['basetype']
|
||||
format_m = re.match(r'''(?x)
|
||||
@ -71,22 +62,28 @@ class ZDFIE(InfoExtractor):
|
||||
|
||||
quality = fnode.find('./quality').text
|
||||
abr = int(fnode.find('./audioBitrate').text) // 1000
|
||||
vbr = int(fnode.find('./videoBitrate').text) // 1000
|
||||
vbr_node = fnode.find('./videoBitrate')
|
||||
vbr = None if vbr_node is None else int(vbr_node.text) // 1000
|
||||
|
||||
format_note = u''
|
||||
width_node = fnode.find('./width')
|
||||
width = None if width_node is None else int_or_none(width_node.text)
|
||||
height_node = fnode.find('./height')
|
||||
height = None if height_node is None else int_or_none(height_node.text)
|
||||
|
||||
format_note = ''
|
||||
if not format_note:
|
||||
format_note = None
|
||||
|
||||
return {
|
||||
'format_id': format_id + u'-' + quality,
|
||||
'format_id': format_id + '-' + quality,
|
||||
'url': video_url,
|
||||
'ext': ext,
|
||||
'acodec': format_m.group('acodec'),
|
||||
'vcodec': format_m.group('vcodec'),
|
||||
'abr': abr,
|
||||
'vbr': vbr,
|
||||
'width': int_or_none(fnode.find('./width').text),
|
||||
'height': int_or_none(fnode.find('./height').text),
|
||||
'width': width,
|
||||
'height': height,
|
||||
'filesize': int_or_none(fnode.find('./filesize').text),
|
||||
'format_note': format_note,
|
||||
'protocol': proto,
|
||||
@ -103,9 +100,10 @@ class ZDFIE(InfoExtractor):
|
||||
return {
|
||||
'id': video_id,
|
||||
'title': title,
|
||||
'formats': formats,
|
||||
'description': description,
|
||||
'uploader': uploader,
|
||||
'duration': duration,
|
||||
'uploader': uploader,
|
||||
'uploader_id': uploader_id,
|
||||
'upload_date': upload_date,
|
||||
'formats': formats,
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import contextlib
|
||||
import ctypes
|
||||
import datetime
|
||||
import email.utils
|
||||
@ -17,6 +18,7 @@ import platform
|
||||
import re
|
||||
import ssl
|
||||
import socket
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import traceback
|
||||
@ -173,6 +175,11 @@ try:
|
||||
except NameError:
|
||||
compat_chr = chr
|
||||
|
||||
try:
|
||||
from xml.etree.ElementTree import ParseError as compat_xml_parse_error
|
||||
except ImportError: # Python 2.6
|
||||
from xml.parsers.expat import ExpatError as compat_xml_parse_error
|
||||
|
||||
def compat_ord(c):
|
||||
if type(c) is int: return c
|
||||
else: return ord(c)
|
||||
@ -761,9 +768,11 @@ def unified_strdate(date_str):
|
||||
date_str = re.sub(r' ?(\+|-)[0-9]{2}:?[0-9]{2}$', '', date_str)
|
||||
format_expressions = [
|
||||
'%d %B %Y',
|
||||
'%d %b %Y',
|
||||
'%B %d %Y',
|
||||
'%b %d %Y',
|
||||
'%Y-%m-%d',
|
||||
'%d.%m.%Y',
|
||||
'%d/%m/%Y',
|
||||
'%Y/%m/%d %H:%M:%S',
|
||||
'%Y-%m-%d %H:%M:%S',
|
||||
@ -772,6 +781,7 @@ def unified_strdate(date_str):
|
||||
'%Y-%m-%dT%H:%M:%S.%fZ',
|
||||
'%Y-%m-%dT%H:%M:%S.%f0Z',
|
||||
'%Y-%m-%dT%H:%M:%S',
|
||||
'%Y-%m-%dT%H:%M:%S.%f',
|
||||
'%Y-%m-%dT%H:%M',
|
||||
]
|
||||
for expression in format_expressions:
|
||||
@ -1143,7 +1153,7 @@ def parse_duration(s):
|
||||
return None
|
||||
|
||||
m = re.match(
|
||||
r'(?:(?:(?P<hours>[0-9]+):)?(?P<mins>[0-9]+):)?(?P<secs>[0-9]+)$', s)
|
||||
r'(?:(?:(?P<hours>[0-9]+)[:h])?(?P<mins>[0-9]+)[:m])?(?P<secs>[0-9]+)s?$', s)
|
||||
if not m:
|
||||
return None
|
||||
res = int(m.group('secs'))
|
||||
@ -1220,3 +1230,36 @@ def uppercase_escape(s):
|
||||
return re.sub(
|
||||
r'\\U([0-9a-fA-F]{8})',
|
||||
lambda m: compat_chr(int(m.group(1), base=16)), s)
|
||||
|
||||
try:
|
||||
struct.pack(u'!I', 0)
|
||||
except TypeError:
|
||||
# In Python 2.6 (and some 2.7 versions), struct requires a bytes argument
|
||||
def struct_pack(spec, *args):
|
||||
if isinstance(spec, compat_str):
|
||||
spec = spec.encode('ascii')
|
||||
return struct.pack(spec, *args)
|
||||
|
||||
def struct_unpack(spec, *args):
|
||||
if isinstance(spec, compat_str):
|
||||
spec = spec.encode('ascii')
|
||||
return struct.unpack(spec, *args)
|
||||
else:
|
||||
struct_pack = struct.pack
|
||||
struct_unpack = struct.unpack
|
||||
|
||||
|
||||
def read_batch_urls(batch_fd):
|
||||
def fixup(url):
|
||||
if not isinstance(url, compat_str):
|
||||
url = url.decode('utf-8', 'replace')
|
||||
BOM_UTF8 = u'\xef\xbb\xbf'
|
||||
if url.startswith(BOM_UTF8):
|
||||
url = url[len(BOM_UTF8):]
|
||||
url = url.strip()
|
||||
if url.startswith(('#', ';', ']')):
|
||||
return False
|
||||
return url
|
||||
|
||||
with contextlib.closing(batch_fd) as fd:
|
||||
return [url for url in map(fixup, fd) if url]
|
||||
|
@ -1,2 +1,2 @@
|
||||
|
||||
__version__ = '2014.02.13'
|
||||
__version__ = '2014.03.04'
|
||||
|
Reference in New Issue
Block a user