Compare commits
250 Commits
2014.06.02
...
2014.07.15
Author | SHA1 | Date | |
---|---|---|---|
|
ee90ddab94 | ||
|
ad25aee245 | ||
|
bd1f325b42 | ||
|
00a82ea805 | ||
|
816930c485 | ||
|
76233cda34 | ||
|
9dcea39985 | ||
|
10d00a756a | ||
|
eb50741129 | ||
|
3804b01276 | ||
|
b1298d8e06 | ||
|
6a46dc8db7 | ||
|
36cb99f958 | ||
|
81650f95e2 | ||
|
34dbcb8505 | ||
|
c993c829e2 | ||
|
0d90e0f067 | ||
|
678f58de4b | ||
|
c961a0e63e | ||
|
aaefb347c0 | ||
|
09018e19a5 | ||
|
345e37831c | ||
|
00ac799b68 | ||
|
133af9385b | ||
|
40c696e5c6 | ||
|
d6d5028922 | ||
|
38ad119f97 | ||
|
4e415288d7 | ||
|
fada438acf | ||
|
1df0ae2170 | ||
|
d96b9d40f0 | ||
|
fa19dfccf9 | ||
|
cdc22cb886 | ||
|
04c77a54b0 | ||
|
64a8c39a1f | ||
|
3d55f2806e | ||
|
1eb867f33f | ||
|
e93f4f7578 | ||
|
45ead916d1 | ||
|
3a0879c8c8 | ||
|
ebf361ce18 | ||
|
953b358668 | ||
|
3dfd25b3aa | ||
|
6f66eedc5d | ||
|
4094b6e36d | ||
|
c09cbf0ed9 | ||
|
391d53e1dd | ||
|
f64ebfe3e5 | ||
|
fc040bfd05 | ||
|
c8bf86d50d | ||
|
61989fb5e9 | ||
|
6f9d4d542f | ||
|
b3a8878080 | ||
|
f4d66a99cf | ||
|
537ba6f381 | ||
|
411f691b21 | ||
|
d6aa1967ad | ||
|
6e1e0e4b5b | ||
|
3941669d69 | ||
|
1aac03797e | ||
|
459af43494 | ||
|
f4f7e3cf41 | ||
|
1fd015516e | ||
|
76bafa8ffe | ||
|
8d5797b00f | ||
|
7571c02c8a | ||
|
49cbe7c8e3 | ||
|
ba4133c9eb | ||
|
b67f1840a1 | ||
|
165c46690f | ||
|
16bc9ab601 | ||
|
15ce1338b4 | ||
|
0ff30c5333 | ||
|
6feb2d5e80 | ||
|
1e07fea200 | ||
|
7aeb67b39b | ||
|
93881db22a | ||
|
64ed7a38f9 | ||
|
2fd466fcfc | ||
|
dc2fc73691 | ||
|
c4808c6009 | ||
|
c67f584eb3 | ||
|
29f6ed78e8 | ||
|
7807ee664d | ||
|
d518d06efd | ||
|
25a0cc44b9 | ||
|
825cdcec3c | ||
|
41b610acab | ||
|
0364fa8b65 | ||
|
849086a1ae | ||
|
36fbc6887f | ||
|
a8a98e43f2 | ||
|
57bdc730e2 | ||
|
31a196d7f5 | ||
|
9b27e6c3b4 | ||
|
62f1f9507f | ||
|
ee8dda41ae | ||
|
01ba178097 | ||
|
78ff59d052 | ||
|
f3f1cd6b3b | ||
|
803540e811 | ||
|
458ade6361 | ||
|
a69969ee05 | ||
|
f2b8db57eb | ||
|
331ae266ff | ||
|
4242001863 | ||
|
78338f71ca | ||
|
f5172a3084 | ||
|
c7df67edbd | ||
|
d410fee91d | ||
|
ba7aa464de | ||
|
8333034dce | ||
|
637b6af80f | ||
|
1044f8afd2 | ||
|
2f775107f9 | ||
|
85342674b2 | ||
|
fd69098a45 | ||
|
8867f908fc | ||
|
b7c33124c8 | ||
|
89a8c423c7 | ||
|
cea2582df2 | ||
|
e423e0baaa | ||
|
60b2dd1285 | ||
|
36ddd8b3f7 | ||
|
7575d52a73 | ||
|
9a2dc4f7ac | ||
|
c5cd249e41 | ||
|
8940c1c058 | ||
|
27ec04b232 | ||
|
d2824416aa | ||
|
18061bbab0 | ||
|
4ecbbcbcea | ||
|
55c97a03e1 | ||
|
98aeac6ea9 | ||
|
8bfb6723cb | ||
|
a20575e8ae | ||
|
7724572519 | ||
|
d763637f6a | ||
|
c26e9ac4b2 | ||
|
896bf55352 | ||
|
a23ba9b53c | ||
|
38a9339baf | ||
|
def8b4039f | ||
|
a14e1538fe | ||
|
5f28a1acad | ||
|
25e9953c6f | ||
|
f9df094ca5 | ||
|
b60a469023 | ||
|
7012631257 | ||
|
e6c9f80c48 | ||
|
895ce482b1 | ||
|
e5da4021eb | ||
|
2371053565 | ||
|
33bf9033e0 | ||
|
35eacd0dae | ||
|
96bef88f5f | ||
|
5524b242a7 | ||
|
a013eba65f | ||
|
36755d40b4 | ||
|
7d568f5ab8 | ||
|
a7207cd580 | ||
|
e8ef659cd9 | ||
|
b0adbe98fb | ||
|
0c361c41b8 | ||
|
e66ab17a36 | ||
|
cb437dc2ad | ||
|
0d933b2ad5 | ||
|
c5469e046a | ||
|
4d2f143ce5 | ||
|
8f93030c85 | ||
|
fdb9aebead | ||
|
3141feb73b | ||
|
9706f3f802 | ||
|
d5e944359e | ||
|
826ec77fb2 | ||
|
2656f4eb6a | ||
|
2b88feedf7 | ||
|
23566e0d78 | ||
|
828553b614 | ||
|
3048e82a94 | ||
|
09ffa08ba1 | ||
|
e0b4cc489f | ||
|
15e423407f | ||
|
702e522044 | ||
|
77abae55df | ||
|
617c0b2239 | ||
|
814d4257df | ||
|
23ae281b31 | ||
|
94128d6b0d | ||
|
059009c592 | ||
|
9cc977f104 | ||
|
1c0ade7afa | ||
|
f2741c8d3a | ||
|
6ab8f3584a | ||
|
8ae5ce1726 | ||
|
eb92077720 | ||
|
90e0fd4bad | ||
|
05741e05d9 | ||
|
9aa6637644 | ||
|
d30d28156d | ||
|
be6d722904 | ||
|
d551980823 | ||
|
f0a6c3d2bc | ||
|
4e0fb1280a | ||
|
24f5251cce | ||
|
ac1390eee8 | ||
|
4a5b4d34dc | ||
|
63adb0cc61 | ||
|
3c80377b69 | ||
|
24577db241 | ||
|
566bd96da8 | ||
|
ebdb64d605 | ||
|
a6ffb92f0b | ||
|
3217377b3c | ||
|
24da5893fc | ||
|
087ca2cb07 | ||
|
b4e7447458 | ||
|
a45e6aadd7 | ||
|
70e322695d | ||
|
6a15923b77 | ||
|
7ffad0af5a | ||
|
0e3ae92441 | ||
|
b3ae826f7a | ||
|
dede691aca | ||
|
fb6a5b965b | ||
|
6340716b3a | ||
|
b675b32e6b | ||
|
6a3fa81ffb | ||
|
df53a98f2b | ||
|
db23d8d2a2 | ||
|
0d69795014 | ||
|
3374f3fdc2 | ||
|
4bf0727b1f | ||
|
263bd4ec50 | ||
|
9c7b79acd9 | ||
|
14470ac87b | ||
|
0cdf576d86 | ||
|
4ffeca4ea2 | ||
|
211fd6c674 | ||
|
8ae980807a | ||
|
e5c3a4b549 | ||
|
1d0668ed5a | ||
|
305d068362 | ||
|
a231ce87b5 | ||
|
a84d20fc14 | ||
|
9e30092361 | ||
|
10d5c7aa5f | ||
|
412f356e04 | ||
|
34d863f3fc | ||
|
3ee4b60d56 |
@@ -70,8 +70,9 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
--default-search PREFIX Use this prefix for unqualified URLs. For
|
--default-search PREFIX Use this prefix for unqualified URLs. For
|
||||||
example "gvsearch2:" downloads two videos
|
example "gvsearch2:" downloads two videos
|
||||||
from google videos for youtube-dl "large
|
from google videos for youtube-dl "large
|
||||||
apple". By default (with value "auto")
|
apple". Use the value "auto" to let
|
||||||
youtube-dl guesses.
|
youtube-dl guess. The default value "error"
|
||||||
|
just throws an error.
|
||||||
--ignore-config Do not read configuration files. When given
|
--ignore-config Do not read configuration files. When given
|
||||||
in the global configuration file /etc
|
in the global configuration file /etc
|
||||||
/youtube-dl.conf: do not read the user
|
/youtube-dl.conf: do not read the user
|
||||||
@@ -254,7 +255,7 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
128K (default 5)
|
128K (default 5)
|
||||||
--recode-video FORMAT Encode the video to another format if
|
--recode-video FORMAT Encode the video to another format if
|
||||||
necessary (currently supported:
|
necessary (currently supported:
|
||||||
mp4|flv|ogg|webm)
|
mp4|flv|ogg|webm|mkv)
|
||||||
-k, --keep-video keeps the video file on disk after the
|
-k, --keep-video keeps the video file on disk after the
|
||||||
post-processing; the video is erased by
|
post-processing; the video is erased by
|
||||||
default
|
default
|
||||||
|
@@ -13,7 +13,7 @@ from youtube_dl import YoutubeDL
|
|||||||
|
|
||||||
|
|
||||||
def _download_restricted(url, filename, age):
|
def _download_restricted(url, filename, age):
|
||||||
""" Returns true iff the file has been downloaded """
|
""" Returns true if the file has been downloaded """
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
'age_limit': age,
|
'age_limit': age,
|
||||||
|
@@ -69,9 +69,6 @@ class TestAllURLsMatching(unittest.TestCase):
|
|||||||
def test_youtube_show_matching(self):
|
def test_youtube_show_matching(self):
|
||||||
self.assertMatch('http://www.youtube.com/show/airdisasters', ['youtube:show'])
|
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):
|
def test_youtube_search_matching(self):
|
||||||
self.assertMatch('http://www.youtube.com/results?search_query=making+mustard', ['youtube:search_url'])
|
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'])
|
self.assertMatch('https://www.youtube.com/results?baz=bar&search_query=youtube-dl+test+video&filters=video&lclk=video', ['youtube:search_url'])
|
||||||
|
@@ -28,7 +28,9 @@ from youtube_dl.extractor import (
|
|||||||
SoundcloudSetIE,
|
SoundcloudSetIE,
|
||||||
SoundcloudUserIE,
|
SoundcloudUserIE,
|
||||||
SoundcloudPlaylistIE,
|
SoundcloudPlaylistIE,
|
||||||
|
TeacherTubeUserIE,
|
||||||
LivestreamIE,
|
LivestreamIE,
|
||||||
|
LivestreamOriginalIE,
|
||||||
NHLVideocenterIE,
|
NHLVideocenterIE,
|
||||||
BambuserChannelIE,
|
BambuserChannelIE,
|
||||||
BandcampAlbumIE,
|
BandcampAlbumIE,
|
||||||
@@ -39,6 +41,7 @@ from youtube_dl.extractor import (
|
|||||||
KhanAcademyIE,
|
KhanAcademyIE,
|
||||||
EveryonesMixtapeIE,
|
EveryonesMixtapeIE,
|
||||||
RutubeChannelIE,
|
RutubeChannelIE,
|
||||||
|
RutubePersonIE,
|
||||||
GoogleSearchIE,
|
GoogleSearchIE,
|
||||||
GenericIE,
|
GenericIE,
|
||||||
TEDIE,
|
TEDIE,
|
||||||
@@ -108,15 +111,15 @@ class TestPlaylists(unittest.TestCase):
|
|||||||
ie = VineUserIE(dl)
|
ie = VineUserIE(dl)
|
||||||
result = ie.extract('https://vine.co/Visa')
|
result = ie.extract('https://vine.co/Visa')
|
||||||
self.assertIsPlaylist(result)
|
self.assertIsPlaylist(result)
|
||||||
self.assertTrue(len(result['entries']) >= 50)
|
self.assertTrue(len(result['entries']) >= 47)
|
||||||
|
|
||||||
def test_ustream_channel(self):
|
def test_ustream_channel(self):
|
||||||
dl = FakeYDL()
|
dl = FakeYDL()
|
||||||
ie = UstreamChannelIE(dl)
|
ie = UstreamChannelIE(dl)
|
||||||
result = ie.extract('http://www.ustream.tv/channel/young-americans-for-liberty')
|
result = ie.extract('http://www.ustream.tv/channel/channeljapan')
|
||||||
self.assertIsPlaylist(result)
|
self.assertIsPlaylist(result)
|
||||||
self.assertEqual(result['id'], '5124905')
|
self.assertEqual(result['id'], '10874166')
|
||||||
self.assertTrue(len(result['entries']) >= 6)
|
self.assertTrue(len(result['entries']) >= 54)
|
||||||
|
|
||||||
def test_soundcloud_set(self):
|
def test_soundcloud_set(self):
|
||||||
dl = FakeYDL()
|
dl = FakeYDL()
|
||||||
@@ -134,6 +137,14 @@ class TestPlaylists(unittest.TestCase):
|
|||||||
self.assertEqual(result['id'], '9615865')
|
self.assertEqual(result['id'], '9615865')
|
||||||
self.assertTrue(len(result['entries']) >= 12)
|
self.assertTrue(len(result['entries']) >= 12)
|
||||||
|
|
||||||
|
def test_soundcloud_likes(self):
|
||||||
|
dl = FakeYDL()
|
||||||
|
ie = SoundcloudUserIE(dl)
|
||||||
|
result = ie.extract('https://soundcloud.com/the-concept-band/likes')
|
||||||
|
self.assertIsPlaylist(result)
|
||||||
|
self.assertEqual(result['id'], '9615865')
|
||||||
|
self.assertTrue(len(result['entries']) >= 1)
|
||||||
|
|
||||||
def test_soundcloud_playlist(self):
|
def test_soundcloud_playlist(self):
|
||||||
dl = FakeYDL()
|
dl = FakeYDL()
|
||||||
ie = SoundcloudPlaylistIE(dl)
|
ie = SoundcloudPlaylistIE(dl)
|
||||||
@@ -153,6 +164,14 @@ class TestPlaylists(unittest.TestCase):
|
|||||||
self.assertEqual(result['title'], 'TEDCity2.0 (English)')
|
self.assertEqual(result['title'], 'TEDCity2.0 (English)')
|
||||||
self.assertTrue(len(result['entries']) >= 4)
|
self.assertTrue(len(result['entries']) >= 4)
|
||||||
|
|
||||||
|
def test_livestreamoriginal_folder(self):
|
||||||
|
dl = FakeYDL()
|
||||||
|
ie = LivestreamOriginalIE(dl)
|
||||||
|
result = ie.extract('https://www.livestream.com/newplay/folder?dirId=a07bf706-d0e4-4e75-a747-b021d84f2fd3')
|
||||||
|
self.assertIsPlaylist(result)
|
||||||
|
self.assertEqual(result['id'], 'a07bf706-d0e4-4e75-a747-b021d84f2fd3')
|
||||||
|
self.assertTrue(len(result['entries']) >= 28)
|
||||||
|
|
||||||
def test_nhl_videocenter(self):
|
def test_nhl_videocenter(self):
|
||||||
dl = FakeYDL()
|
dl = FakeYDL()
|
||||||
ie = NHLVideocenterIE(dl)
|
ie = NHLVideocenterIE(dl)
|
||||||
@@ -255,10 +274,18 @@ class TestPlaylists(unittest.TestCase):
|
|||||||
def test_rutube_channel(self):
|
def test_rutube_channel(self):
|
||||||
dl = FakeYDL()
|
dl = FakeYDL()
|
||||||
ie = RutubeChannelIE(dl)
|
ie = RutubeChannelIE(dl)
|
||||||
result = ie.extract('http://rutube.ru/tags/video/1409')
|
result = ie.extract('http://rutube.ru/tags/video/1800/')
|
||||||
self.assertIsPlaylist(result)
|
self.assertIsPlaylist(result)
|
||||||
self.assertEqual(result['id'], '1409')
|
self.assertEqual(result['id'], '1800')
|
||||||
self.assertTrue(len(result['entries']) >= 34)
|
self.assertTrue(len(result['entries']) >= 68)
|
||||||
|
|
||||||
|
def test_rutube_person(self):
|
||||||
|
dl = FakeYDL()
|
||||||
|
ie = RutubePersonIE(dl)
|
||||||
|
result = ie.extract('http://rutube.ru/video/person/313878/')
|
||||||
|
self.assertIsPlaylist(result)
|
||||||
|
self.assertEqual(result['id'], '313878')
|
||||||
|
self.assertTrue(len(result['entries']) >= 37)
|
||||||
|
|
||||||
def test_multiple_brightcove_videos(self):
|
def test_multiple_brightcove_videos(self):
|
||||||
# https://github.com/rg3/youtube-dl/issues/2283
|
# https://github.com/rg3/youtube-dl/issues/2283
|
||||||
@@ -360,5 +387,13 @@ class TestPlaylists(unittest.TestCase):
|
|||||||
result['title'], 'Brace Yourself - Today\'s Weirdest News')
|
result['title'], 'Brace Yourself - Today\'s Weirdest News')
|
||||||
self.assertTrue(len(result['entries']) >= 10)
|
self.assertTrue(len(result['entries']) >= 10)
|
||||||
|
|
||||||
|
def test_TeacherTubeUser(self):
|
||||||
|
dl = FakeYDL()
|
||||||
|
ie = TeacherTubeUserIE(dl)
|
||||||
|
result = ie.extract('http://www.teachertube.com/user/profile/rbhagwati2')
|
||||||
|
self.assertIsPlaylist(result)
|
||||||
|
self.assertEqual(result['id'], 'rbhagwati2')
|
||||||
|
self.assertTrue(len(result['entries']) >= 179)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@@ -87,7 +87,7 @@ class TestYoutubeSubtitles(BaseTestSubtitles):
|
|||||||
|
|
||||||
def test_youtube_nosubtitles(self):
|
def test_youtube_nosubtitles(self):
|
||||||
self.DL.expect_warning(u'video doesn\'t have subtitles')
|
self.DL.expect_warning(u'video doesn\'t have subtitles')
|
||||||
self.url = 'sAjKT8FhjI8'
|
self.url = 'n5BB19UTcdA'
|
||||||
self.DL.params['writesubtitles'] = True
|
self.DL.params['writesubtitles'] = True
|
||||||
self.DL.params['allsubtitles'] = True
|
self.DL.params['allsubtitles'] = True
|
||||||
subtitles = self.getSubtitles()
|
subtitles = self.getSubtitles()
|
||||||
|
@@ -112,11 +112,11 @@ class TestYoutubeLists(unittest.TestCase):
|
|||||||
def test_youtube_mix(self):
|
def test_youtube_mix(self):
|
||||||
dl = FakeYDL()
|
dl = FakeYDL()
|
||||||
ie = YoutubePlaylistIE(dl)
|
ie = YoutubePlaylistIE(dl)
|
||||||
result = ie.extract('http://www.youtube.com/watch?v=lLJf9qJHR3E&list=RDrjFaenf1T-Y')
|
result = ie.extract('https://www.youtube.com/watch?v=W01L70IGBgE&index=2&list=RDOQpdSVF_k_w')
|
||||||
entries = result['entries']
|
entries = result['entries']
|
||||||
self.assertTrue(len(entries) >= 20)
|
self.assertTrue(len(entries) >= 20)
|
||||||
original_video = entries[0]
|
original_video = entries[0]
|
||||||
self.assertEqual(original_video['id'], 'rjFaenf1T-Y')
|
self.assertEqual(original_video['id'], 'OQpdSVF_k_w')
|
||||||
|
|
||||||
def test_youtube_toptracks(self):
|
def test_youtube_toptracks(self):
|
||||||
print('Skipping: The playlist page gives error 500')
|
print('Skipping: The playlist page gives error 500')
|
||||||
|
@@ -33,6 +33,18 @@ _TESTS = [
|
|||||||
90,
|
90,
|
||||||
u']\\[@?>=<;:/.-,+*)(\'&%$#"hZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjiagfedcb39876',
|
u']\\[@?>=<;:/.-,+*)(\'&%$#"hZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjiagfedcb39876',
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
u'https://s.ytimg.com/yts/jsbin/html5player-en_US-vfl0Cbn9e.js',
|
||||||
|
u'js',
|
||||||
|
84,
|
||||||
|
u'O1I3456789abcde0ghijklmnopqrstuvwxyzABCDEFGHfJKLMN2PQRSTUVW@YZ!"#$%&\'()*+,-./:;<=',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
u'https://s.ytimg.com/yts/jsbin/html5player-en_US-vflXGBaUN.js',
|
||||||
|
u'js',
|
||||||
|
u'2ACFC7A61CA478CD21425E5A57EBD73DDC78E22A.2094302436B2D377D14A3BBA23022D023B8BC25AA',
|
||||||
|
u'A52CB8B320D22032ABB3A41D773D2B6342034902.A22E87CDD37DBE75A5E52412DC874AC16A7CFCA2',
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -44,7 +56,7 @@ class TestSignature(unittest.TestCase):
|
|||||||
os.mkdir(self.TESTDATA_DIR)
|
os.mkdir(self.TESTDATA_DIR)
|
||||||
|
|
||||||
|
|
||||||
def make_tfunc(url, stype, sig_length, expected_sig):
|
def make_tfunc(url, stype, sig_input, expected_sig):
|
||||||
basename = url.rpartition('/')[2]
|
basename = url.rpartition('/')[2]
|
||||||
m = re.match(r'.*-([a-zA-Z0-9_-]+)\.[a-z]+$', basename)
|
m = re.match(r'.*-([a-zA-Z0-9_-]+)\.[a-z]+$', basename)
|
||||||
assert m, '%r should follow URL format' % basename
|
assert m, '%r should follow URL format' % basename
|
||||||
@@ -66,7 +78,9 @@ def make_tfunc(url, stype, sig_length, expected_sig):
|
|||||||
with open(fn, 'rb') as testf:
|
with open(fn, 'rb') as testf:
|
||||||
swfcode = testf.read()
|
swfcode = testf.read()
|
||||||
func = ie._parse_sig_swf(swfcode)
|
func = ie._parse_sig_swf(swfcode)
|
||||||
src_sig = compat_str(string.printable[:sig_length])
|
src_sig = (
|
||||||
|
compat_str(string.printable[:sig_input])
|
||||||
|
if isinstance(sig_input, int) else sig_input)
|
||||||
got_sig = func(src_sig)
|
got_sig = func(src_sig)
|
||||||
self.assertEqual(got_sig, expected_sig)
|
self.assertEqual(got_sig, expected_sig)
|
||||||
|
|
||||||
|
@@ -717,6 +717,17 @@ class YoutubeDL(object):
|
|||||||
info_dict['playlist'] = None
|
info_dict['playlist'] = None
|
||||||
info_dict['playlist_index'] = None
|
info_dict['playlist_index'] = None
|
||||||
|
|
||||||
|
thumbnails = info_dict.get('thumbnails')
|
||||||
|
if thumbnails:
|
||||||
|
thumbnails.sort(key=lambda t: (
|
||||||
|
t.get('width'), t.get('height'), t.get('url')))
|
||||||
|
for t in thumbnails:
|
||||||
|
if 'width' in t and 'height' in t:
|
||||||
|
t['resolution'] = '%dx%d' % (t['width'], t['height'])
|
||||||
|
|
||||||
|
if thumbnails and 'thumbnail' not in info_dict:
|
||||||
|
info_dict['thumbnail'] = thumbnails[-1]['url']
|
||||||
|
|
||||||
if 'display_id' not in info_dict and 'id' in info_dict:
|
if 'display_id' not in info_dict and 'id' in info_dict:
|
||||||
info_dict['display_id'] = info_dict['id']
|
info_dict['display_id'] = info_dict['id']
|
||||||
|
|
||||||
@@ -982,6 +993,8 @@ class YoutubeDL(object):
|
|||||||
fd = get_suitable_downloader(info)(self, self.params)
|
fd = get_suitable_downloader(info)(self, self.params)
|
||||||
for ph in self._progress_hooks:
|
for ph in self._progress_hooks:
|
||||||
fd.add_progress_hook(ph)
|
fd.add_progress_hook(ph)
|
||||||
|
if self.params.get('verbose'):
|
||||||
|
self.to_stdout('[debug] Invoking downloader on %r' % info.get('url'))
|
||||||
return fd.download(name, info)
|
return fd.download(name, info)
|
||||||
if info_dict.get('requested_formats') is not None:
|
if info_dict.get('requested_formats') is not None:
|
||||||
downloaded = []
|
downloaded = []
|
||||||
|
@@ -57,6 +57,13 @@ __authors__ = (
|
|||||||
'Jason Normore',
|
'Jason Normore',
|
||||||
'Hoje Lee',
|
'Hoje Lee',
|
||||||
'Adam Thalhammer',
|
'Adam Thalhammer',
|
||||||
|
'Georg Jähnig',
|
||||||
|
'Ralf Haring',
|
||||||
|
'Koki Takahashi',
|
||||||
|
'Ariset Llerena',
|
||||||
|
'Adam Malcontenti-Wilson',
|
||||||
|
'Tobias Bell',
|
||||||
|
'Naglis Jonaitis',
|
||||||
)
|
)
|
||||||
|
|
||||||
__license__ = 'Public Domain'
|
__license__ = 'Public Domain'
|
||||||
@@ -267,7 +274,7 @@ def parseOpts(overrideArguments=None):
|
|||||||
general.add_option(
|
general.add_option(
|
||||||
'--default-search',
|
'--default-search',
|
||||||
dest='default_search', metavar='PREFIX',
|
dest='default_search', metavar='PREFIX',
|
||||||
help='Use this prefix for unqualified URLs. For example "gvsearch2:" downloads two videos from google videos for youtube-dl "large apple". By default (with value "auto") youtube-dl guesses.')
|
help='Use this prefix for unqualified URLs. For example "gvsearch2:" downloads two videos from google videos for youtube-dl "large apple". Use the value "auto" to let youtube-dl guess. The default value "error" just throws an error.')
|
||||||
general.add_option(
|
general.add_option(
|
||||||
'--ignore-config',
|
'--ignore-config',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
@@ -503,7 +510,7 @@ def parseOpts(overrideArguments=None):
|
|||||||
postproc.add_option('--audio-quality', metavar='QUALITY', dest='audioquality', default='5',
|
postproc.add_option('--audio-quality', metavar='QUALITY', dest='audioquality', default='5',
|
||||||
help='ffmpeg/avconv audio quality specification, insert a value between 0 (better) and 9 (worse) for VBR or a specific bitrate like 128K (default 5)')
|
help='ffmpeg/avconv audio quality specification, insert a value between 0 (better) and 9 (worse) for VBR or a specific bitrate like 128K (default 5)')
|
||||||
postproc.add_option('--recode-video', metavar='FORMAT', dest='recodevideo', default=None,
|
postproc.add_option('--recode-video', metavar='FORMAT', dest='recodevideo', default=None,
|
||||||
help='Encode the video to another format if necessary (currently supported: mp4|flv|ogg|webm)')
|
help='Encode the video to another format if necessary (currently supported: mp4|flv|ogg|webm|mkv)')
|
||||||
postproc.add_option('-k', '--keep-video', action='store_true', dest='keepvideo', default=False,
|
postproc.add_option('-k', '--keep-video', action='store_true', dest='keepvideo', default=False,
|
||||||
help='keeps the video file on disk after the post-processing; the video is erased by default')
|
help='keeps the video file on disk after the post-processing; the video is erased by default')
|
||||||
postproc.add_option('--no-post-overwrites', action='store_true', dest='nopostoverwrites', default=False,
|
postproc.add_option('--no-post-overwrites', action='store_true', dest='nopostoverwrites', default=False,
|
||||||
|
@@ -25,7 +25,7 @@ class HlsFD(FileDownloader):
|
|||||||
except (OSError, IOError):
|
except (OSError, IOError):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
self.report_error(u'm3u8 download detected but ffmpeg or avconv could not be found')
|
self.report_error(u'm3u8 download detected but ffmpeg or avconv could not be found. Please install one.')
|
||||||
cmd = [program] + args
|
cmd = [program] + args
|
||||||
|
|
||||||
retval = subprocess.call(cmd)
|
retval = subprocess.call(cmd)
|
||||||
|
@@ -96,6 +96,7 @@ class RtmpFD(FileDownloader):
|
|||||||
flash_version = info_dict.get('flash_version', None)
|
flash_version = info_dict.get('flash_version', None)
|
||||||
live = info_dict.get('rtmp_live', False)
|
live = info_dict.get('rtmp_live', False)
|
||||||
conn = info_dict.get('rtmp_conn', None)
|
conn = info_dict.get('rtmp_conn', None)
|
||||||
|
protocol = info_dict.get('rtmp_protocol', None)
|
||||||
|
|
||||||
self.report_destination(filename)
|
self.report_destination(filename)
|
||||||
tmpfilename = self.temp_name(filename)
|
tmpfilename = self.temp_name(filename)
|
||||||
@@ -105,7 +106,7 @@ class RtmpFD(FileDownloader):
|
|||||||
try:
|
try:
|
||||||
subprocess.call(['rtmpdump', '-h'], stdout=(open(os.path.devnull, 'w')), stderr=subprocess.STDOUT)
|
subprocess.call(['rtmpdump', '-h'], stdout=(open(os.path.devnull, 'w')), stderr=subprocess.STDOUT)
|
||||||
except (OSError, IOError):
|
except (OSError, IOError):
|
||||||
self.report_error('RTMP download detected but "rtmpdump" could not be run')
|
self.report_error('RTMP download detected but "rtmpdump" could not be run. Please install it.')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Download using rtmpdump. rtmpdump returns exit code 2 when
|
# Download using rtmpdump. rtmpdump returns exit code 2 when
|
||||||
@@ -133,6 +134,8 @@ class RtmpFD(FileDownloader):
|
|||||||
basic_args += ['--conn', entry]
|
basic_args += ['--conn', entry]
|
||||||
elif isinstance(conn, compat_str):
|
elif isinstance(conn, compat_str):
|
||||||
basic_args += ['--conn', conn]
|
basic_args += ['--conn', conn]
|
||||||
|
if protocol is not None:
|
||||||
|
basic_args += ['--protocol', protocol]
|
||||||
args = basic_args + [[], ['--resume', '--skip', '1']][not live and self.params.get('continuedl', False)]
|
args = basic_args + [[], ['--resume', '--skip', '1']][not live and self.params.get('continuedl', False)]
|
||||||
|
|
||||||
if sys.platform == 'win32' and sys.version_info < (3, 0):
|
if sys.platform == 'win32' and sys.version_info < (3, 0):
|
||||||
|
@@ -3,6 +3,7 @@ from .addanime import AddAnimeIE
|
|||||||
from .aftonbladet import AftonbladetIE
|
from .aftonbladet import AftonbladetIE
|
||||||
from .anitube import AnitubeIE
|
from .anitube import AnitubeIE
|
||||||
from .aol import AolIE
|
from .aol import AolIE
|
||||||
|
from .allocine import AllocineIE
|
||||||
from .aparat import AparatIE
|
from .aparat import AparatIE
|
||||||
from .appletrailers import AppleTrailersIE
|
from .appletrailers import AppleTrailersIE
|
||||||
from .archiveorg import ArchiveOrgIE
|
from .archiveorg import ArchiveOrgIE
|
||||||
@@ -63,6 +64,7 @@ from .dailymotion import (
|
|||||||
from .daum import DaumIE
|
from .daum import DaumIE
|
||||||
from .dotsub import DotsubIE
|
from .dotsub import DotsubIE
|
||||||
from .dreisat import DreiSatIE
|
from .dreisat import DreiSatIE
|
||||||
|
from .drtv import DRTVIE
|
||||||
from .defense import DefenseGouvFrIE
|
from .defense import DefenseGouvFrIE
|
||||||
from .discovery import DiscoveryIE
|
from .discovery import DiscoveryIE
|
||||||
from .divxstage import DivxStageIE
|
from .divxstage import DivxStageIE
|
||||||
@@ -81,6 +83,7 @@ from .extremetube import ExtremeTubeIE
|
|||||||
from .facebook import FacebookIE
|
from .facebook import FacebookIE
|
||||||
from .faz import FazIE
|
from .faz import FazIE
|
||||||
from .fc2 import FC2IE
|
from .fc2 import FC2IE
|
||||||
|
from .firedrive import FiredriveIE
|
||||||
from .firstpost import FirstpostIE
|
from .firstpost import FirstpostIE
|
||||||
from .firsttv import FirstTVIE
|
from .firsttv import FirstTVIE
|
||||||
from .fivemin import FiveMinIE
|
from .fivemin import FiveMinIE
|
||||||
@@ -103,12 +106,15 @@ from .freesound import FreesoundIE
|
|||||||
from .freespeech import FreespeechIE
|
from .freespeech import FreespeechIE
|
||||||
from .funnyordie import FunnyOrDieIE
|
from .funnyordie import FunnyOrDieIE
|
||||||
from .gamekings import GamekingsIE
|
from .gamekings import GamekingsIE
|
||||||
|
from .gameone import GameOneIE
|
||||||
from .gamespot import GameSpotIE
|
from .gamespot import GameSpotIE
|
||||||
from .gametrailers import GametrailersIE
|
from .gametrailers import GametrailersIE
|
||||||
from .gdcvault import GDCVaultIE
|
from .gdcvault import GDCVaultIE
|
||||||
from .generic import GenericIE
|
from .generic import GenericIE
|
||||||
from .googleplus import GooglePlusIE
|
from .googleplus import GooglePlusIE
|
||||||
from .googlesearch import GoogleSearchIE
|
from .googlesearch import GoogleSearchIE
|
||||||
|
from .gorillavid import GorillaVidIE
|
||||||
|
from .goshgay import GoshgayIE
|
||||||
from .hark import HarkIE
|
from .hark import HarkIE
|
||||||
from .helsinki import HelsinkiIE
|
from .helsinki import HelsinkiIE
|
||||||
from .hentaistigma import HentaiStigmaIE
|
from .hentaistigma import HentaiStigmaIE
|
||||||
@@ -142,10 +148,15 @@ from .khanacademy import KhanAcademyIE
|
|||||||
from .kickstarter import KickStarterIE
|
from .kickstarter import KickStarterIE
|
||||||
from .keek import KeekIE
|
from .keek import KeekIE
|
||||||
from .kontrtube import KontrTubeIE
|
from .kontrtube import KontrTubeIE
|
||||||
|
from .ku6 import Ku6IE
|
||||||
from .la7 import LA7IE
|
from .la7 import LA7IE
|
||||||
from .lifenews import LifeNewsIE
|
from .lifenews import LifeNewsIE
|
||||||
from .liveleak import LiveLeakIE
|
from .liveleak import LiveLeakIE
|
||||||
from .livestream import LivestreamIE, LivestreamOriginalIE
|
from .livestream import (
|
||||||
|
LivestreamIE,
|
||||||
|
LivestreamOriginalIE,
|
||||||
|
LivestreamShortenerIE,
|
||||||
|
)
|
||||||
from .lynda import (
|
from .lynda import (
|
||||||
LyndaIE,
|
LyndaIE,
|
||||||
LyndaCourseIE
|
LyndaCourseIE
|
||||||
@@ -163,11 +174,13 @@ from .mpora import MporaIE
|
|||||||
from .mofosex import MofosexIE
|
from .mofosex import MofosexIE
|
||||||
from .mooshare import MooshareIE
|
from .mooshare import MooshareIE
|
||||||
from .morningstar import MorningstarIE
|
from .morningstar import MorningstarIE
|
||||||
|
from .motherless import MotherlessIE
|
||||||
from .motorsport import MotorsportIE
|
from .motorsport import MotorsportIE
|
||||||
from .moviezine import MoviezineIE
|
from .moviezine import MoviezineIE
|
||||||
from .movshare import MovShareIE
|
from .movshare import MovShareIE
|
||||||
from .mtv import (
|
from .mtv import (
|
||||||
MTVIE,
|
MTVIE,
|
||||||
|
MTVServicesEmbeddedIE,
|
||||||
MTVIggyIE,
|
MTVIggyIE,
|
||||||
)
|
)
|
||||||
from .musicplayon import MusicPlayOnIE
|
from .musicplayon import MusicPlayOnIE
|
||||||
@@ -194,6 +207,7 @@ from .normalboots import NormalbootsIE
|
|||||||
from .novamov import NovaMovIE
|
from .novamov import NovaMovIE
|
||||||
from .nowness import NownessIE
|
from .nowness import NownessIE
|
||||||
from .nowvideo import NowVideoIE
|
from .nowvideo import NowVideoIE
|
||||||
|
from .npo import NPOIE
|
||||||
from .nrk import (
|
from .nrk import (
|
||||||
NRKIE,
|
NRKIE,
|
||||||
NRKTVIE,
|
NRKTVIE,
|
||||||
@@ -215,8 +229,10 @@ from .pornotube import PornotubeIE
|
|||||||
from .prosiebensat1 import ProSiebenSat1IE
|
from .prosiebensat1 import ProSiebenSat1IE
|
||||||
from .pyvideo import PyvideoIE
|
from .pyvideo import PyvideoIE
|
||||||
from .radiofrance import RadioFranceIE
|
from .radiofrance import RadioFranceIE
|
||||||
|
from .rai import RaiIE
|
||||||
from .rbmaradio import RBMARadioIE
|
from .rbmaradio import RBMARadioIE
|
||||||
from .redtube import RedTubeIE
|
from .redtube import RedTubeIE
|
||||||
|
from .reverbnation import ReverbNationIE
|
||||||
from .ringtv import RingTVIE
|
from .ringtv import RingTVIE
|
||||||
from .ro220 import Ro220IE
|
from .ro220 import Ro220IE
|
||||||
from .rottentomatoes import RottenTomatoesIE
|
from .rottentomatoes import RottenTomatoesIE
|
||||||
@@ -225,6 +241,7 @@ from .rtbf import RTBFIE
|
|||||||
from .rtlnow import RTLnowIE
|
from .rtlnow import RTLnowIE
|
||||||
from .rts import RTSIE
|
from .rts import RTSIE
|
||||||
from .rtve import RTVEALaCartaIE
|
from .rtve import RTVEALaCartaIE
|
||||||
|
from .ruhd import RUHDIE
|
||||||
from .rutube import (
|
from .rutube import (
|
||||||
RutubeIE,
|
RutubeIE,
|
||||||
RutubeChannelIE,
|
RutubeChannelIE,
|
||||||
@@ -234,6 +251,7 @@ from .rutube import (
|
|||||||
from .rutv import RUTVIE
|
from .rutv import RUTVIE
|
||||||
from .savefrom import SaveFromIE
|
from .savefrom import SaveFromIE
|
||||||
from .scivee import SciVeeIE
|
from .scivee import SciVeeIE
|
||||||
|
from .screencast import ScreencastIE
|
||||||
from .servingsys import ServingSysIE
|
from .servingsys import ServingSysIE
|
||||||
from .sina import SinaIE
|
from .sina import SinaIE
|
||||||
from .slideshare import SlideshareIE
|
from .slideshare import SlideshareIE
|
||||||
@@ -251,13 +269,15 @@ from .soundcloud import (
|
|||||||
SoundcloudUserIE,
|
SoundcloudUserIE,
|
||||||
SoundcloudPlaylistIE
|
SoundcloudPlaylistIE
|
||||||
)
|
)
|
||||||
from .southparkstudios import (
|
from .soundgasm import SoundgasmIE
|
||||||
SouthParkStudiosIE,
|
from .southpark import (
|
||||||
|
SouthParkIE,
|
||||||
SouthparkDeIE,
|
SouthparkDeIE,
|
||||||
)
|
)
|
||||||
from .space import SpaceIE
|
from .space import SpaceIE
|
||||||
from .spankwire import SpankwireIE
|
from .spankwire import SpankwireIE
|
||||||
from .spiegel import SpiegelIE
|
from .spiegel import SpiegelIE
|
||||||
|
from .spiegeltv import SpiegeltvIE
|
||||||
from .spike import SpikeIE
|
from .spike import SpikeIE
|
||||||
from .stanfordoc import StanfordOpenClassroomIE
|
from .stanfordoc import StanfordOpenClassroomIE
|
||||||
from .steam import SteamIE
|
from .steam import SteamIE
|
||||||
@@ -266,9 +286,16 @@ from .streamcz import StreamCZIE
|
|||||||
from .swrmediathek import SWRMediathekIE
|
from .swrmediathek import SWRMediathekIE
|
||||||
from .syfy import SyfyIE
|
from .syfy import SyfyIE
|
||||||
from .sztvhu import SztvHuIE
|
from .sztvhu import SztvHuIE
|
||||||
|
from .tagesschau import TagesschauIE
|
||||||
|
from .teachertube import (
|
||||||
|
TeacherTubeIE,
|
||||||
|
TeacherTubeUserIE,
|
||||||
|
)
|
||||||
|
from .teachingchannel import TeachingChannelIE
|
||||||
from .teamcoco import TeamcocoIE
|
from .teamcoco import TeamcocoIE
|
||||||
from .techtalks import TechTalksIE
|
from .techtalks import TechTalksIE
|
||||||
from .ted import TEDIE
|
from .ted import TEDIE
|
||||||
|
from .tenplay import TenPlayIE
|
||||||
from .testurl import TestURLIE
|
from .testurl import TestURLIE
|
||||||
from .tf1 import TF1IE
|
from .tf1 import TF1IE
|
||||||
from .theplatform import ThePlatformIE
|
from .theplatform import ThePlatformIE
|
||||||
@@ -298,6 +325,7 @@ from .veehd import VeeHDIE
|
|||||||
from .veoh import VeohIE
|
from .veoh import VeohIE
|
||||||
from .vesti import VestiIE
|
from .vesti import VestiIE
|
||||||
from .vevo import VevoIE
|
from .vevo import VevoIE
|
||||||
|
from .vh1 import VH1IE
|
||||||
from .viddler import ViddlerIE
|
from .viddler import ViddlerIE
|
||||||
from .videobam import VideoBamIE
|
from .videobam import VideoBamIE
|
||||||
from .videodetective import VideoDetectiveIE
|
from .videodetective import VideoDetectiveIE
|
||||||
@@ -315,14 +343,17 @@ from .vimeo import (
|
|||||||
VimeoReviewIE,
|
VimeoReviewIE,
|
||||||
VimeoWatchLaterIE,
|
VimeoWatchLaterIE,
|
||||||
)
|
)
|
||||||
|
from .vimple import VimpleIE
|
||||||
from .vine import (
|
from .vine import (
|
||||||
VineIE,
|
VineIE,
|
||||||
VineUserIE,
|
VineUserIE,
|
||||||
)
|
)
|
||||||
from .viki import VikiIE
|
from .viki import VikiIE
|
||||||
from .vk import VKIE
|
from .vk import VKIE
|
||||||
|
from .vodlocker import VodlockerIE
|
||||||
from .vube import VubeIE
|
from .vube import VubeIE
|
||||||
from .vuclip import VuClipIE
|
from .vuclip import VuClipIE
|
||||||
|
from .vulture import VultureIE
|
||||||
from .washingtonpost import WashingtonPostIE
|
from .washingtonpost import WashingtonPostIE
|
||||||
from .wat import WatIE
|
from .wat import WatIE
|
||||||
from .wdr import (
|
from .wdr import (
|
||||||
@@ -334,6 +365,7 @@ from .weibo import WeiboIE
|
|||||||
from .wimp import WimpIE
|
from .wimp import WimpIE
|
||||||
from .wistia import WistiaIE
|
from .wistia import WistiaIE
|
||||||
from .worldstarhiphop import WorldStarHipHopIE
|
from .worldstarhiphop import WorldStarHipHopIE
|
||||||
|
from .wrzuta import WrzutaIE
|
||||||
from .xbef import XBefIE
|
from .xbef import XBefIE
|
||||||
from .xhamster import XHamsterIE
|
from .xhamster import XHamsterIE
|
||||||
from .xnxx import XNXXIE
|
from .xnxx import XNXXIE
|
||||||
|
89
youtube_dl/extractor/allocine.py
Normal file
89
youtube_dl/extractor/allocine.py
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
compat_str,
|
||||||
|
qualities,
|
||||||
|
determine_ext,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AllocineIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?allocine\.fr/(?P<typ>article|video|film)/(fichearticle_gen_carticle=|player_gen_cmedia=|fichefilm_gen_cfilm=)(?P<id>[0-9]+)(?:\.html)?'
|
||||||
|
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://www.allocine.fr/article/fichearticle_gen_carticle=18635087.html',
|
||||||
|
'md5': '0c9fcf59a841f65635fa300ac43d8269',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '19546517',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Astérix - Le Domaine des Dieux Teaser VF',
|
||||||
|
'description': 'md5:4a754271d9c6f16c72629a8a993ee884',
|
||||||
|
'thumbnail': 're:http://.*\.jpg',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.allocine.fr/video/player_gen_cmedia=19540403&cfilm=222257.html',
|
||||||
|
'md5': 'd0cdce5d2b9522ce279fdfec07ff16e0',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '19540403',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Planes 2 Bande-annonce VF',
|
||||||
|
'description': 'md5:c4b1f7bd682a91de6491ada267ec0f4d',
|
||||||
|
'thumbnail': 're:http://.*\.jpg',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.allocine.fr/film/fichefilm_gen_cfilm=181290.html',
|
||||||
|
'md5': '101250fb127ef9ca3d73186ff22a47ce',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '19544709',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Dragons 2 - Bande annonce finale VF',
|
||||||
|
'description': 'md5:e74a4dc750894bac300ece46c7036490',
|
||||||
|
'thumbnail': 're:http://.*\.jpg',
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
typ = mobj.group('typ')
|
||||||
|
display_id = mobj.group('id')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
|
||||||
|
if typ == 'film':
|
||||||
|
video_id = self._search_regex(r'href="/video/player_gen_cmedia=([0-9]+).+"', webpage, 'video id')
|
||||||
|
else:
|
||||||
|
player = self._search_regex(r'data-player=\'([^\']+)\'>', webpage, 'data player')
|
||||||
|
|
||||||
|
player_data = json.loads(player)
|
||||||
|
video_id = compat_str(player_data['refMedia'])
|
||||||
|
|
||||||
|
xml = self._download_xml('http://www.allocine.fr/ws/AcVisiondataV4.ashx?media=%s' % video_id, display_id)
|
||||||
|
|
||||||
|
video = xml.find('.//AcVisionVideo').attrib
|
||||||
|
quality = qualities(['ld', 'md', 'hd'])
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
for k, v in video.items():
|
||||||
|
if re.match(r'.+_path', k):
|
||||||
|
format_id = k.split('_')[0]
|
||||||
|
formats.append({
|
||||||
|
'format_id': format_id,
|
||||||
|
'quality': quality(format_id),
|
||||||
|
'url': v,
|
||||||
|
'ext': determine_ext(v),
|
||||||
|
})
|
||||||
|
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': video['videoTitle'],
|
||||||
|
'thumbnail': self._og_search_thumbnail(webpage),
|
||||||
|
'formats': formats,
|
||||||
|
'description': self._og_search_description(webpage),
|
||||||
|
}
|
@@ -1,22 +1,24 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
class AnitubeIE(InfoExtractor):
|
class AnitubeIE(InfoExtractor):
|
||||||
IE_NAME = u'anitube.se'
|
IE_NAME = 'anitube.se'
|
||||||
_VALID_URL = r'https?://(?:www\.)?anitube\.se/video/(?P<id>\d+)'
|
_VALID_URL = r'https?://(?:www\.)?anitube\.se/video/(?P<id>\d+)'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
u'url': u'http://www.anitube.se/video/36621',
|
'url': 'http://www.anitube.se/video/36621',
|
||||||
u'md5': u'59d0eeae28ea0bc8c05e7af429998d43',
|
'md5': '59d0eeae28ea0bc8c05e7af429998d43',
|
||||||
u'file': u'36621.mp4',
|
'info_dict': {
|
||||||
u'info_dict': {
|
'id': '36621',
|
||||||
u'id': u'36621',
|
'ext': 'mp4',
|
||||||
u'ext': u'mp4',
|
'title': 'Recorder to Randoseru 01',
|
||||||
u'title': u'Recorder to Randoseru 01',
|
'duration': 180.19,
|
||||||
},
|
},
|
||||||
u'skip': u'Blocked in the US',
|
'skip': 'Blocked in the US',
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
@@ -24,13 +26,15 @@ class AnitubeIE(InfoExtractor):
|
|||||||
video_id = mobj.group('id')
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
key = self._html_search_regex(r'http://www\.anitube\.se/embed/([A-Za-z0-9_-]*)',
|
key = self._html_search_regex(
|
||||||
webpage, u'key')
|
r'http://www\.anitube\.se/embed/([A-Za-z0-9_-]*)', webpage, 'key')
|
||||||
|
|
||||||
config_xml = self._download_xml('http://www.anitube.se/nuevo/econfig.php?key=%s' % key,
|
config_xml = self._download_xml(
|
||||||
key)
|
'http://www.anitube.se/nuevo/econfig.php?key=%s' % key, key)
|
||||||
|
|
||||||
video_title = config_xml.find('title').text
|
video_title = config_xml.find('title').text
|
||||||
|
thumbnail = config_xml.find('image').text
|
||||||
|
duration = float(config_xml.find('duration').text)
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
video_url = config_xml.find('file')
|
video_url = config_xml.find('file')
|
||||||
@@ -49,5 +53,7 @@ class AnitubeIE(InfoExtractor):
|
|||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': video_title,
|
'title': video_title,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'duration': duration,
|
||||||
'formats': formats
|
'formats': formats
|
||||||
}
|
}
|
||||||
|
@@ -39,38 +39,42 @@ class ARDIE(InfoExtractor):
|
|||||||
|
|
||||||
title = self._html_search_regex(
|
title = self._html_search_regex(
|
||||||
[r'<h1(?:\s+class="boxTopHeadline")?>(.*?)</h1>',
|
[r'<h1(?:\s+class="boxTopHeadline")?>(.*?)</h1>',
|
||||||
|
r'<meta name="dcterms.title" content="(.*?)"/>',
|
||||||
r'<h4 class="headline">(.*?)</h4>'],
|
r'<h4 class="headline">(.*?)</h4>'],
|
||||||
webpage, 'title')
|
webpage, 'title')
|
||||||
description = self._html_search_meta(
|
description = self._html_search_meta(
|
||||||
'dcterms.abstract', webpage, 'description')
|
'dcterms.abstract', webpage, 'description')
|
||||||
thumbnail = self._og_search_thumbnail(webpage)
|
thumbnail = self._og_search_thumbnail(webpage)
|
||||||
|
|
||||||
streams = [
|
|
||||||
mo.groupdict()
|
media_info = self._download_json(
|
||||||
for mo in re.finditer(
|
'http://www.ardmediathek.de/play/media/%s' % video_id, video_id)
|
||||||
r'mediaCollection\.addMediaStream\((?P<media_type>\d+), (?P<quality>\d+), "(?P<rtmp_url>[^"]*)", "(?P<video_url>[^"]*)", "[^"]*"\)', webpage)]
|
# The second element of the _mediaArray contains the standard http urls
|
||||||
|
streams = media_info['_mediaArray'][1]['_mediaStreamArray']
|
||||||
if not streams:
|
if not streams:
|
||||||
if '"fsk"' in webpage:
|
if '"fsk"' in webpage:
|
||||||
raise ExtractorError('This video is only available after 20:00')
|
raise ExtractorError('This video is only available after 20:00')
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
for s in streams:
|
|
||||||
format = {
|
|
||||||
'quality': int(s['quality']),
|
|
||||||
}
|
|
||||||
if s.get('rtmp_url'):
|
|
||||||
format['protocol'] = 'rtmp'
|
|
||||||
format['url'] = s['rtmp_url']
|
|
||||||
format['playpath'] = s['video_url']
|
|
||||||
else:
|
|
||||||
format['url'] = s['video_url']
|
|
||||||
|
|
||||||
quality_name = self._search_regex(
|
for s in streams:
|
||||||
r'[,.]([a-zA-Z0-9_-]+),?\.mp4', format['url'],
|
if type(s['_stream']) == list:
|
||||||
'quality name', default='NA')
|
for index, url in enumerate(s['_stream'][::-1]):
|
||||||
format['format_id'] = '%s-%s-%s-%s' % (
|
quality = s['_quality'] + index
|
||||||
determine_ext(format['url']), quality_name, s['media_type'],
|
formats.append({
|
||||||
s['quality'])
|
'quality': quality,
|
||||||
|
'url': url,
|
||||||
|
'format_id': '%s-%s' % (determine_ext(url), quality)
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
|
||||||
|
format = {
|
||||||
|
'quality': s['_quality'],
|
||||||
|
'url': s['_stream'],
|
||||||
|
}
|
||||||
|
|
||||||
|
format['format_id'] = '%s-%s' % (
|
||||||
|
determine_ext(format['url']), format['quality'])
|
||||||
|
|
||||||
formats.append(format)
|
formats.append(format)
|
||||||
|
|
||||||
|
@@ -39,7 +39,10 @@ class ArteTvIE(InfoExtractor):
|
|||||||
|
|
||||||
formats = [{
|
formats = [{
|
||||||
'forma_id': q.attrib['quality'],
|
'forma_id': q.attrib['quality'],
|
||||||
'url': q.text,
|
# The playpath starts at 'mp4:', if we don't manually
|
||||||
|
# split the url, rtmpdump will incorrectly parse them
|
||||||
|
'url': q.text.split('mp4:', 1)[0],
|
||||||
|
'play_path': 'mp4:' + q.text.split('mp4:', 1)[1],
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'quality': 2 if q.attrib['quality'] == 'hd' else 1,
|
'quality': 2 if q.attrib['quality'] == 'hd' else 1,
|
||||||
} for q in config.findall('./urls/url')]
|
} for q in config.findall('./urls/url')]
|
||||||
@@ -111,7 +114,7 @@ class ArteTVPlus7IE(InfoExtractor):
|
|||||||
if not formats:
|
if not formats:
|
||||||
# Some videos are only available in the 'Originalversion'
|
# Some videos are only available in the 'Originalversion'
|
||||||
# they aren't tagged as being in French or German
|
# they aren't tagged as being in French or German
|
||||||
if all(f['versionCode'] == 'VO' for f in all_formats):
|
if all(f['versionCode'] == 'VO' or f['versionCode'] == 'VA' for f in all_formats):
|
||||||
formats = all_formats
|
formats = all_formats
|
||||||
else:
|
else:
|
||||||
raise ExtractorError(u'The formats list is empty')
|
raise ExtractorError(u'The formats list is empty')
|
||||||
@@ -189,9 +192,10 @@ class ArteTVFutureIE(ArteTVPlus7IE):
|
|||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://future.arte.tv/fr/sujet/info-sciences#article-anchor-7081',
|
'url': 'http://future.arte.tv/fr/sujet/info-sciences#article-anchor-7081',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '050940-003',
|
'id': '5201',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Les champignons au secours de la planète',
|
'title': 'Les champignons au secours de la planète',
|
||||||
|
'upload_date': '20131101',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -13,7 +13,7 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class BiliBiliIE(InfoExtractor):
|
class BiliBiliIE(InfoExtractor):
|
||||||
_VALID_URL = r'http://www\.bilibili\.tv/video/av(?P<id>[0-9]+)/'
|
_VALID_URL = r'http://www\.bilibili\.(?:tv|com)/video/av(?P<id>[0-9]+)/'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.bilibili.tv/video/av1074402/',
|
'url': 'http://www.bilibili.tv/video/av1074402/',
|
||||||
@@ -56,7 +56,7 @@ class BiliBiliIE(InfoExtractor):
|
|||||||
'thumbnailUrl', video_code, 'thumbnail', fatal=False)
|
'thumbnailUrl', video_code, 'thumbnail', fatal=False)
|
||||||
|
|
||||||
player_params = compat_parse_qs(self._html_search_regex(
|
player_params = compat_parse_qs(self._html_search_regex(
|
||||||
r'<iframe .*?class="player" src="https://secure.bilibili.tv/secure,([^"]+)"',
|
r'<iframe .*?class="player" src="https://secure\.bilibili\.(?:tv|com)/secure,([^"]+)"',
|
||||||
webpage, 'player params'))
|
webpage, 'player params'))
|
||||||
|
|
||||||
if 'cid' in player_params:
|
if 'cid' in player_params:
|
||||||
|
@@ -4,9 +4,7 @@ import json
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import remove_start
|
||||||
remove_start,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class BlinkxIE(InfoExtractor):
|
class BlinkxIE(InfoExtractor):
|
||||||
@@ -15,9 +13,10 @@ class BlinkxIE(InfoExtractor):
|
|||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.blinkx.com/ce/8aQUy7GVFYgFzpKhT0oqsilwOGFRVXk3R1ZGWWdGenBLaFQwb3FzaWx3OGFRVXk3R1ZGWWdGenB',
|
'url': 'http://www.blinkx.com/ce/8aQUy7GVFYgFzpKhT0oqsilwOGFRVXk3R1ZGWWdGenBLaFQwb3FzaWx3OGFRVXk3R1ZGWWdGenB',
|
||||||
'file': '8aQUy7GV.mp4',
|
|
||||||
'md5': '2e9a07364af40163a908edbf10bb2492',
|
'md5': '2e9a07364af40163a908edbf10bb2492',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'id': '8aQUy7GV',
|
||||||
|
'ext': 'mp4',
|
||||||
'title': 'Police Car Rolls Away',
|
'title': 'Police Car Rolls Away',
|
||||||
'uploader': 'stupidvideos.com',
|
'uploader': 'stupidvideos.com',
|
||||||
'upload_date': '20131215',
|
'upload_date': '20131215',
|
||||||
@@ -27,6 +26,7 @@ class BlinkxIE(InfoExtractor):
|
|||||||
'thumbnails': [{
|
'thumbnails': [{
|
||||||
'width': 100,
|
'width': 100,
|
||||||
'height': 76,
|
'height': 76,
|
||||||
|
'resolution': '100x76',
|
||||||
'url': 'http://cdn.blinkx.com/stream/b/41/StupidVideos/20131215/1873969261/1873969261_tn_0.jpg',
|
'url': 'http://cdn.blinkx.com/stream/b/41/StupidVideos/20131215/1873969261/1873969261_tn_0.jpg',
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
@@ -37,7 +37,7 @@ class BlinkxIE(InfoExtractor):
|
|||||||
video_id = m.group('id')
|
video_id = m.group('id')
|
||||||
display_id = video_id[:8]
|
display_id = video_id[:8]
|
||||||
|
|
||||||
api_url = (u'https://apib4.blinkx.com/api.php?action=play_video&' +
|
api_url = ('https://apib4.blinkx.com/api.php?action=play_video&' +
|
||||||
'video=%s' % video_id)
|
'video=%s' % video_id)
|
||||||
data_json = self._download_webpage(api_url, display_id)
|
data_json = self._download_webpage(api_url, display_id)
|
||||||
data = json.loads(data_json)['api']['results'][0]
|
data = json.loads(data_json)['api']['results'][0]
|
||||||
@@ -55,13 +55,13 @@ class BlinkxIE(InfoExtractor):
|
|||||||
duration = m['d']
|
duration = m['d']
|
||||||
elif m['type'] == 'youtube':
|
elif m['type'] == 'youtube':
|
||||||
yt_id = m['link']
|
yt_id = m['link']
|
||||||
self.to_screen(u'Youtube video detected: %s' % yt_id)
|
self.to_screen('Youtube video detected: %s' % yt_id)
|
||||||
return self.url_result(yt_id, 'Youtube', video_id=yt_id)
|
return self.url_result(yt_id, 'Youtube', video_id=yt_id)
|
||||||
elif m['type'] in ('flv', 'mp4'):
|
elif m['type'] in ('flv', 'mp4'):
|
||||||
vcodec = remove_start(m['vcodec'], 'ff')
|
vcodec = remove_start(m['vcodec'], 'ff')
|
||||||
acodec = remove_start(m['acodec'], 'ff')
|
acodec = remove_start(m['acodec'], 'ff')
|
||||||
tbr = (int(m['vbr']) + int(m['abr'])) // 1000
|
tbr = (int(m['vbr']) + int(m['abr'])) // 1000
|
||||||
format_id = u'%s-%sk-%s' % (vcodec, tbr, m['w'])
|
format_id = '%s-%sk-%s' % (vcodec, tbr, m['w'])
|
||||||
formats.append({
|
formats.append({
|
||||||
'format_id': format_id,
|
'format_id': format_id,
|
||||||
'url': m['link'],
|
'url': m['link'],
|
||||||
|
@@ -15,7 +15,7 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class BlipTVIE(SubtitlesInfoExtractor):
|
class BlipTVIE(SubtitlesInfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:\w+\.)?blip\.tv/(?:(?:.+-|rss/flash/)(?P<id>\d+)|((?:play/|api\.swf#)(?P<lookup_id>[\da-zA-Z]+)))'
|
_VALID_URL = r'https?://(?:\w+\.)?blip\.tv/(?:(?:.+-|rss/flash/)(?P<id>\d+)|((?:play/|api\.swf#)(?P<lookup_id>[\da-zA-Z+]+)))'
|
||||||
|
|
||||||
_TESTS = [
|
_TESTS = [
|
||||||
{
|
{
|
||||||
|
@@ -17,15 +17,13 @@ class BRIE(InfoExtractor):
|
|||||||
|
|
||||||
_TESTS = [
|
_TESTS = [
|
||||||
{
|
{
|
||||||
'url': 'http://www.br.de/mediathek/video/anselm-gruen-114.html',
|
'url': 'http://www.br.de/mediathek/video/sendungen/heimatsound/heimatsound-festival-2014-trailer-100.html',
|
||||||
'md5': 'c4f83cf0f023ba5875aba0bf46860df2',
|
'md5': '93556dd2bcb2948d9259f8670c516d59',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '2c8d81c5-6fb7-4a74-88d4-e768e5856532',
|
'id': '25e279aa-1ffd-40fd-9955-5325bd48a53a',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Feiern und Verzichten',
|
'title': 'Am 1. und 2. August in Oberammergau',
|
||||||
'description': 'Anselm Grün: Feiern und Verzichten',
|
'description': 'md5:dfd224e5aa6819bc1fcbb7826a932021',
|
||||||
'uploader': 'BR/Birgit Baier',
|
|
||||||
'upload_date': '20140301',
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@@ -15,6 +15,7 @@ from ..utils import (
|
|||||||
compat_urllib_request,
|
compat_urllib_request,
|
||||||
compat_parse_qs,
|
compat_parse_qs,
|
||||||
|
|
||||||
|
determine_ext,
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
unsmuggle_url,
|
unsmuggle_url,
|
||||||
unescapeHTML,
|
unescapeHTML,
|
||||||
@@ -29,10 +30,11 @@ class BrightcoveIE(InfoExtractor):
|
|||||||
{
|
{
|
||||||
# From http://www.8tv.cat/8aldia/videos/xavier-sala-i-martin-aquesta-tarda-a-8-al-dia/
|
# From http://www.8tv.cat/8aldia/videos/xavier-sala-i-martin-aquesta-tarda-a-8-al-dia/
|
||||||
'url': 'http://c.brightcove.com/services/viewer/htmlFederated?playerID=1654948606001&flashID=myExperience&%40videoPlayer=2371591881001',
|
'url': 'http://c.brightcove.com/services/viewer/htmlFederated?playerID=1654948606001&flashID=myExperience&%40videoPlayer=2371591881001',
|
||||||
'file': '2371591881001.mp4',
|
|
||||||
'md5': '5423e113865d26e40624dce2e4b45d95',
|
'md5': '5423e113865d26e40624dce2e4b45d95',
|
||||||
'note': 'Test Brightcove downloads and detection in GenericIE',
|
'note': 'Test Brightcove downloads and detection in GenericIE',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'id': '2371591881001',
|
||||||
|
'ext': 'mp4',
|
||||||
'title': 'Xavier Sala i Martín: “Un banc que no presta és un banc zombi que no serveix per a res”',
|
'title': 'Xavier Sala i Martín: “Un banc que no presta és un banc zombi que no serveix per a res”',
|
||||||
'uploader': '8TV',
|
'uploader': '8TV',
|
||||||
'description': 'md5:a950cc4285c43e44d763d036710cd9cd',
|
'description': 'md5:a950cc4285c43e44d763d036710cd9cd',
|
||||||
@@ -41,8 +43,9 @@ class BrightcoveIE(InfoExtractor):
|
|||||||
{
|
{
|
||||||
# From http://medianetwork.oracle.com/video/player/1785452137001
|
# From http://medianetwork.oracle.com/video/player/1785452137001
|
||||||
'url': 'http://c.brightcove.com/services/viewer/htmlFederated?playerID=1217746023001&flashID=myPlayer&%40videoPlayer=1785452137001',
|
'url': 'http://c.brightcove.com/services/viewer/htmlFederated?playerID=1217746023001&flashID=myPlayer&%40videoPlayer=1785452137001',
|
||||||
'file': '1785452137001.flv',
|
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'id': '1785452137001',
|
||||||
|
'ext': 'flv',
|
||||||
'title': 'JVMLS 2012: Arrays 2.0 - Opportunities and Challenges',
|
'title': 'JVMLS 2012: Arrays 2.0 - Opportunities and Challenges',
|
||||||
'description': 'John Rose speaks at the JVM Language Summit, August 1, 2012.',
|
'description': 'John Rose speaks at the JVM Language Summit, August 1, 2012.',
|
||||||
'uploader': 'Oracle',
|
'uploader': 'Oracle',
|
||||||
@@ -70,7 +73,20 @@ class BrightcoveIE(InfoExtractor):
|
|||||||
'description': 'md5:363109c02998fee92ec02211bd8000df',
|
'description': 'md5:363109c02998fee92ec02211bd8000df',
|
||||||
'uploader': 'National Ballet of Canada',
|
'uploader': 'National Ballet of Canada',
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
# test flv videos served by akamaihd.net
|
||||||
|
# From http://www.redbull.com/en/bike/stories/1331655643987/replay-uci-dh-world-cup-2014-from-fort-william
|
||||||
|
'url': 'http://c.brightcove.com/services/viewer/htmlFederated?%40videoPlayer=ref%3ABC2996102916001&linkBaseURL=http%3A%2F%2Fwww.redbull.com%2Fen%2Fbike%2Fvideos%2F1331655630249%2Freplay-uci-fort-william-2014-dh&playerKey=AQ%7E%7E%2CAAAApYJ7UqE%7E%2Cxqr_zXk0I-zzNndy8NlHogrCb5QdyZRf&playerID=1398061561001#__youtubedl_smuggle=%7B%22Referer%22%3A+%22http%3A%2F%2Fwww.redbull.com%2Fen%2Fbike%2Fstories%2F1331655643987%2Freplay-uci-dh-world-cup-2014-from-fort-william%22%7D',
|
||||||
|
# The md5 checksum changes on each download
|
||||||
|
'info_dict': {
|
||||||
|
'id': '2996102916001',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'UCI MTB World Cup 2014: Fort William, UK - Downhill Finals',
|
||||||
|
'uploader': 'Red Bull TV',
|
||||||
|
'description': 'UCI MTB World Cup 2014: Fort William, UK - Downhill Finals',
|
||||||
|
},
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -187,7 +203,7 @@ class BrightcoveIE(InfoExtractor):
|
|||||||
webpage = self._download_webpage(req, video_id)
|
webpage = self._download_webpage(req, video_id)
|
||||||
|
|
||||||
self.report_extraction(video_id)
|
self.report_extraction(video_id)
|
||||||
info = self._search_regex(r'var experienceJSON = ({.*?});', webpage, 'json')
|
info = self._search_regex(r'var experienceJSON = ({.*});', webpage, 'json')
|
||||||
info = json.loads(info)['data']
|
info = json.loads(info)['data']
|
||||||
video_info = info['programmedContent']['videoPlayer']['mediaDTO']
|
video_info = info['programmedContent']['videoPlayer']['mediaDTO']
|
||||||
video_info['_youtubedl_adServerURL'] = info.get('adServerURL')
|
video_info['_youtubedl_adServerURL'] = info.get('adServerURL')
|
||||||
@@ -219,12 +235,26 @@ class BrightcoveIE(InfoExtractor):
|
|||||||
|
|
||||||
renditions = video_info.get('renditions')
|
renditions = video_info.get('renditions')
|
||||||
if renditions:
|
if renditions:
|
||||||
renditions = sorted(renditions, key=lambda r: r['size'])
|
formats = []
|
||||||
info['formats'] = [{
|
for rend in renditions:
|
||||||
'url': rend['defaultURL'],
|
url = rend['defaultURL']
|
||||||
'height': rend.get('frameHeight'),
|
if rend['remote']:
|
||||||
'width': rend.get('frameWidth'),
|
# This type of renditions are served through akamaihd.net,
|
||||||
} for rend in renditions]
|
# but they don't use f4m manifests
|
||||||
|
url = url.replace('control/', '') + '?&v=3.3.0&fp=13&r=FEEFJ&g=RTSJIMBMPFPB'
|
||||||
|
ext = 'flv'
|
||||||
|
else:
|
||||||
|
ext = determine_ext(url)
|
||||||
|
size = rend.get('size')
|
||||||
|
formats.append({
|
||||||
|
'url': url,
|
||||||
|
'ext': ext,
|
||||||
|
'height': rend.get('frameHeight'),
|
||||||
|
'width': rend.get('frameWidth'),
|
||||||
|
'filesize': size if size != 0 else None,
|
||||||
|
})
|
||||||
|
self._sort_formats(formats)
|
||||||
|
info['formats'] = formats
|
||||||
elif video_info.get('FLVFullLengthURL') is not None:
|
elif video_info.get('FLVFullLengthURL') is not None:
|
||||||
info.update({
|
info.update({
|
||||||
'url': video_info['FLVFullLengthURL'],
|
'url': video_info['FLVFullLengthURL'],
|
||||||
|
@@ -1,19 +1,19 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
from .mtv import MTVIE
|
from .mtv import MTVIE
|
||||||
|
|
||||||
|
|
||||||
class CMTIE(MTVIE):
|
class CMTIE(MTVIE):
|
||||||
IE_NAME = u'cmt.com'
|
IE_NAME = 'cmt.com'
|
||||||
_VALID_URL = r'https?://www\.cmt\.com/videos/.+?/(?P<videoid>[^/]+)\.jhtml'
|
_VALID_URL = r'https?://www\.cmt\.com/videos/.+?/(?P<videoid>[^/]+)\.jhtml'
|
||||||
_FEED_URL = 'http://www.cmt.com/sitewide/apps/player/embed/rss/'
|
_FEED_URL = 'http://www.cmt.com/sitewide/apps/player/embed/rss/'
|
||||||
|
|
||||||
_TESTS = [
|
_TESTS = [{
|
||||||
{
|
'url': 'http://www.cmt.com/videos/garth-brooks/989124/the-call-featuring-trisha-yearwood.jhtml#artist=30061',
|
||||||
u'url': u'http://www.cmt.com/videos/garth-brooks/989124/the-call-featuring-trisha-yearwood.jhtml#artist=30061',
|
'md5': 'e6b7ef3c4c45bbfae88061799bbba6c2',
|
||||||
u'md5': u'e6b7ef3c4c45bbfae88061799bbba6c2',
|
'info_dict': {
|
||||||
u'info_dict': {
|
'id': '989124',
|
||||||
u'id': u'989124',
|
'ext': 'mp4',
|
||||||
u'ext': u'mp4',
|
'title': 'Garth Brooks - "The Call (featuring Trisha Yearwood)"',
|
||||||
u'title': u'Garth Brooks - "The Call (featuring Trisha Yearwood)"',
|
'description': 'Blame It All On My Roots',
|
||||||
u'description': u'Blame It All On My Roots',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
]
|
}]
|
||||||
|
@@ -79,8 +79,11 @@ class CNNIE(InfoExtractor):
|
|||||||
|
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
thumbnails = sorted([((int(t.attrib['height']),int(t.attrib['width'])), t.text) for t in info.findall('images/image')])
|
thumbnails = [{
|
||||||
thumbs_dict = [{'resolution': res, 'url': t_url} for (res, t_url) in thumbnails]
|
'height': int(t.attrib['height']),
|
||||||
|
'width': int(t.attrib['width']),
|
||||||
|
'url': t.text,
|
||||||
|
} for t in info.findall('images/image')]
|
||||||
|
|
||||||
metas_el = info.find('metas')
|
metas_el = info.find('metas')
|
||||||
upload_date = (
|
upload_date = (
|
||||||
@@ -93,8 +96,7 @@ class CNNIE(InfoExtractor):
|
|||||||
'id': info.attrib['id'],
|
'id': info.attrib['id'],
|
||||||
'title': info.find('headline').text,
|
'title': info.find('headline').text,
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
'thumbnail': thumbnails[-1][1],
|
'thumbnails': thumbnails,
|
||||||
'thumbnails': thumbs_dict,
|
|
||||||
'description': info.find('description').text,
|
'description': info.find('description').text,
|
||||||
'duration': duration,
|
'duration': duration,
|
||||||
'upload_date': upload_date,
|
'upload_date': upload_date,
|
||||||
|
@@ -130,7 +130,7 @@ class ComedyCentralShowsIE(InfoExtractor):
|
|||||||
raise ExtractorError('Invalid redirected URL: ' + url)
|
raise ExtractorError('Invalid redirected URL: ' + url)
|
||||||
if mobj.group('episode') == '':
|
if mobj.group('episode') == '':
|
||||||
raise ExtractorError('Redirected URL is still not specific: ' + url)
|
raise ExtractorError('Redirected URL is still not specific: ' + url)
|
||||||
epTitle = mobj.group('episode').rpartition('/')[-1]
|
epTitle = (mobj.group('episode') or mobj.group('videotitle')).rpartition('/')[-1]
|
||||||
|
|
||||||
mMovieParams = re.findall('(?:<param name="movie" value="|var url = ")(http://media.mtvnservices.com/([^"]*(?:episode|video).*?:.*?))"', webpage)
|
mMovieParams = re.findall('(?:<param name="movie" value="|var url = ")(http://media.mtvnservices.com/([^"]*(?:episode|video).*?:.*?))"', webpage)
|
||||||
if len(mMovieParams) == 0:
|
if len(mMovieParams) == 0:
|
||||||
|
@@ -1,11 +1,12 @@
|
|||||||
import base64
|
import base64
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
|
import netrc
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
import netrc
|
import time
|
||||||
import xml.etree.ElementTree
|
import xml.etree.ElementTree
|
||||||
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
@@ -92,8 +93,12 @@ class InfoExtractor(object):
|
|||||||
unique, but available before title. Typically, id is
|
unique, but available before title. Typically, id is
|
||||||
something like "4234987", title "Dancing naked mole rats",
|
something like "4234987", title "Dancing naked mole rats",
|
||||||
and display_id "dancing-naked-mole-rats"
|
and display_id "dancing-naked-mole-rats"
|
||||||
thumbnails: A list of dictionaries (with the entries "resolution" and
|
thumbnails: A list of dictionaries, with the following entries:
|
||||||
"url") for the varying thumbnails
|
* "url"
|
||||||
|
* "width" (optional, int)
|
||||||
|
* "height" (optional, int)
|
||||||
|
* "resolution" (optional, string "{width}x{height"},
|
||||||
|
deprecated)
|
||||||
thumbnail: Full URL to a video thumbnail image.
|
thumbnail: Full URL to a video thumbnail image.
|
||||||
description: One-line video description.
|
description: One-line video description.
|
||||||
uploader: Full name of the video uploader.
|
uploader: Full name of the video uploader.
|
||||||
@@ -455,14 +460,17 @@ class InfoExtractor(object):
|
|||||||
if secure: regexes = self._og_regexes('video:secure_url') + regexes
|
if secure: regexes = self._og_regexes('video:secure_url') + regexes
|
||||||
return self._html_search_regex(regexes, html, name, **kargs)
|
return self._html_search_regex(regexes, html, name, **kargs)
|
||||||
|
|
||||||
def _html_search_meta(self, name, html, display_name=None, fatal=False):
|
def _og_search_url(self, html, **kargs):
|
||||||
|
return self._og_search_property('url', html, **kargs)
|
||||||
|
|
||||||
|
def _html_search_meta(self, name, html, display_name=None, fatal=False, **kwargs):
|
||||||
if display_name is None:
|
if display_name is None:
|
||||||
display_name = name
|
display_name = name
|
||||||
return self._html_search_regex(
|
return self._html_search_regex(
|
||||||
r'''(?ix)<meta
|
r'''(?ix)<meta
|
||||||
(?=[^>]+(?:itemprop|name|property)=["\']%s["\'])
|
(?=[^>]+(?:itemprop|name|property)=["\']%s["\'])
|
||||||
[^>]+content=["\']([^"\']+)["\']''' % re.escape(name),
|
[^>]+content=["\']([^"\']+)["\']''' % re.escape(name),
|
||||||
html, display_name, fatal=fatal)
|
html, display_name, fatal=fatal, **kwargs)
|
||||||
|
|
||||||
def _dc_search_uploader(self, html):
|
def _dc_search_uploader(self, html):
|
||||||
return self._html_search_meta('dc.creator', html, 'uploader')
|
return self._html_search_meta('dc.creator', html, 'uploader')
|
||||||
@@ -568,6 +576,13 @@ class InfoExtractor(object):
|
|||||||
else:
|
else:
|
||||||
return url
|
return url
|
||||||
|
|
||||||
|
def _sleep(self, timeout, video_id, msg_template=None):
|
||||||
|
if msg_template is None:
|
||||||
|
msg_template = u'%(video_id)s: Waiting for %(timeout)s seconds'
|
||||||
|
msg = msg_template % {'video_id': video_id, 'timeout': timeout}
|
||||||
|
self.to_screen(msg)
|
||||||
|
time.sleep(timeout)
|
||||||
|
|
||||||
|
|
||||||
class SearchInfoExtractor(InfoExtractor):
|
class SearchInfoExtractor(InfoExtractor):
|
||||||
"""
|
"""
|
||||||
@@ -611,4 +626,3 @@ class SearchInfoExtractor(InfoExtractor):
|
|||||||
@property
|
@property
|
||||||
def SEARCH_KEY(self):
|
def SEARCH_KEY(self):
|
||||||
return self._SEARCH_KEY
|
return self._SEARCH_KEY
|
||||||
|
|
||||||
|
@@ -1,40 +1,43 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import determine_ext
|
|
||||||
|
|
||||||
class CriterionIE(InfoExtractor):
|
class CriterionIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://www\.criterion\.com/films/(\d*)-.+'
|
_VALID_URL = r'https?://www\.criterion\.com/films/(?P<id>[0-9]+)-.+'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
u'url': u'http://www.criterion.com/films/184-le-samourai',
|
'url': 'http://www.criterion.com/films/184-le-samourai',
|
||||||
u'file': u'184.mp4',
|
'md5': 'bc51beba55685509883a9a7830919ec3',
|
||||||
u'md5': u'bc51beba55685509883a9a7830919ec3',
|
'info_dict': {
|
||||||
u'info_dict': {
|
'id': '184',
|
||||||
u"title": u"Le Samouraï",
|
'ext': 'mp4',
|
||||||
u"description" : u'md5:a2b4b116326558149bef81f76dcbb93f',
|
'title': 'Le Samouraï',
|
||||||
|
'description': 'md5:a2b4b116326558149bef81f76dcbb93f',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
video_id = mobj.group(1)
|
video_id = mobj.group('id')
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
final_url = self._search_regex(r'so.addVariable\("videoURL", "(.+?)"\)\;',
|
final_url = self._search_regex(
|
||||||
webpage, 'video url')
|
r'so.addVariable\("videoURL", "(.+?)"\)\;', webpage, 'video url')
|
||||||
title = self._html_search_regex(r'<meta content="(.+?)" property="og:title" />',
|
title = self._og_search_title(webpage)
|
||||||
webpage, 'video title')
|
description = self._html_search_regex(
|
||||||
description = self._html_search_regex(r'<meta name="description" content="(.+?)" />',
|
r'<meta name="description" content="(.+?)" />',
|
||||||
webpage, 'video description')
|
webpage, 'video description')
|
||||||
thumbnail = self._search_regex(r'so.addVariable\("thumbnailURL", "(.+?)"\)\;',
|
thumbnail = self._search_regex(
|
||||||
webpage, 'thumbnail url')
|
r'so.addVariable\("thumbnailURL", "(.+?)"\)\;',
|
||||||
|
webpage, 'thumbnail url')
|
||||||
|
|
||||||
return {'id': video_id,
|
return {
|
||||||
'url' : final_url,
|
'id': video_id,
|
||||||
'title': title,
|
'url': final_url,
|
||||||
'ext': determine_ext(final_url),
|
'title': title,
|
||||||
'description': description,
|
'description': description,
|
||||||
'thumbnail': thumbnail,
|
'thumbnail': thumbnail,
|
||||||
}
|
}
|
||||||
|
@@ -150,7 +150,7 @@ class DailymotionIE(DailymotionBaseInfoExtractor, SubtitlesInfoExtractor):
|
|||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
'uploader': info['owner_screenname'],
|
'uploader': info['owner.screenname'],
|
||||||
'upload_date': video_upload_date,
|
'upload_date': video_upload_date,
|
||||||
'title': self._og_search_title(webpage),
|
'title': self._og_search_title(webpage),
|
||||||
'subtitles': video_subtitles,
|
'subtitles': video_subtitles,
|
||||||
|
@@ -7,9 +7,9 @@ from .common import InfoExtractor
|
|||||||
|
|
||||||
|
|
||||||
class DiscoveryIE(InfoExtractor):
|
class DiscoveryIE(InfoExtractor):
|
||||||
_VALID_URL = r'http://dsc\.discovery\.com\/[a-zA-Z0-9\-]*/[a-zA-Z0-9\-]*/videos/(?P<id>[a-zA-Z0-9\-]*)(.htm)?'
|
_VALID_URL = r'http://www\.discovery\.com\/[a-zA-Z0-9\-]*/[a-zA-Z0-9\-]*/videos/(?P<id>[a-zA-Z0-9\-]*)(.htm)?'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://dsc.discovery.com/tv-shows/mythbusters/videos/mission-impossible-outtakes.htm',
|
'url': 'http://www.discovery.com/tv-shows/mythbusters/videos/mission-impossible-outtakes.htm',
|
||||||
'md5': 'e12614f9ee303a6ccef415cb0793eba2',
|
'md5': 'e12614f9ee303a6ccef415cb0793eba2',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '614784',
|
'id': '614784',
|
||||||
|
@@ -1,39 +1,37 @@
|
|||||||
# coding: utf-8
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import unified_strdate
|
||||||
unified_strdate,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class DreiSatIE(InfoExtractor):
|
class DreiSatIE(InfoExtractor):
|
||||||
IE_NAME = '3sat'
|
IE_NAME = '3sat'
|
||||||
_VALID_URL = r'(?:http://)?(?:www\.)?3sat\.de/mediathek/(?:index\.php)?\?(?:(?:mode|display)=[^&]+&)*obj=(?P<id>[0-9]+)$'
|
_VALID_URL = r'(?:http://)?(?:www\.)?3sat\.de/mediathek/(?:index\.php)?\?(?:(?:mode|display)=[^&]+&)*obj=(?P<id>[0-9]+)$'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
u"url": u"http://www.3sat.de/mediathek/index.php?obj=36983",
|
'url': 'http://www.3sat.de/mediathek/index.php?obj=36983',
|
||||||
u'file': u'36983.mp4',
|
'md5': '9dcfe344732808dbfcc901537973c922',
|
||||||
u'md5': u'9dcfe344732808dbfcc901537973c922',
|
'info_dict': {
|
||||||
u'info_dict': {
|
'id': '36983',
|
||||||
u"title": u"Kaffeeland Schweiz",
|
'ext': 'mp4',
|
||||||
u"description": u"Über 80 Kaffeeröstereien liefern in der Schweiz das Getränk, in das das Land so vernarrt ist: Mehr als 1000 Tassen trinkt ein Schweizer pro Jahr. SCHWEIZWEIT nimmt die Kaffeekultur unter die...",
|
'title': 'Kaffeeland Schweiz',
|
||||||
u"uploader": u"3sat",
|
'description': 'md5:cc4424b18b75ae9948b13929a0814033',
|
||||||
u"upload_date": u"20130622"
|
'uploader': '3sat',
|
||||||
|
'upload_date': '20130622'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
video_id = mobj.group('id')
|
video_id = mobj.group('id')
|
||||||
details_url = 'http://www.3sat.de/mediathek/xmlservice/web/beitragsDetails?ak=web&id=%s' % video_id
|
details_url = 'http://www.3sat.de/mediathek/xmlservice/web/beitragsDetails?ak=web&id=%s' % video_id
|
||||||
details_doc = self._download_xml(details_url, video_id, note=u'Downloading video details')
|
details_doc = self._download_xml(details_url, video_id, 'Downloading video details')
|
||||||
|
|
||||||
thumbnail_els = details_doc.findall('.//teaserimage')
|
thumbnail_els = details_doc.findall('.//teaserimage')
|
||||||
thumbnails = [{
|
thumbnails = [{
|
||||||
'width': te.attrib['key'].partition('x')[0],
|
'width': int(te.attrib['key'].partition('x')[0]),
|
||||||
'height': te.attrib['key'].partition('x')[2],
|
'height': int(te.attrib['key'].partition('x')[2]),
|
||||||
'url': te.text,
|
'url': te.text,
|
||||||
} for te in thumbnail_els]
|
} for te in thumbnail_els]
|
||||||
|
|
||||||
|
91
youtube_dl/extractor/drtv.py
Normal file
91
youtube_dl/extractor/drtv.py
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .subtitles import SubtitlesInfoExtractor
|
||||||
|
from .common import ExtractorError
|
||||||
|
from ..utils import parse_iso8601
|
||||||
|
|
||||||
|
|
||||||
|
class DRTVIE(SubtitlesInfoExtractor):
|
||||||
|
_VALID_URL = r'http://(?:www\.)?dr\.dk/tv/se/[^/]+/(?P<id>[\da-z-]+)'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.dr.dk/tv/se/partiets-mand/partiets-mand-7-8',
|
||||||
|
'md5': '4a7e1dd65cdb2643500a3f753c942f25',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'partiets-mand-7-8',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Partiets mand (7:8)',
|
||||||
|
'description': 'md5:a684b90a8f9336cd4aab94b7647d7862',
|
||||||
|
'timestamp': 1403047940,
|
||||||
|
'upload_date': '20140617',
|
||||||
|
'duration': 1299.040,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
programcard = self._download_json(
|
||||||
|
'http://www.dr.dk/mu/programcard/expanded/%s' % video_id, video_id, 'Downloading video JSON')
|
||||||
|
|
||||||
|
data = programcard['Data'][0]
|
||||||
|
|
||||||
|
title = data['Title']
|
||||||
|
description = data['Description']
|
||||||
|
timestamp = parse_iso8601(data['CreatedTime'][:-5])
|
||||||
|
|
||||||
|
thumbnail = None
|
||||||
|
duration = None
|
||||||
|
|
||||||
|
restricted_to_denmark = False
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
subtitles = {}
|
||||||
|
|
||||||
|
for asset in data['Assets']:
|
||||||
|
if asset['Kind'] == 'Image':
|
||||||
|
thumbnail = asset['Uri']
|
||||||
|
elif asset['Kind'] == 'VideoResource':
|
||||||
|
duration = asset['DurationInMilliseconds'] / 1000.0
|
||||||
|
restricted_to_denmark = asset['RestrictedToDenmark']
|
||||||
|
for link in asset['Links']:
|
||||||
|
target = link['Target']
|
||||||
|
uri = link['Uri']
|
||||||
|
formats.append({
|
||||||
|
'url': uri + '?hdcore=3.3.0&plugin=aasp-3.3.0.99.43' if target == 'HDS' else uri,
|
||||||
|
'format_id': target,
|
||||||
|
'ext': link['FileFormat'],
|
||||||
|
'preference': -1 if target == 'HDS' else -2,
|
||||||
|
})
|
||||||
|
subtitles_list = asset.get('SubtitlesList')
|
||||||
|
if isinstance(subtitles_list, list):
|
||||||
|
LANGS = {
|
||||||
|
'Danish': 'dk',
|
||||||
|
}
|
||||||
|
for subs in subtitles_list:
|
||||||
|
lang = subs['Language']
|
||||||
|
subtitles[LANGS.get(lang, lang)] = subs['Uri']
|
||||||
|
|
||||||
|
if not formats and restricted_to_denmark:
|
||||||
|
raise ExtractorError(
|
||||||
|
'Unfortunately, DR is not allowed to show this program outside Denmark.', expected=True)
|
||||||
|
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
if self._downloader.params.get('listsubtitles', False):
|
||||||
|
self._list_available_subtitles(video_id, subtitles)
|
||||||
|
return
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'timestamp': timestamp,
|
||||||
|
'duration': duration,
|
||||||
|
'formats': formats,
|
||||||
|
'subtitles': self.extract_subtitles(video_id, subtitles),
|
||||||
|
}
|
@@ -37,7 +37,7 @@ class ExtremeTubeIE(InfoExtractor):
|
|||||||
webpage = self._download_webpage(req, video_id)
|
webpage = self._download_webpage(req, video_id)
|
||||||
|
|
||||||
video_title = self._html_search_regex(
|
video_title = self._html_search_regex(
|
||||||
r'<h1 [^>]*?title="([^"]+)"[^>]*>\1<', webpage, 'title')
|
r'<h1 [^>]*?title="([^"]+)"[^>]*>', webpage, 'title')
|
||||||
uploader = self._html_search_regex(
|
uploader = self._html_search_regex(
|
||||||
r'>Posted by:(?=<)(?:\s|<[^>]*>)*(.+?)\|', webpage, 'uploader',
|
r'>Posted by:(?=<)(?:\s|<[^>]*>)*(.+?)\|', webpage, 'uploader',
|
||||||
fatal=False)
|
fatal=False)
|
||||||
|
@@ -50,10 +50,13 @@ class FC2IE(InfoExtractor):
|
|||||||
raise ExtractorError('Error code: %s' % info['err_code'][0])
|
raise ExtractorError('Error code: %s' % info['err_code'][0])
|
||||||
|
|
||||||
video_url = info['filepath'][0] + '?mid=' + info['mid'][0]
|
video_url = info['filepath'][0] + '?mid=' + info['mid'][0]
|
||||||
|
title_info = info.get('title')
|
||||||
|
if title_info:
|
||||||
|
title = title_info[0]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': info['title'][0],
|
'title': title,
|
||||||
'url': video_url,
|
'url': video_url,
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'thumbnail': thumbnail,
|
'thumbnail': thumbnail,
|
||||||
|
83
youtube_dl/extractor/firedrive.py
Normal file
83
youtube_dl/extractor/firedrive.py
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
ExtractorError,
|
||||||
|
compat_urllib_parse,
|
||||||
|
compat_urllib_request,
|
||||||
|
determine_ext,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class FiredriveIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?firedrive\.com/' + \
|
||||||
|
'(?:file|embed)/(?P<id>[0-9a-zA-Z]+)'
|
||||||
|
_FILE_DELETED_REGEX = r'<div class="removed_file_image">'
|
||||||
|
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://www.firedrive.com/file/FEB892FA160EBD01',
|
||||||
|
'md5': 'd5d4252f80ebeab4dc2d5ceaed1b7970',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'FEB892FA160EBD01',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'bbb_theora_486kbit.flv',
|
||||||
|
'thumbnail': 're:^http://.*\.jpg$',
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
url = 'http://firedrive.com/file/%s' % video_id
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
if re.search(self._FILE_DELETED_REGEX, webpage) is not None:
|
||||||
|
raise ExtractorError('Video %s does not exist' % video_id,
|
||||||
|
expected=True)
|
||||||
|
|
||||||
|
fields = dict(re.findall(r'''(?x)<input\s+
|
||||||
|
type="hidden"\s+
|
||||||
|
name="([^"]+)"\s+
|
||||||
|
(?:id="[^"]+"\s+)?
|
||||||
|
value="([^"]*)"
|
||||||
|
''', webpage))
|
||||||
|
|
||||||
|
post = compat_urllib_parse.urlencode(fields)
|
||||||
|
req = compat_urllib_request.Request(url, post)
|
||||||
|
req.add_header('Content-type', 'application/x-www-form-urlencoded')
|
||||||
|
|
||||||
|
# Apparently, this header is required for confirmation to work.
|
||||||
|
req.add_header('Host', 'www.firedrive.com')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(req, video_id,
|
||||||
|
'Downloading video page')
|
||||||
|
|
||||||
|
title = self._search_regex(r'class="external_title_left">(.+)</div>',
|
||||||
|
webpage, 'title')
|
||||||
|
thumbnail = self._search_regex(r'image:\s?"(//[^\"]+)', webpage,
|
||||||
|
'thumbnail', fatal=False)
|
||||||
|
if thumbnail is not None:
|
||||||
|
thumbnail = 'http:' + thumbnail
|
||||||
|
|
||||||
|
ext = self._search_regex(r'type:\s?\'([^\']+)\',',
|
||||||
|
webpage, 'extension', fatal=False)
|
||||||
|
video_url = self._search_regex(
|
||||||
|
r'file:\s?\'(http[^\']+)\',', webpage, 'file url')
|
||||||
|
|
||||||
|
formats = [{
|
||||||
|
'format_id': 'sd',
|
||||||
|
'url': video_url,
|
||||||
|
'ext': ext,
|
||||||
|
}]
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'formats': formats,
|
||||||
|
}
|
@@ -15,6 +15,7 @@ class FirstpostIE(InfoExtractor):
|
|||||||
'id': '1025403',
|
'id': '1025403',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'India to launch indigenous aircraft carrier INS Vikrant today',
|
'title': 'India to launch indigenous aircraft carrier INS Vikrant today',
|
||||||
|
'description': 'md5:feef3041cb09724e0bdc02843348f5f4',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,13 +23,16 @@ class FirstpostIE(InfoExtractor):
|
|||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
video_id = mobj.group('id')
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
page = self._download_webpage(url, video_id)
|
||||||
|
title = self._html_search_meta('twitter:title', page, 'title')
|
||||||
|
description = self._html_search_meta('twitter:description', page, 'title')
|
||||||
|
|
||||||
data = self._download_xml(
|
data = self._download_xml(
|
||||||
'http://www.firstpost.com/getvideoxml-%s.xml' % video_id, video_id,
|
'http://www.firstpost.com/getvideoxml-%s.xml' % video_id, video_id,
|
||||||
'Downloading video XML')
|
'Downloading video XML')
|
||||||
|
|
||||||
item = data.find('./playlist/item')
|
item = data.find('./playlist/item')
|
||||||
thumbnail = item.find('./image').text
|
thumbnail = item.find('./image').text
|
||||||
title = item.find('./title').text
|
|
||||||
|
|
||||||
formats = [
|
formats = [
|
||||||
{
|
{
|
||||||
@@ -42,6 +46,7 @@ class FirstpostIE(InfoExtractor):
|
|||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': title,
|
'title': title,
|
||||||
|
'description': description,
|
||||||
'thumbnail': thumbnail,
|
'thumbnail': thumbnail,
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
}
|
}
|
||||||
|
90
youtube_dl/extractor/gameone.py
Normal file
90
youtube_dl/extractor/gameone.py
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
xpath_with_ns,
|
||||||
|
parse_iso8601
|
||||||
|
)
|
||||||
|
|
||||||
|
NAMESPACE_MAP = {
|
||||||
|
'media': 'http://search.yahoo.com/mrss/',
|
||||||
|
}
|
||||||
|
|
||||||
|
# URL prefix to download the mp4 files directly instead of streaming via rtmp
|
||||||
|
# Credits go to XBox-Maniac
|
||||||
|
# http://board.jdownloader.org/showpost.php?p=185835&postcount=31
|
||||||
|
RAW_MP4_URL = 'http://cdn.riptide-mtvn.com/'
|
||||||
|
|
||||||
|
|
||||||
|
class GameOneIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?gameone\.de/tv/(?P<id>\d+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.gameone.de/tv/288',
|
||||||
|
'md5': '136656b7fb4c9cb4a8e2d500651c499b',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '288',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Game One - Folge 288',
|
||||||
|
'duration': 1238,
|
||||||
|
'thumbnail': 'http://s3.gameone.de/gameone/assets/video_metas/teaser_images/000/643/636/big/640x360.jpg',
|
||||||
|
'description': 'FIFA-Pressepokal 2014, Star Citizen, Kingdom Come: Deliverance, Project Cars, Schöner Trants Nerdquiz Folge 2 Runde 1',
|
||||||
|
'age_limit': 16,
|
||||||
|
'upload_date': '20140513',
|
||||||
|
'timestamp': 1399980122,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
og_video = self._og_search_video_url(webpage, secure=False)
|
||||||
|
description = self._html_search_meta('description', webpage)
|
||||||
|
age_limit = int(
|
||||||
|
self._search_regex(
|
||||||
|
r'age=(\d+)',
|
||||||
|
self._html_search_meta(
|
||||||
|
'age-de-meta-label',
|
||||||
|
webpage),
|
||||||
|
'age_limit',
|
||||||
|
'0'))
|
||||||
|
mrss_url = self._search_regex(r'mrss=([^&]+)', og_video, 'mrss')
|
||||||
|
|
||||||
|
mrss = self._download_xml(mrss_url, video_id, 'Downloading mrss')
|
||||||
|
title = mrss.find('.//item/title').text
|
||||||
|
thumbnail = mrss.find('.//item/image').get('url')
|
||||||
|
timestamp = parse_iso8601(mrss.find('.//pubDate').text, delimiter=' ')
|
||||||
|
content = mrss.find(xpath_with_ns('.//media:content', NAMESPACE_MAP))
|
||||||
|
content_url = content.get('url')
|
||||||
|
|
||||||
|
content = self._download_xml(
|
||||||
|
content_url,
|
||||||
|
video_id,
|
||||||
|
'Downloading media:content')
|
||||||
|
rendition_items = content.findall('.//rendition')
|
||||||
|
duration = int(rendition_items[0].get('duration'))
|
||||||
|
formats = [
|
||||||
|
{
|
||||||
|
'url': re.sub(r'.*/(r2)', RAW_MP4_URL + r'\1', r.find('./src').text),
|
||||||
|
'width': int(r.get('width')),
|
||||||
|
'height': int(r.get('height')),
|
||||||
|
'tbr': int(r.get('bitrate')),
|
||||||
|
}
|
||||||
|
for r in rendition_items
|
||||||
|
]
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'duration': duration,
|
||||||
|
'formats': formats,
|
||||||
|
'description': description,
|
||||||
|
'age_limit': age_limit,
|
||||||
|
'timestamp': timestamp,
|
||||||
|
}
|
@@ -260,7 +260,35 @@ class GenericIE(InfoExtractor):
|
|||||||
'uploader': 'Spi0n',
|
'uploader': 'Spi0n',
|
||||||
},
|
},
|
||||||
'add_ie': ['Dailymotion'],
|
'add_ie': ['Dailymotion'],
|
||||||
}
|
},
|
||||||
|
# YouTube embed
|
||||||
|
{
|
||||||
|
'url': 'http://www.badzine.de/ansicht/datum/2014/06/09/so-funktioniert-die-neue-englische-badminton-liga.html',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'FXRb4ykk4S0',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'The NBL Auction 2014',
|
||||||
|
'uploader': 'BADMINTON England',
|
||||||
|
'uploader_id': 'BADMINTONEvents',
|
||||||
|
'upload_date': '20140603',
|
||||||
|
'description': 'md5:9ef128a69f1e262a700ed83edb163a73',
|
||||||
|
},
|
||||||
|
'add_ie': ['Youtube'],
|
||||||
|
'params': {
|
||||||
|
'skip_download': True,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
# MTVSercices embed
|
||||||
|
{
|
||||||
|
'url': 'http://www.gametrailers.com/news-post/76093/north-america-europe-is-getting-that-mario-kart-8-mercedes-dlc-too',
|
||||||
|
'md5': '35727f82f58c76d996fc188f9755b0d5',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '0306a69b-8adf-4fb5-aace-75f8e8cbfca9',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Review',
|
||||||
|
'description': 'Mario\'s life in the fast lane has never looked so good.',
|
||||||
|
},
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
def report_download_webpage(self, video_id):
|
def report_download_webpage(self, video_id):
|
||||||
@@ -355,7 +383,7 @@ class GenericIE(InfoExtractor):
|
|||||||
if not parsed_url.scheme:
|
if not parsed_url.scheme:
|
||||||
default_search = self._downloader.params.get('default_search')
|
default_search = self._downloader.params.get('default_search')
|
||||||
if default_search is None:
|
if default_search is None:
|
||||||
default_search = 'auto_warning'
|
default_search = 'error'
|
||||||
|
|
||||||
if default_search in ('auto', 'auto_warning'):
|
if default_search in ('auto', 'auto_warning'):
|
||||||
if '/' in url:
|
if '/' in url:
|
||||||
@@ -369,8 +397,13 @@ class GenericIE(InfoExtractor):
|
|||||||
expected=True)
|
expected=True)
|
||||||
else:
|
else:
|
||||||
self._downloader.report_warning(
|
self._downloader.report_warning(
|
||||||
'Falling back to youtube search for %s . Set --default-search to "auto" to suppress this warning.' % url)
|
'Falling back to youtube search for %s . Set --default-search "auto" to suppress this warning.' % url)
|
||||||
return self.url_result('ytsearch:' + url)
|
return self.url_result('ytsearch:' + url)
|
||||||
|
elif default_search == 'error':
|
||||||
|
raise ExtractorError(
|
||||||
|
('%r is not a valid URL. '
|
||||||
|
'Set --default-search "ytseach" (or run youtube-dl "ytsearch:%s" ) to search YouTube'
|
||||||
|
) % (url, url), expected=True)
|
||||||
else:
|
else:
|
||||||
assert ':' in default_search
|
assert ':' in default_search
|
||||||
return self.url_result(default_search + url)
|
return self.url_result(default_search + url)
|
||||||
@@ -478,8 +511,13 @@ class GenericIE(InfoExtractor):
|
|||||||
|
|
||||||
# Look for embedded YouTube player
|
# Look for embedded YouTube player
|
||||||
matches = re.findall(r'''(?x)
|
matches = re.findall(r'''(?x)
|
||||||
(?:<iframe[^>]+?src=|embedSWF\(\s*)
|
(?:
|
||||||
(["\'])(?P<url>(?:https?:)?//(?:www\.)?youtube\.com/
|
<iframe[^>]+?src=|
|
||||||
|
<embed[^>]+?src=|
|
||||||
|
embedSWF\(?:\s*
|
||||||
|
)
|
||||||
|
(["\'])
|
||||||
|
(?P<url>(?:https?:)?//(?:www\.)?youtube\.com/
|
||||||
(?:embed|v)/.+?)
|
(?:embed|v)/.+?)
|
||||||
\1''', webpage)
|
\1''', webpage)
|
||||||
if matches:
|
if matches:
|
||||||
@@ -587,6 +625,11 @@ class GenericIE(InfoExtractor):
|
|||||||
if mobj is not None:
|
if mobj is not None:
|
||||||
return self.url_result(mobj.group('url'), 'VK')
|
return self.url_result(mobj.group('url'), 'VK')
|
||||||
|
|
||||||
|
# Look for embedded ivi player
|
||||||
|
mobj = re.search(r'<embed[^>]+?src=(["\'])(?P<url>https?://(?:www\.)?ivi\.ru/video/player.+?)\1', webpage)
|
||||||
|
if mobj is not None:
|
||||||
|
return self.url_result(mobj.group('url'), 'Ivi')
|
||||||
|
|
||||||
# Look for embedded Huffington Post player
|
# Look for embedded Huffington Post player
|
||||||
mobj = re.search(
|
mobj = re.search(
|
||||||
r'<iframe[^>]+?src=(["\'])(?P<url>https?://embed\.live\.huffingtonpost\.com/.+?)\1', webpage)
|
r'<iframe[^>]+?src=(["\'])(?P<url>https?://embed\.live\.huffingtonpost\.com/.+?)\1', webpage)
|
||||||
@@ -646,6 +689,22 @@ class GenericIE(InfoExtractor):
|
|||||||
url = unescapeHTML(mobj.group('url'))
|
url = unescapeHTML(mobj.group('url'))
|
||||||
return self.url_result(url)
|
return self.url_result(url)
|
||||||
|
|
||||||
|
# Look for embedded vulture.com player
|
||||||
|
mobj = re.search(
|
||||||
|
r'<iframe src="(?P<url>https?://video\.vulture\.com/[^"]+)"',
|
||||||
|
webpage)
|
||||||
|
if mobj is not None:
|
||||||
|
url = unescapeHTML(mobj.group('url'))
|
||||||
|
return self.url_result(url, ie='Vulture')
|
||||||
|
|
||||||
|
# Look for embedded mtvservices player
|
||||||
|
mobj = re.search(
|
||||||
|
r'<iframe src="(?P<url>https?://media\.mtvnservices\.com/embed/[^"]+)"',
|
||||||
|
webpage)
|
||||||
|
if mobj is not None:
|
||||||
|
url = unescapeHTML(mobj.group('url'))
|
||||||
|
return self.url_result(url, ie='MTVServicesEmbedded')
|
||||||
|
|
||||||
# Start with something easy: JW Player in SWFObject
|
# Start with something easy: JW Player in SWFObject
|
||||||
found = re.findall(r'flashvars: [\'"](?:.*&)?file=(http[^\'"&]*)', webpage)
|
found = re.findall(r'flashvars: [\'"](?:.*&)?file=(http[^\'"&]*)', webpage)
|
||||||
if not found:
|
if not found:
|
||||||
|
@@ -52,8 +52,7 @@ class GooglePlusIE(InfoExtractor):
|
|||||||
|
|
||||||
# Extract title
|
# Extract title
|
||||||
# Get the first line for title
|
# Get the first line for title
|
||||||
video_title = self._html_search_regex(r'<meta name\=\"Description\" content\=\"(.*?)[\n<"]',
|
video_title = self._og_search_description(webpage).splitlines()[0]
|
||||||
webpage, 'title', default='NA')
|
|
||||||
|
|
||||||
# Step 2, Simulate clicking the image box to launch video
|
# Step 2, Simulate clicking the image box to launch video
|
||||||
DOMAIN = 'https://plus.google.com/'
|
DOMAIN = 'https://plus.google.com/'
|
||||||
|
88
youtube_dl/extractor/gorillavid.py
Normal file
88
youtube_dl/extractor/gorillavid.py
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
determine_ext,
|
||||||
|
compat_urllib_parse,
|
||||||
|
compat_urllib_request,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class GorillaVidIE(InfoExtractor):
|
||||||
|
IE_DESC = 'GorillaVid.in and daclips.in'
|
||||||
|
_VALID_URL = r'''(?x)
|
||||||
|
https?://(?P<host>(?:www\.)?
|
||||||
|
(?:daclips\.in|gorillavid\.in))/
|
||||||
|
(?:embed-)?(?P<id>[0-9a-zA-Z]+)(?:-[0-9]+x[0-9]+\.html)?
|
||||||
|
'''
|
||||||
|
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://gorillavid.in/06y9juieqpmi',
|
||||||
|
'md5': '5ae4a3580620380619678ee4875893ba',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '06y9juieqpmi',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Rebecca Black My Moment Official Music Video Reaction',
|
||||||
|
'thumbnail': 're:http://.*\.jpg',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://gorillavid.in/embed-z08zf8le23c6-960x480.html',
|
||||||
|
'md5': 'c9e293ca74d46cad638e199c3f3fe604',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'z08zf8le23c6',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Say something nice',
|
||||||
|
'thumbnail': 're:http://.*\.jpg',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://daclips.in/3rso4kdn6f9m',
|
||||||
|
'md5': '1ad8fd39bb976eeb66004d3a4895f106',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '3rso4kdn6f9m',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Micro Pig piglets ready on 16th July 2009',
|
||||||
|
'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('http://%s/%s' % (mobj.group('host'), video_id), video_id)
|
||||||
|
|
||||||
|
fields = dict(re.findall(r'''(?x)<input\s+
|
||||||
|
type="hidden"\s+
|
||||||
|
name="([^"]+)"\s+
|
||||||
|
(?:id="[^"]+"\s+)?
|
||||||
|
value="([^"]*)"
|
||||||
|
''', webpage))
|
||||||
|
|
||||||
|
if fields['op'] == 'download1':
|
||||||
|
post = compat_urllib_parse.urlencode(fields)
|
||||||
|
|
||||||
|
req = compat_urllib_request.Request(url, post)
|
||||||
|
req.add_header('Content-type', 'application/x-www-form-urlencoded')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(req, video_id, 'Downloading video page')
|
||||||
|
|
||||||
|
title = self._search_regex(r'style="z-index: [0-9]+;">([0-9a-zA-Z ]+)(?:-.+)?</span>', webpage, 'title')
|
||||||
|
thumbnail = self._search_regex(r'image:\'(http[^\']+)\',', webpage, 'thumbnail')
|
||||||
|
url = self._search_regex(r'file: \'(http[^\']+)\',', webpage, 'file url')
|
||||||
|
|
||||||
|
formats = [{
|
||||||
|
'format_id': 'sd',
|
||||||
|
'url': url,
|
||||||
|
'ext': determine_ext(url),
|
||||||
|
'quality': 1,
|
||||||
|
}]
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'formats': formats,
|
||||||
|
}
|
73
youtube_dl/extractor/goshgay.py
Normal file
73
youtube_dl/extractor/goshgay.py
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
compat_urlparse,
|
||||||
|
str_to_int,
|
||||||
|
ExtractorError,
|
||||||
|
)
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
class GoshgayIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'^(?:https?://)www.goshgay.com/video(?P<id>\d+?)($|/)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.goshgay.com/video4116282',
|
||||||
|
'md5': '268b9f3c3229105c57859e166dd72b03',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '4116282',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'md5:089833a4790b5e103285a07337f245bf',
|
||||||
|
'thumbnail': 're:http://.*\.jpg',
|
||||||
|
'age_limit': 18,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
title = self._search_regex(r'class="video-title"><h1>(.+?)<', webpage, 'title')
|
||||||
|
|
||||||
|
player_config = self._search_regex(
|
||||||
|
r'(?s)jwplayer\("player"\)\.setup\(({.+?})\)', webpage, 'config settings')
|
||||||
|
player_vars = json.loads(player_config.replace("'", '"'))
|
||||||
|
width = str_to_int(player_vars.get('width'))
|
||||||
|
height = str_to_int(player_vars.get('height'))
|
||||||
|
config_uri = player_vars.get('config')
|
||||||
|
|
||||||
|
if config_uri is None:
|
||||||
|
raise ExtractorError('Missing config URI')
|
||||||
|
node = self._download_xml(config_uri, video_id, 'Downloading player config XML',
|
||||||
|
errnote='Unable to download XML')
|
||||||
|
if node is None:
|
||||||
|
raise ExtractorError('Missing config XML')
|
||||||
|
if node.tag != 'config':
|
||||||
|
raise ExtractorError('Missing config attribute')
|
||||||
|
fns = node.findall('file')
|
||||||
|
imgs = node.findall('image')
|
||||||
|
if len(fns) != 1:
|
||||||
|
raise ExtractorError('Missing media URI')
|
||||||
|
video_url = fns[0].text
|
||||||
|
if len(imgs) < 1:
|
||||||
|
thumbnail = None
|
||||||
|
else:
|
||||||
|
thumbnail = imgs[0].text
|
||||||
|
|
||||||
|
url_comp = compat_urlparse.urlparse(url)
|
||||||
|
ref = "%s://%s%s" % (url_comp[0], url_comp[1], url_comp[2])
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'url': video_url,
|
||||||
|
'title': title,
|
||||||
|
'width': width,
|
||||||
|
'height': height,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'http_referer': ref,
|
||||||
|
'age_limit': 18,
|
||||||
|
}
|
@@ -1,10 +1,11 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
compat_str,
|
|
||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
compat_urllib_request,
|
compat_urllib_request,
|
||||||
|
|
||||||
@@ -13,59 +14,55 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class HypemIE(InfoExtractor):
|
class HypemIE(InfoExtractor):
|
||||||
"""Information Extractor for hypem"""
|
_VALID_URL = r'http://(?:www\.)?hypem\.com/track/([^/]+)/([^/]+)'
|
||||||
_VALID_URL = r'(?:http://)?(?:www\.)?hypem\.com/track/([^/]+)/([^/]+)'
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
u'url': u'http://hypem.com/track/1v6ga/BODYWORK+-+TAME',
|
'url': 'http://hypem.com/track/1v6ga/BODYWORK+-+TAME',
|
||||||
u'file': u'1v6ga.mp3',
|
'md5': 'b9cc91b5af8995e9f0c1cee04c575828',
|
||||||
u'md5': u'b9cc91b5af8995e9f0c1cee04c575828',
|
'info_dict': {
|
||||||
u'info_dict': {
|
'id': '1v6ga',
|
||||||
u"title": u"Tame"
|
'ext': 'mp3',
|
||||||
|
'title': 'Tame',
|
||||||
|
'uploader': 'BODYWORK',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
if mobj is None:
|
|
||||||
raise ExtractorError(u'Invalid URL: %s' % url)
|
|
||||||
track_id = mobj.group(1)
|
track_id = mobj.group(1)
|
||||||
|
|
||||||
data = {'ax': 1, 'ts': time.time()}
|
data = {'ax': 1, 'ts': time.time()}
|
||||||
data_encoded = compat_urllib_parse.urlencode(data)
|
data_encoded = compat_urllib_parse.urlencode(data)
|
||||||
complete_url = url + "?" + data_encoded
|
complete_url = url + "?" + data_encoded
|
||||||
request = compat_urllib_request.Request(complete_url)
|
request = compat_urllib_request.Request(complete_url)
|
||||||
response, urlh = self._download_webpage_handle(request, track_id, u'Downloading webpage with the url')
|
response, urlh = self._download_webpage_handle(
|
||||||
|
request, track_id, 'Downloading webpage with the url')
|
||||||
cookie = urlh.headers.get('Set-Cookie', '')
|
cookie = urlh.headers.get('Set-Cookie', '')
|
||||||
|
|
||||||
self.report_extraction(track_id)
|
html_tracks = self._html_search_regex(
|
||||||
|
r'(?ms)<script type="application/json" id="displayList-data">\s*(.*?)\s*</script>',
|
||||||
html_tracks = self._html_search_regex(r'<script type="application/json" id="displayList-data">(.*?)</script>',
|
response, 'tracks')
|
||||||
response, u'tracks', flags=re.MULTILINE|re.DOTALL).strip()
|
|
||||||
try:
|
try:
|
||||||
track_list = json.loads(html_tracks)
|
track_list = json.loads(html_tracks)
|
||||||
track = track_list[u'tracks'][0]
|
track = track_list['tracks'][0]
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise ExtractorError(u'Hypemachine contained invalid JSON.')
|
raise ExtractorError('Hypemachine contained invalid JSON.')
|
||||||
|
|
||||||
key = track[u"key"]
|
key = track['key']
|
||||||
track_id = track[u"id"]
|
track_id = track['id']
|
||||||
artist = track[u"artist"]
|
artist = track['artist']
|
||||||
title = track[u"song"]
|
title = track['song']
|
||||||
|
|
||||||
serve_url = "http://hypem.com/serve/source/%s/%s" % (compat_str(track_id), compat_str(key))
|
serve_url = "http://hypem.com/serve/source/%s/%s" % (track_id, key)
|
||||||
request = compat_urllib_request.Request(serve_url, "" , {'Content-Type': 'application/json'})
|
request = compat_urllib_request.Request(
|
||||||
|
serve_url, '', {'Content-Type': 'application/json'})
|
||||||
request.add_header('cookie', cookie)
|
request.add_header('cookie', cookie)
|
||||||
song_data_json = self._download_webpage(request, track_id, u'Downloading metadata')
|
song_data = self._download_json(request, track_id, 'Downloading metadata')
|
||||||
try:
|
final_url = song_data["url"]
|
||||||
song_data = json.loads(song_data_json)
|
|
||||||
except ValueError:
|
|
||||||
raise ExtractorError(u'Hypemachine contained invalid JSON.')
|
|
||||||
final_url = song_data[u"url"]
|
|
||||||
|
|
||||||
return [{
|
return {
|
||||||
'id': track_id,
|
'id': track_id,
|
||||||
'url': final_url,
|
'url': final_url,
|
||||||
'ext': "mp3",
|
'ext': 'mp3',
|
||||||
'title': title,
|
'title': title,
|
||||||
'artist': artist,
|
'uploader': artist,
|
||||||
}]
|
}
|
||||||
|
@@ -14,7 +14,7 @@ from ..utils import (
|
|||||||
class IviIE(InfoExtractor):
|
class IviIE(InfoExtractor):
|
||||||
IE_DESC = 'ivi.ru'
|
IE_DESC = 'ivi.ru'
|
||||||
IE_NAME = 'ivi'
|
IE_NAME = 'ivi'
|
||||||
_VALID_URL = r'https?://(?:www\.)?ivi\.ru/watch(?:/(?P<compilationid>[^/]+))?/(?P<videoid>\d+)'
|
_VALID_URL = r'https?://(?:www\.)?ivi\.ru/(?:watch/(?:[^/]+/)?|video/player\?.*?videoId=)(?P<videoid>\d+)'
|
||||||
|
|
||||||
_TESTS = [
|
_TESTS = [
|
||||||
# Single movie
|
# Single movie
|
||||||
|
35
youtube_dl/extractor/ku6.py
Normal file
35
youtube_dl/extractor/ku6.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
|
class Ku6IE(InfoExtractor):
|
||||||
|
_VALID_URL = r'http://v\.ku6\.com/show/(?P<id>[a-zA-Z0-9\-\_]+)(?:\.)*html'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://v.ku6.com/show/JG-8yS14xzBr4bCn1pu0xw...html',
|
||||||
|
'md5': '01203549b9efbb45f4b87d55bdea1ed1',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'JG-8yS14xzBr4bCn1pu0xw',
|
||||||
|
'ext': 'f4v',
|
||||||
|
'title': 'techniques test',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
title = self._search_regex(r'<h1 title=.*>(.*?)</h1>', webpage, 'title')
|
||||||
|
dataUrl = 'http://v.ku6.com/fetchVideo4Player/%s.html' % video_id
|
||||||
|
jsonData = self._download_json(dataUrl, video_id)
|
||||||
|
downloadUrl = jsonData['data']['f']
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'url': downloadUrl
|
||||||
|
}
|
||||||
|
|
@@ -24,7 +24,7 @@ class LifeNewsIE(InfoExtractor):
|
|||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'МВД разыскивает мужчин, оставивших в IKEA сумку с автоматом',
|
'title': 'МВД разыскивает мужчин, оставивших в IKEA сумку с автоматом',
|
||||||
'description': 'Камеры наблюдения гипермаркета зафиксировали троих мужчин, спрятавших оружейный арсенал в камере хранения.',
|
'description': 'Камеры наблюдения гипермаркета зафиксировали троих мужчин, спрятавших оружейный арсенал в камере хранения.',
|
||||||
'thumbnail': 'http://lifenews.ru/static/posts/2014/1/126342/.video.jpg',
|
'thumbnail': 're:http://.*\.jpg',
|
||||||
'upload_date': '20140130',
|
'upload_date': '20140130',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import json
|
import json
|
||||||
|
|
||||||
@@ -6,31 +8,35 @@ from ..utils import (
|
|||||||
compat_urllib_parse_urlparse,
|
compat_urllib_parse_urlparse,
|
||||||
compat_urlparse,
|
compat_urlparse,
|
||||||
xpath_with_ns,
|
xpath_with_ns,
|
||||||
|
compat_str,
|
||||||
|
orderedSet,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class LivestreamIE(InfoExtractor):
|
class LivestreamIE(InfoExtractor):
|
||||||
IE_NAME = u'livestream'
|
IE_NAME = 'livestream'
|
||||||
_VALID_URL = r'http://new\.livestream\.com/.*?/(?P<event_name>.*?)(/videos/(?P<id>\d+))?/?$'
|
_VALID_URL = r'http://new\.livestream\.com/.*?/(?P<event_name>.*?)(/videos/(?P<id>\d+))?/?$'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
u'url': u'http://new.livestream.com/CoheedandCambria/WebsterHall/videos/4719370',
|
'url': 'http://new.livestream.com/CoheedandCambria/WebsterHall/videos/4719370',
|
||||||
u'file': u'4719370.mp4',
|
'md5': '53274c76ba7754fb0e8d072716f2292b',
|
||||||
u'md5': u'0d2186e3187d185a04b3cdd02b828836',
|
'info_dict': {
|
||||||
u'info_dict': {
|
'id': '4719370',
|
||||||
u'title': u'Live from Webster Hall NYC',
|
'ext': 'mp4',
|
||||||
u'upload_date': u'20121012',
|
'title': 'Live from Webster Hall NYC',
|
||||||
|
'upload_date': '20121012',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def _extract_video_info(self, video_data):
|
def _extract_video_info(self, video_data):
|
||||||
video_url = video_data.get('progressive_url_hd') or video_data.get('progressive_url')
|
video_url = video_data.get('progressive_url_hd') or video_data.get('progressive_url')
|
||||||
return {'id': video_data['id'],
|
return {
|
||||||
'url': video_url,
|
'id': compat_str(video_data['id']),
|
||||||
'ext': 'mp4',
|
'url': video_url,
|
||||||
'title': video_data['caption'],
|
'ext': 'mp4',
|
||||||
'thumbnail': video_data['thumbnail_url'],
|
'title': video_data['caption'],
|
||||||
'upload_date': video_data['updated_at'].replace('-','')[:8],
|
'thumbnail': video_data['thumbnail_url'],
|
||||||
}
|
'upload_date': video_data['updated_at'].replace('-', '')[:8],
|
||||||
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
@@ -40,43 +46,43 @@ class LivestreamIE(InfoExtractor):
|
|||||||
|
|
||||||
if video_id is None:
|
if video_id is None:
|
||||||
# This is an event page:
|
# This is an event page:
|
||||||
config_json = self._search_regex(r'window.config = ({.*?});',
|
config_json = self._search_regex(
|
||||||
webpage, u'window config')
|
r'window.config = ({.*?});', webpage, 'window config')
|
||||||
info = json.loads(config_json)['event']
|
info = json.loads(config_json)['event']
|
||||||
videos = [self._extract_video_info(video_data['data'])
|
videos = [self._extract_video_info(video_data['data'])
|
||||||
for video_data in info['feed']['data'] if video_data['type'] == u'video']
|
for video_data in info['feed']['data'] if video_data['type'] == 'video']
|
||||||
return self.playlist_result(videos, info['id'], info['full_name'])
|
return self.playlist_result(videos, info['id'], info['full_name'])
|
||||||
else:
|
else:
|
||||||
og_video = self._og_search_video_url(webpage, name=u'player url')
|
og_video = self._og_search_video_url(webpage, 'player url')
|
||||||
query_str = compat_urllib_parse_urlparse(og_video).query
|
query_str = compat_urllib_parse_urlparse(og_video).query
|
||||||
query = compat_urlparse.parse_qs(query_str)
|
query = compat_urlparse.parse_qs(query_str)
|
||||||
api_url = query['play_url'][0].replace('.smil', '')
|
api_url = query['play_url'][0].replace('.smil', '')
|
||||||
info = json.loads(self._download_webpage(api_url, video_id,
|
info = json.loads(self._download_webpage(
|
||||||
u'Downloading video info'))
|
api_url, video_id, 'Downloading video info'))
|
||||||
return self._extract_video_info(info)
|
return self._extract_video_info(info)
|
||||||
|
|
||||||
|
|
||||||
# The original version of Livestream uses a different system
|
# The original version of Livestream uses a different system
|
||||||
class LivestreamOriginalIE(InfoExtractor):
|
class LivestreamOriginalIE(InfoExtractor):
|
||||||
IE_NAME = u'livestream:original'
|
IE_NAME = 'livestream:original'
|
||||||
_VALID_URL = r'https?://www\.livestream\.com/(?P<user>[^/]+)/video\?.*?clipId=(?P<id>.*?)(&|$)'
|
_VALID_URL = r'''(?x)https?://www\.livestream\.com/
|
||||||
|
(?P<user>[^/]+)/(?P<type>video|folder)
|
||||||
|
(?:\?.*?Id=|/)(?P<id>.*?)(&|$)
|
||||||
|
'''
|
||||||
_TEST = {
|
_TEST = {
|
||||||
u'url': u'http://www.livestream.com/dealbook/video?clipId=pla_8aa4a3f1-ba15-46a4-893b-902210e138fb',
|
'url': 'http://www.livestream.com/dealbook/video?clipId=pla_8aa4a3f1-ba15-46a4-893b-902210e138fb',
|
||||||
u'info_dict': {
|
'info_dict': {
|
||||||
u'id': u'pla_8aa4a3f1-ba15-46a4-893b-902210e138fb',
|
'id': 'pla_8aa4a3f1-ba15-46a4-893b-902210e138fb',
|
||||||
u'ext': u'flv',
|
'ext': 'flv',
|
||||||
u'title': u'Spark 1 (BitCoin) with Cameron Winklevoss & Tyler Winklevoss of Winklevoss Capital',
|
'title': 'Spark 1 (BitCoin) with Cameron Winklevoss & Tyler Winklevoss of Winklevoss Capital',
|
||||||
},
|
},
|
||||||
u'params': {
|
'params': {
|
||||||
# rtmp
|
# rtmp
|
||||||
u'skip_download': True,
|
'skip_download': True,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _extract_video(self, user, video_id):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
|
||||||
video_id = mobj.group('id')
|
|
||||||
user = mobj.group('user')
|
|
||||||
api_url = 'http://x{0}x.api.channel.livestream.com/2.0/clipdetails?extendedInfo=true&id={1}'.format(user, video_id)
|
api_url = 'http://x{0}x.api.channel.livestream.com/2.0/clipdetails?extendedInfo=true&id={1}'.format(user, video_id)
|
||||||
|
|
||||||
info = self._download_xml(api_url, video_id)
|
info = self._download_xml(api_url, video_id)
|
||||||
@@ -84,7 +90,7 @@ class LivestreamOriginalIE(InfoExtractor):
|
|||||||
ns = {'media': 'http://search.yahoo.com/mrss'}
|
ns = {'media': 'http://search.yahoo.com/mrss'}
|
||||||
thumbnail_url = item.find(xpath_with_ns('media:thumbnail', ns)).attrib['url']
|
thumbnail_url = item.find(xpath_with_ns('media:thumbnail', ns)).attrib['url']
|
||||||
# Remove the extension and number from the path (like 1.jpg)
|
# Remove the extension and number from the path (like 1.jpg)
|
||||||
path = self._search_regex(r'(user-files/.+)_.*?\.jpg$', thumbnail_url, u'path')
|
path = self._search_regex(r'(user-files/.+)_.*?\.jpg$', thumbnail_url, 'path')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
@@ -94,3 +100,44 @@ class LivestreamOriginalIE(InfoExtractor):
|
|||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'thumbnail': thumbnail_url,
|
'thumbnail': thumbnail_url,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def _extract_folder(self, url, folder_id):
|
||||||
|
webpage = self._download_webpage(url, folder_id)
|
||||||
|
urls = orderedSet(re.findall(r'<a href="(https?://livestre\.am/.*?)"', webpage))
|
||||||
|
|
||||||
|
return {
|
||||||
|
'_type': 'playlist',
|
||||||
|
'id': folder_id,
|
||||||
|
'entries': [{
|
||||||
|
'_type': 'url',
|
||||||
|
'url': video_url,
|
||||||
|
} for video_url in urls],
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
id = mobj.group('id')
|
||||||
|
user = mobj.group('user')
|
||||||
|
url_type = mobj.group('type')
|
||||||
|
if url_type == 'folder':
|
||||||
|
return self._extract_folder(url, id)
|
||||||
|
else:
|
||||||
|
return self._extract_video(user, id)
|
||||||
|
|
||||||
|
|
||||||
|
# The server doesn't support HEAD request, the generic extractor can't detect
|
||||||
|
# the redirection
|
||||||
|
class LivestreamShortenerIE(InfoExtractor):
|
||||||
|
IE_NAME = 'livestream:shortener'
|
||||||
|
IE_DESC = False # Do not list
|
||||||
|
_VALID_URL = r'https?://livestre\.am/(?P<id>.+)'
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
id = mobj.group('id')
|
||||||
|
webpage = self._download_webpage(url, id)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'_type': 'url',
|
||||||
|
'url': self._og_search_url(webpage),
|
||||||
|
}
|
||||||
|
87
youtube_dl/extractor/motherless.py
Normal file
87
youtube_dl/extractor/motherless.py
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
int_or_none,
|
||||||
|
unified_strdate,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MotherlessIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'http://(?:www\.)?motherless\.com/(?P<id>[A-Z0-9]+)'
|
||||||
|
_TESTS = [
|
||||||
|
{
|
||||||
|
'url': 'http://motherless.com/AC3FFE1',
|
||||||
|
'md5': '5527fef81d2e529215dad3c2d744a7d9',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'AC3FFE1',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Fucked in the ass while playing PS3',
|
||||||
|
'categories': ['Gaming', 'anal', 'reluctant', 'rough', 'Wife'],
|
||||||
|
'upload_date': '20100913',
|
||||||
|
'uploader_id': 'famouslyfuckedup',
|
||||||
|
'thumbnail': 're:http://.*\.jpg',
|
||||||
|
'age_limit': 18,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://motherless.com/532291B',
|
||||||
|
'md5': 'bc59a6b47d1f958e61fbd38a4d31b131',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '532291B',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Amazing girl playing the omegle game, PERFECT!',
|
||||||
|
'categories': ['Amateur', 'webcam', 'omegle', 'pink', 'young', 'masturbate', 'teen', 'game', 'hairy'],
|
||||||
|
'upload_date': '20140622',
|
||||||
|
'uploader_id': 'Sulivana7x',
|
||||||
|
'thumbnail': 're:http://.*\.jpg',
|
||||||
|
'age_limit': 18,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
def _real_extract(self,url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
title = self._html_search_regex(r'id="view-upload-title">\s+([^<]+)<', webpage, 'title')
|
||||||
|
|
||||||
|
video_url = self._html_search_regex(r'setup\(\{\s+"file".+: "([^"]+)",', webpage, 'video_url')
|
||||||
|
age_limit = self._rta_search(webpage)
|
||||||
|
|
||||||
|
view_count = self._html_search_regex(r'<strong>Views</strong>\s+([^<]+)<', webpage, 'view_count')
|
||||||
|
|
||||||
|
upload_date = self._html_search_regex(r'<strong>Uploaded</strong>\s+([^<]+)<', webpage, 'upload_date')
|
||||||
|
if 'Ago' in upload_date:
|
||||||
|
days = int(re.search(r'([0-9]+)', upload_date).group(1))
|
||||||
|
upload_date = (datetime.datetime.now() - datetime.timedelta(days=days)).strftime('%Y%m%d')
|
||||||
|
else:
|
||||||
|
upload_date = unified_strdate(upload_date)
|
||||||
|
|
||||||
|
like_count = self._html_search_regex(r'<strong>Favorited</strong>\s+([^<]+)<', webpage, 'like_count')
|
||||||
|
|
||||||
|
comment_count = webpage.count('class="media-comment-contents"')
|
||||||
|
uploader_id = self._html_search_regex(r'"thumb-member-username">\s+<a href="/m/([^"]+)"', webpage, 'uploader_id')
|
||||||
|
|
||||||
|
categories = self._html_search_meta('keywords', webpage)
|
||||||
|
if categories:
|
||||||
|
categories = [cat.strip() for cat in categories.split(',')]
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'upload_date': upload_date,
|
||||||
|
'uploader_id': uploader_id,
|
||||||
|
'thumbnail': self._og_search_thumbnail(webpage),
|
||||||
|
'categories': categories,
|
||||||
|
'view_count': int_or_none(view_count.replace(',', '')),
|
||||||
|
'like_count': int_or_none(like_count.replace(',', '')),
|
||||||
|
'comment_count': comment_count,
|
||||||
|
'age_limit': age_limit,
|
||||||
|
'url': video_url,
|
||||||
|
}
|
@@ -28,7 +28,7 @@ class MporaIE(InfoExtractor):
|
|||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
data_json = self._search_regex(
|
data_json = self._search_regex(
|
||||||
r"new FM\.Player\('[^']+',\s*(\{.*?)\);\n", webpage, 'json')
|
r"new FM\.Player\('[^']+',\s*(\{.*?)\).player;", webpage, 'json')
|
||||||
|
|
||||||
data = json.loads(data_json)
|
data = json.loads(data_json)
|
||||||
|
|
||||||
|
@@ -22,6 +22,7 @@ def _media_xml_tag(tag):
|
|||||||
|
|
||||||
class MTVServicesInfoExtractor(InfoExtractor):
|
class MTVServicesInfoExtractor(InfoExtractor):
|
||||||
_MOBILE_TEMPLATE = None
|
_MOBILE_TEMPLATE = None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _id_from_uri(uri):
|
def _id_from_uri(uri):
|
||||||
return uri.split(':')[-1]
|
return uri.split(':')[-1]
|
||||||
@@ -35,6 +36,9 @@ class MTVServicesInfoExtractor(InfoExtractor):
|
|||||||
base = 'http://mtvnmobile.vo.llnwd.net/kip0/_pxn=1+_pxI0=Ripod-h264+_pxL0=undefined+_pxM0=+_pxK=18639+_pxE=mp4/44620/mtvnorigin/'
|
base = 'http://mtvnmobile.vo.llnwd.net/kip0/_pxn=1+_pxI0=Ripod-h264+_pxL0=undefined+_pxM0=+_pxK=18639+_pxE=mp4/44620/mtvnorigin/'
|
||||||
return base + m.group('finalid')
|
return base + m.group('finalid')
|
||||||
|
|
||||||
|
def _get_feed_url(self, uri):
|
||||||
|
return self._FEED_URL
|
||||||
|
|
||||||
def _get_thumbnail_url(self, uri, itemdoc):
|
def _get_thumbnail_url(self, uri, itemdoc):
|
||||||
search_path = '%s/%s' % (_media_xml_tag('group'), _media_xml_tag('thumbnail'))
|
search_path = '%s/%s' % (_media_xml_tag('group'), _media_xml_tag('thumbnail'))
|
||||||
thumb_node = itemdoc.find(search_path)
|
thumb_node = itemdoc.find(search_path)
|
||||||
@@ -80,6 +84,7 @@ class MTVServicesInfoExtractor(InfoExtractor):
|
|||||||
})
|
})
|
||||||
except (KeyError, TypeError):
|
except (KeyError, TypeError):
|
||||||
raise ExtractorError('Invalid rendition field.')
|
raise ExtractorError('Invalid rendition field.')
|
||||||
|
self._sort_formats(formats)
|
||||||
return formats
|
return formats
|
||||||
|
|
||||||
def _get_video_info(self, itemdoc):
|
def _get_video_info(self, itemdoc):
|
||||||
@@ -135,10 +140,10 @@ class MTVServicesInfoExtractor(InfoExtractor):
|
|||||||
|
|
||||||
def _get_videos_info(self, uri):
|
def _get_videos_info(self, uri):
|
||||||
video_id = self._id_from_uri(uri)
|
video_id = self._id_from_uri(uri)
|
||||||
|
feed_url = self._get_feed_url(uri)
|
||||||
data = compat_urllib_parse.urlencode({'uri': uri})
|
data = compat_urllib_parse.urlencode({'uri': uri})
|
||||||
|
|
||||||
idoc = self._download_xml(
|
idoc = self._download_xml(
|
||||||
self._FEED_URL + '?' + data, video_id,
|
feed_url + '?' + data, video_id,
|
||||||
'Downloading info', transform_source=fix_xml_ampersands)
|
'Downloading info', transform_source=fix_xml_ampersands)
|
||||||
return [self._get_video_info(item) for item in idoc.findall('.//item')]
|
return [self._get_video_info(item) for item in idoc.findall('.//item')]
|
||||||
|
|
||||||
@@ -153,12 +158,46 @@ class MTVServicesInfoExtractor(InfoExtractor):
|
|||||||
if mgid.endswith('.swf'):
|
if mgid.endswith('.swf'):
|
||||||
mgid = mgid[:-4]
|
mgid = mgid[:-4]
|
||||||
except RegexNotFoundError:
|
except RegexNotFoundError:
|
||||||
|
mgid = None
|
||||||
|
|
||||||
|
if mgid is None or ':' not in mgid:
|
||||||
mgid = self._search_regex(
|
mgid = self._search_regex(
|
||||||
[r'data-mgid="(.*?)"', r'swfobject.embedSWF\(".*?(mgid:.*?)"'],
|
[r'data-mgid="(.*?)"', r'swfobject.embedSWF\(".*?(mgid:.*?)"'],
|
||||||
webpage, u'mgid')
|
webpage, u'mgid')
|
||||||
return self._get_videos_info(mgid)
|
return self._get_videos_info(mgid)
|
||||||
|
|
||||||
|
|
||||||
|
class MTVServicesEmbeddedIE(MTVServicesInfoExtractor):
|
||||||
|
IE_NAME = 'mtvservices:embedded'
|
||||||
|
_VALID_URL = r'https?://media\.mtvnservices\.com/embed/(?P<mgid>.+?)(\?|/|$)'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
# From http://www.thewrap.com/peter-dinklage-sums-up-game-of-thrones-in-45-seconds-video/
|
||||||
|
'url': 'http://media.mtvnservices.com/embed/mgid:uma:video:mtv.com:1043906/cp~vid%3D1043906%26uri%3Dmgid%3Auma%3Avideo%3Amtv.com%3A1043906',
|
||||||
|
'md5': 'cb349b21a7897164cede95bd7bf3fbb9',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '1043906',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Peter Dinklage Sums Up \'Game Of Thrones\' In 45 Seconds',
|
||||||
|
'description': '"Sexy sexy sexy, stabby stabby stabby, beautiful language," says Peter Dinklage as he tries summarizing "Game of Thrones" in under a minute.',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_feed_url(self, uri):
|
||||||
|
video_id = self._id_from_uri(uri)
|
||||||
|
site_id = uri.replace(video_id, '')
|
||||||
|
config_url = 'http://media.mtvnservices.com/pmt/e1/players/{0}/config.xml'.format(site_id)
|
||||||
|
config_doc = self._download_xml(config_url, video_id)
|
||||||
|
feed_node = config_doc.find('.//feed')
|
||||||
|
feed_url = feed_node.text.strip().split('?')[0]
|
||||||
|
return feed_url
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
mgid = mobj.group('mgid')
|
||||||
|
return self._get_videos_info(mgid)
|
||||||
|
|
||||||
|
|
||||||
class MTVIE(MTVServicesInfoExtractor):
|
class MTVIE(MTVServicesInfoExtractor):
|
||||||
_VALID_URL = r'''(?x)^https?://
|
_VALID_URL = r'''(?x)^https?://
|
||||||
(?:(?:www\.)?mtv\.com/videos/.+?/(?P<videoid>[0-9]+)/[^/]+$|
|
(?:(?:www\.)?mtv\.com/videos/.+?/(?P<videoid>[0-9]+)/[^/]+$|
|
||||||
|
@@ -1,4 +1,6 @@
|
|||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
@@ -12,12 +14,13 @@ class NaverIE(InfoExtractor):
|
|||||||
_VALID_URL = r'https?://(?:m\.)?tvcast\.naver\.com/v/(?P<id>\d+)'
|
_VALID_URL = r'https?://(?:m\.)?tvcast\.naver\.com/v/(?P<id>\d+)'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
u'url': u'http://tvcast.naver.com/v/81652',
|
'url': 'http://tvcast.naver.com/v/81652',
|
||||||
u'file': u'81652.mp4',
|
'info_dict': {
|
||||||
u'info_dict': {
|
'id': '81652',
|
||||||
u'title': u'[9월 모의고사 해설강의][수학_김상희] 수학 A형 16~20번',
|
'ext': 'mp4',
|
||||||
u'description': u'합격불변의 법칙 메가스터디 | 메가스터디 수학 김상희 선생님이 9월 모의고사 수학A형 16번에서 20번까지 해설강의를 공개합니다.',
|
'title': '[9월 모의고사 해설강의][수학_김상희] 수학 A형 16~20번',
|
||||||
u'upload_date': u'20130903',
|
'description': '합격불변의 법칙 메가스터디 | 메가스터디 수학 김상희 선생님이 9월 모의고사 수학A형 16번에서 20번까지 해설강의를 공개합니다.',
|
||||||
|
'upload_date': '20130903',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,7 +31,7 @@ class NaverIE(InfoExtractor):
|
|||||||
m_id = re.search(r'var rmcPlayer = new nhn.rmcnmv.RMCVideoPlayer\("(.+?)", "(.+?)"',
|
m_id = re.search(r'var rmcPlayer = new nhn.rmcnmv.RMCVideoPlayer\("(.+?)", "(.+?)"',
|
||||||
webpage)
|
webpage)
|
||||||
if m_id is None:
|
if m_id is None:
|
||||||
raise ExtractorError(u'couldn\'t extract vid and key')
|
raise ExtractorError('couldn\'t extract vid and key')
|
||||||
vid = m_id.group(1)
|
vid = m_id.group(1)
|
||||||
key = m_id.group(2)
|
key = m_id.group(2)
|
||||||
query = compat_urllib_parse.urlencode({'vid': vid, 'inKey': key,})
|
query = compat_urllib_parse.urlencode({'vid': vid, 'inKey': key,})
|
||||||
@@ -39,22 +42,27 @@ class NaverIE(InfoExtractor):
|
|||||||
})
|
})
|
||||||
info = self._download_xml(
|
info = self._download_xml(
|
||||||
'http://serviceapi.rmcnmv.naver.com/flash/videoInfo.nhn?' + query,
|
'http://serviceapi.rmcnmv.naver.com/flash/videoInfo.nhn?' + query,
|
||||||
video_id, u'Downloading video info')
|
video_id, 'Downloading video info')
|
||||||
urls = self._download_xml(
|
urls = self._download_xml(
|
||||||
'http://serviceapi.rmcnmv.naver.com/flash/playableEncodingOption.nhn?' + query_urls,
|
'http://serviceapi.rmcnmv.naver.com/flash/playableEncodingOption.nhn?' + query_urls,
|
||||||
video_id, u'Downloading video formats info')
|
video_id, 'Downloading video formats info')
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
for format_el in urls.findall('EncodingOptions/EncodingOption'):
|
for format_el in urls.findall('EncodingOptions/EncodingOption'):
|
||||||
domain = format_el.find('Domain').text
|
domain = format_el.find('Domain').text
|
||||||
if domain.startswith('rtmp'):
|
f = {
|
||||||
continue
|
|
||||||
formats.append({
|
|
||||||
'url': domain + format_el.find('uri').text,
|
'url': domain + format_el.find('uri').text,
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'width': int(format_el.find('width').text),
|
'width': int(format_el.find('width').text),
|
||||||
'height': int(format_el.find('height').text),
|
'height': int(format_el.find('height').text),
|
||||||
})
|
}
|
||||||
|
if domain.startswith('rtmp'):
|
||||||
|
f.update({
|
||||||
|
'ext': 'flv',
|
||||||
|
'rtmp_protocol': '1', # rtmpt
|
||||||
|
})
|
||||||
|
formats.append(f)
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
|
@@ -18,15 +18,15 @@ class NDRIE(InfoExtractor):
|
|||||||
|
|
||||||
_TESTS = [
|
_TESTS = [
|
||||||
{
|
{
|
||||||
'url': 'http://www.ndr.de/fernsehen/sendungen/markt/markt7959.html',
|
'url': 'http://www.ndr.de/fernsehen/media/dienordreportage325.html',
|
||||||
'md5': 'e7a6079ca39d3568f4996cb858dd6708',
|
'md5': '4a4eeafd17c3058b65f0c8f091355855',
|
||||||
'note': 'Video file',
|
'note': 'Video file',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '7959',
|
'id': '325',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Markt - die ganze Sendung',
|
'title': 'Blaue Bohnen aus Blocken',
|
||||||
'description': 'md5:af9179cf07f67c5c12dc6d9997e05725',
|
'description': 'md5:190d71ba2ccddc805ed01547718963bc',
|
||||||
'duration': 2655,
|
'duration': 1715,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@@ -1,22 +1,28 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import month_by_name
|
from ..utils import (
|
||||||
|
month_by_name,
|
||||||
|
int_or_none,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class NDTVIE(InfoExtractor):
|
class NDTVIE(InfoExtractor):
|
||||||
_VALID_URL = r'^https?://(?:www\.)?ndtv\.com/video/player/[^/]*/[^/]*/(?P<id>[a-z0-9]+)'
|
_VALID_URL = r'^https?://(?:www\.)?ndtv\.com/video/player/[^/]*/[^/]*/(?P<id>[a-z0-9]+)'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
u"url": u"http://www.ndtv.com/video/player/news/ndtv-exclusive-don-t-need-character-certificate-from-rahul-gandhi-says-arvind-kejriwal/300710",
|
'url': 'http://www.ndtv.com/video/player/news/ndtv-exclusive-don-t-need-character-certificate-from-rahul-gandhi-says-arvind-kejriwal/300710',
|
||||||
u"file": u"300710.mp4",
|
'md5': '39f992dbe5fb531c395d8bbedb1e5e88',
|
||||||
u"md5": u"39f992dbe5fb531c395d8bbedb1e5e88",
|
'info_dict': {
|
||||||
u"info_dict": {
|
'id': '300710',
|
||||||
u"title": u"NDTV exclusive: Don't need character certificate from Rahul Gandhi, says Arvind Kejriwal",
|
'ext': 'mp4',
|
||||||
u"description": u"In an exclusive interview to NDTV, Aam Aadmi Party's Arvind Kejriwal says it makes no difference to him that Rahul Gandhi said the Congress needs to learn from his party.",
|
'title': "NDTV exclusive: Don't need character certificate from Rahul Gandhi, says Arvind Kejriwal",
|
||||||
u"upload_date": u"20131208",
|
'description': 'md5:ab2d4b4a6056c5cb4caa6d729deabf02',
|
||||||
u"duration": 1327,
|
'upload_date': '20131208',
|
||||||
u"thumbnail": u"http://i.ndtvimg.com/video/images/vod/medium/2013-12/big_300710_1386518307.jpg",
|
'duration': 1327,
|
||||||
|
'thumbnail': 'http://i.ndtvimg.com/video/images/vod/medium/2013-12/big_300710_1386518307.jpg',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,13 +33,12 @@ class NDTVIE(InfoExtractor):
|
|||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
filename = self._search_regex(
|
filename = self._search_regex(
|
||||||
r"__filename='([^']+)'", webpage, u'video filename')
|
r"__filename='([^']+)'", webpage, 'video filename')
|
||||||
video_url = (u'http://bitcast-b.bitgravity.com/ndtvod/23372/ndtv/%s' %
|
video_url = ('http://bitcast-b.bitgravity.com/ndtvod/23372/ndtv/%s' %
|
||||||
filename)
|
filename)
|
||||||
|
|
||||||
duration_str = filename = self._search_regex(
|
duration = int_or_none(self._search_regex(
|
||||||
r"__duration='([^']+)'", webpage, u'duration', fatal=False)
|
r"__duration='([^']+)'", webpage, 'duration', fatal=False))
|
||||||
duration = None if duration_str is None else int(duration_str)
|
|
||||||
|
|
||||||
date_m = re.search(r'''(?x)
|
date_m = re.search(r'''(?x)
|
||||||
<p\s+class="vod_dateline">\s*
|
<p\s+class="vod_dateline">\s*
|
||||||
@@ -41,7 +46,7 @@ class NDTVIE(InfoExtractor):
|
|||||||
(?P<monthname>[A-Za-z]+)\s+(?P<day>[0-9]+),\s*(?P<year>[0-9]+)
|
(?P<monthname>[A-Za-z]+)\s+(?P<day>[0-9]+),\s*(?P<year>[0-9]+)
|
||||||
''', webpage)
|
''', webpage)
|
||||||
upload_date = None
|
upload_date = None
|
||||||
assert date_m
|
|
||||||
if date_m is not None:
|
if date_m is not None:
|
||||||
month = month_by_name(date_m.group('monthname'))
|
month = month_by_name(date_m.group('monthname'))
|
||||||
if month is not None:
|
if month is not None:
|
||||||
@@ -49,14 +54,19 @@ class NDTVIE(InfoExtractor):
|
|||||||
date_m.group('year'), month, int(date_m.group('day')))
|
date_m.group('year'), month, int(date_m.group('day')))
|
||||||
|
|
||||||
description = self._og_search_description(webpage)
|
description = self._og_search_description(webpage)
|
||||||
READ_MORE = u' (Read more)'
|
READ_MORE = ' (Read more)'
|
||||||
if description.endswith(READ_MORE):
|
if description.endswith(READ_MORE):
|
||||||
description = description[:-len(READ_MORE)]
|
description = description[:-len(READ_MORE)]
|
||||||
|
|
||||||
|
title = self._og_search_title(webpage)
|
||||||
|
TITLE_SUFFIX = ' - NDTV'
|
||||||
|
if title.endswith(TITLE_SUFFIX):
|
||||||
|
title = title[:-len(TITLE_SUFFIX)]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'url': video_url,
|
'url': video_url,
|
||||||
'title': self._og_search_title(webpage),
|
'title': title,
|
||||||
'description': description,
|
'description': description,
|
||||||
'thumbnail': self._og_search_thumbnail(webpage),
|
'thumbnail': self._og_search_thumbnail(webpage),
|
||||||
'duration': duration,
|
'duration': duration,
|
||||||
|
@@ -4,18 +4,19 @@ from __future__ import unicode_literals
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from ..utils import ExtractorError
|
||||||
|
|
||||||
|
|
||||||
class NewstubeIE(InfoExtractor):
|
class NewstubeIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?newstube\.ru/media/(?P<id>.+)'
|
_VALID_URL = r'https?://(?:www\.)?newstube\.ru/media/(?P<id>.+)'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://newstube.ru/media/na-korable-progress-prodolzhaetsya-testirovanie-sistemy-kurs',
|
'url': 'http://www.newstube.ru/media/telekanal-cnn-peremestil-gorod-slavyansk-v-krym',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'd156a237-a6e9-4111-a682-039995f721f1',
|
'id': '728e0ef2-e187-4012-bac0-5a081fdcb1f6',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'title': 'На корабле «Прогресс» продолжается тестирование системы «Курс»',
|
'title': 'Телеканал CNN переместил город Славянск в Крым',
|
||||||
'description': 'md5:d0cbe7b4a6f600552617e48548d5dc77',
|
'description': 'md5:419a8c9f03442bc0b0a794d689360335',
|
||||||
'duration': 20.04,
|
'duration': 31.05,
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
# rtmp download
|
# rtmp download
|
||||||
@@ -40,6 +41,10 @@ class NewstubeIE(InfoExtractor):
|
|||||||
def ns(s):
|
def ns(s):
|
||||||
return s.replace('/', '/%(ns)s') % {'ns': '{http://app1.newstube.ru/N2SiteWS/player.asmx}'}
|
return s.replace('/', '/%(ns)s') % {'ns': '{http://app1.newstube.ru/N2SiteWS/player.asmx}'}
|
||||||
|
|
||||||
|
error_message = player.find(ns('./ErrorMessage'))
|
||||||
|
if error_message is not None:
|
||||||
|
raise ExtractorError('%s returned error: %s' % (self.IE_NAME, error_message.text), expected=True)
|
||||||
|
|
||||||
session_id = player.find(ns('./SessionId')).text
|
session_id = player.find(ns('./SessionId')).text
|
||||||
media_info = player.find(ns('./Medias/MediaInfo'))
|
media_info = player.find(ns('./Medias/MediaInfo'))
|
||||||
title = media_info.find(ns('./Name')).text
|
title = media_info.find(ns('./Name')).text
|
||||||
|
@@ -8,10 +8,9 @@ from ..utils import (
|
|||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
compat_urllib_request,
|
compat_urllib_request,
|
||||||
compat_urlparse,
|
compat_urlparse,
|
||||||
compat_str,
|
|
||||||
|
|
||||||
ExtractorError,
|
|
||||||
unified_strdate,
|
unified_strdate,
|
||||||
|
parse_duration,
|
||||||
|
int_or_none,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -30,6 +29,7 @@ class NiconicoIE(InfoExtractor):
|
|||||||
'uploader_id': '2698420',
|
'uploader_id': '2698420',
|
||||||
'upload_date': '20131123',
|
'upload_date': '20131123',
|
||||||
'description': '(c) copyright 2008, Blender Foundation / www.bigbuckbunny.org',
|
'description': '(c) copyright 2008, Blender Foundation / www.bigbuckbunny.org',
|
||||||
|
'duration': 33,
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
'username': 'ydl.niconico@gmail.com',
|
'username': 'ydl.niconico@gmail.com',
|
||||||
@@ -37,17 +37,20 @@ class NiconicoIE(InfoExtractor):
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
_VALID_URL = r'^https?://(?:www\.|secure\.)?nicovideo\.jp/watch/([a-z][a-z][0-9]+)(?:.*)$'
|
_VALID_URL = r'https?://(?:www\.|secure\.)?nicovideo\.jp/watch/((?:[a-z]{2})?[0-9]+)'
|
||||||
_NETRC_MACHINE = 'niconico'
|
_NETRC_MACHINE = 'niconico'
|
||||||
|
# Determine whether the downloader uses authentication to download video
|
||||||
|
_AUTHENTICATE = False
|
||||||
|
|
||||||
def _real_initialize(self):
|
def _real_initialize(self):
|
||||||
self._login()
|
if self._downloader.params.get('username', None) is not None:
|
||||||
|
self._AUTHENTICATE = True
|
||||||
|
|
||||||
|
if self._AUTHENTICATE:
|
||||||
|
self._login()
|
||||||
|
|
||||||
def _login(self):
|
def _login(self):
|
||||||
(username, password) = self._get_login_info()
|
(username, password) = self._get_login_info()
|
||||||
if username is None:
|
|
||||||
# Login is required
|
|
||||||
raise ExtractorError('No login info available, needed for using %s.' % self.IE_NAME, expected=True)
|
|
||||||
|
|
||||||
# Log in
|
# Log in
|
||||||
login_form_strs = {
|
login_form_strs = {
|
||||||
@@ -79,44 +82,66 @@ class NiconicoIE(InfoExtractor):
|
|||||||
'http://ext.nicovideo.jp/api/getthumbinfo/' + video_id, video_id,
|
'http://ext.nicovideo.jp/api/getthumbinfo/' + video_id, video_id,
|
||||||
note='Downloading video info page')
|
note='Downloading video info page')
|
||||||
|
|
||||||
# Get flv info
|
if self._AUTHENTICATE:
|
||||||
flv_info_webpage = self._download_webpage(
|
# Get flv info
|
||||||
'http://flapi.nicovideo.jp/api/getflv?v=' + video_id,
|
flv_info_webpage = self._download_webpage(
|
||||||
video_id, 'Downloading flv info')
|
'http://flapi.nicovideo.jp/api/getflv?v=' + video_id,
|
||||||
|
video_id, 'Downloading flv info')
|
||||||
|
else:
|
||||||
|
# Get external player info
|
||||||
|
ext_player_info = self._download_webpage(
|
||||||
|
'http://ext.nicovideo.jp/thumb_watch/' + video_id, video_id)
|
||||||
|
thumb_play_key = self._search_regex(
|
||||||
|
r'\'thumbPlayKey\'\s*:\s*\'(.*?)\'', ext_player_info, 'thumbPlayKey')
|
||||||
|
|
||||||
|
# Get flv info
|
||||||
|
flv_info_data = compat_urllib_parse.urlencode({
|
||||||
|
'k': thumb_play_key,
|
||||||
|
'v': video_id
|
||||||
|
})
|
||||||
|
flv_info_request = compat_urllib_request.Request(
|
||||||
|
'http://ext.nicovideo.jp/thumb_watch', flv_info_data,
|
||||||
|
{'Content-Type': 'application/x-www-form-urlencoded'})
|
||||||
|
flv_info_webpage = self._download_webpage(
|
||||||
|
flv_info_request, video_id,
|
||||||
|
note='Downloading flv info', errnote='Unable to download flv info')
|
||||||
|
|
||||||
video_real_url = compat_urlparse.parse_qs(flv_info_webpage)['url'][0]
|
video_real_url = compat_urlparse.parse_qs(flv_info_webpage)['url'][0]
|
||||||
|
|
||||||
# Start extracting information
|
# Start extracting information
|
||||||
video_title = video_info.find('.//title').text
|
title = video_info.find('.//title').text
|
||||||
video_extension = video_info.find('.//movie_type').text
|
extension = video_info.find('.//movie_type').text
|
||||||
video_format = video_extension.upper()
|
video_format = extension.upper()
|
||||||
video_thumbnail = video_info.find('.//thumbnail_url').text
|
thumbnail = video_info.find('.//thumbnail_url').text
|
||||||
video_description = video_info.find('.//description').text
|
description = video_info.find('.//description').text
|
||||||
video_uploader_id = video_info.find('.//user_id').text
|
upload_date = unified_strdate(video_info.find('.//first_retrieve').text.split('+')[0])
|
||||||
video_upload_date = unified_strdate(video_info.find('.//first_retrieve').text.split('+')[0])
|
view_count = int_or_none(video_info.find('.//view_counter').text)
|
||||||
video_view_count = video_info.find('.//view_counter').text
|
comment_count = int_or_none(video_info.find('.//comment_num').text)
|
||||||
video_webpage_url = video_info.find('.//watch_url').text
|
duration = parse_duration(video_info.find('.//length').text)
|
||||||
|
webpage_url = video_info.find('.//watch_url').text
|
||||||
|
|
||||||
# uploader
|
if video_info.find('.//ch_id') is not None:
|
||||||
video_uploader = video_uploader_id
|
uploader_id = video_info.find('.//ch_id').text
|
||||||
url = 'http://seiga.nicovideo.jp/api/user/info?id=' + video_uploader_id
|
uploader = video_info.find('.//ch_name').text
|
||||||
try:
|
elif video_info.find('.//user_id') is not None:
|
||||||
user_info = self._download_xml(
|
uploader_id = video_info.find('.//user_id').text
|
||||||
url, video_id, note='Downloading user information')
|
uploader = video_info.find('.//user_nickname').text
|
||||||
video_uploader = user_info.find('.//nickname').text
|
else:
|
||||||
except ExtractorError as err:
|
uploader_id = uploader = None
|
||||||
self._downloader.report_warning('Unable to download user info webpage: %s' % compat_str(err))
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'url': video_real_url,
|
'url': video_real_url,
|
||||||
'title': video_title,
|
'title': title,
|
||||||
'ext': video_extension,
|
'ext': extension,
|
||||||
'format': video_format,
|
'format': video_format,
|
||||||
'thumbnail': video_thumbnail,
|
'thumbnail': thumbnail,
|
||||||
'description': video_description,
|
'description': description,
|
||||||
'uploader': video_uploader,
|
'uploader': uploader,
|
||||||
'upload_date': video_upload_date,
|
'upload_date': upload_date,
|
||||||
'uploader_id': video_uploader_id,
|
'uploader_id': uploader_id,
|
||||||
'view_count': video_view_count,
|
'view_count': view_count,
|
||||||
'webpage_url': video_webpage_url,
|
'comment_count': comment_count,
|
||||||
|
'duration': duration,
|
||||||
|
'webpage_url': webpage_url,
|
||||||
}
|
}
|
||||||
|
@@ -47,7 +47,7 @@ class NineGagIE(InfoExtractor):
|
|||||||
webpage = self._download_webpage(url, display_id)
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
|
||||||
post_view = json.loads(self._html_search_regex(
|
post_view = json.loads(self._html_search_regex(
|
||||||
r'var postView = new app\.PostView\({\s*post:\s*({.+?}),', webpage, 'post view'))
|
r'var postView = new app\.PostView\({\s*post:\s*({.+?}),\s*posts:\s*prefetchedCurrentPost', webpage, 'post view'))
|
||||||
|
|
||||||
youtube_id = post_view['videoExternalId']
|
youtube_id = post_view['videoExternalId']
|
||||||
title = post_view['title']
|
title = post_view['title']
|
||||||
|
@@ -35,7 +35,7 @@ class NocoIE(InfoExtractor):
|
|||||||
video_id = mobj.group('id')
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
medias = self._download_json(
|
medias = self._download_json(
|
||||||
'http://api.noco.tv/1.0/video/medias/%s' % video_id, video_id, 'Downloading video JSON')
|
'https://api.noco.tv/1.0/video/medias/%s' % video_id, video_id, 'Downloading video JSON')
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ class NocoIE(InfoExtractor):
|
|||||||
format_id = fmt['quality_key']
|
format_id = fmt['quality_key']
|
||||||
|
|
||||||
file = self._download_json(
|
file = self._download_json(
|
||||||
'http://api.noco.tv/1.0/video/file/%s/fr/%s' % (format_id.lower(), video_id),
|
'https://api.noco.tv/1.0/video/file/%s/fr/%s' % (format_id.lower(), video_id),
|
||||||
video_id, 'Downloading %s video JSON' % format_id)
|
video_id, 'Downloading %s video JSON' % format_id)
|
||||||
|
|
||||||
file_url = file['file']
|
file_url = file['file']
|
||||||
@@ -71,7 +71,7 @@ class NocoIE(InfoExtractor):
|
|||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
show = self._download_json(
|
show = self._download_json(
|
||||||
'http://api.noco.tv/1.0/shows/show/%s' % video_id, video_id, 'Downloading show JSON')[0]
|
'https://api.noco.tv/1.0/shows/show/%s' % video_id, video_id, 'Downloading show JSON')[0]
|
||||||
|
|
||||||
upload_date = unified_strdate(show['indexed'])
|
upload_date = unified_strdate(show['indexed'])
|
||||||
uploader = show['partner_name']
|
uploader = show['partner_name']
|
||||||
|
62
youtube_dl/extractor/npo.py
Normal file
62
youtube_dl/extractor/npo.py
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
unified_strdate,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class NPOIE(InfoExtractor):
|
||||||
|
IE_NAME = 'npo.nl'
|
||||||
|
_VALID_URL = r'https?://www\.npo\.nl/[^/]+/[^/]+/(?P<id>[^/?]+)'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.npo.nl/nieuwsuur/22-06-2014/VPWON_1220719',
|
||||||
|
'md5': '4b3f9c429157ec4775f2c9cb7b911016',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'VPWON_1220719',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Nieuwsuur',
|
||||||
|
'description': 'Dagelijks tussen tien en elf: nieuws, sport en achtergronden.',
|
||||||
|
'upload_date': '20140622',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
metadata = self._download_json(
|
||||||
|
'http://e.omroep.nl/metadata/aflevering/%s' % video_id,
|
||||||
|
video_id,
|
||||||
|
# We have to remove the javascript callback
|
||||||
|
transform_source=lambda j: re.sub(r'parseMetadata\((.*?)\);\n//epc', r'\1', j)
|
||||||
|
)
|
||||||
|
token_page = self._download_webpage(
|
||||||
|
'http://ida.omroep.nl/npoplayer/i.js',
|
||||||
|
video_id,
|
||||||
|
note='Downloading token'
|
||||||
|
)
|
||||||
|
token = self._search_regex(r'npoplayer.token = "(.+?)"', token_page, 'token')
|
||||||
|
streams_info = self._download_json(
|
||||||
|
'http://ida.omroep.nl/odi/?prid=%s&puboptions=h264_std&adaptive=yes&token=%s' % (video_id, token),
|
||||||
|
video_id
|
||||||
|
)
|
||||||
|
|
||||||
|
stream_info = self._download_json(
|
||||||
|
streams_info['streams'][0] + '&type=json',
|
||||||
|
video_id,
|
||||||
|
'Downloading stream info'
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': metadata['titel'],
|
||||||
|
'ext': 'mp4',
|
||||||
|
'url': stream_info['url'],
|
||||||
|
'description': metadata['info'],
|
||||||
|
'thumbnail': metadata['images'][-1]['url'],
|
||||||
|
'upload_date': unified_strdate(metadata['gidsdatum']),
|
||||||
|
}
|
@@ -6,7 +6,7 @@ import re
|
|||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
int_or_none,
|
float_or_none,
|
||||||
unified_strdate,
|
unified_strdate,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -72,14 +72,14 @@ class NRKIE(InfoExtractor):
|
|||||||
|
|
||||||
|
|
||||||
class NRKTVIE(InfoExtractor):
|
class NRKTVIE(InfoExtractor):
|
||||||
_VALID_URL = r'http://tv\.nrk(?:super)?\.no/(?:serie/[^/]+|program)/(?P<id>[a-z]{4}\d{8})'
|
_VALID_URL = r'http://tv\.nrk(?:super)?\.no/(?:serie/[^/]+|program)/(?P<id>[a-zA-Z]{4}\d{8})'
|
||||||
|
|
||||||
_TESTS = [
|
_TESTS = [
|
||||||
{
|
{
|
||||||
'url': 'http://tv.nrk.no/serie/20-spoersmaal-tv/muhh48000314/23-05-2014',
|
'url': 'http://tv.nrk.no/serie/20-spoersmaal-tv/MUHH48000314/23-05-2014',
|
||||||
'md5': '7b96112fbae1faf09a6f9ae1aff6cb84',
|
'md5': '7b96112fbae1faf09a6f9ae1aff6cb84',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'muhh48000314',
|
'id': 'MUHH48000314',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'title': '20 spørsmål',
|
'title': '20 spørsmål',
|
||||||
'description': 'md5:bdea103bc35494c143c6a9acdd84887a',
|
'description': 'md5:bdea103bc35494c143c6a9acdd84887a',
|
||||||
@@ -89,7 +89,7 @@ class NRKTVIE(InfoExtractor):
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
'url': 'http://tv.nrk.no/program/mdfp15000514',
|
'url': 'http://tv.nrk.no/program/mdfp15000514',
|
||||||
'md5': '383650ece2b25ecec996ad7b5bb2a384',
|
'md5': 'af01795a31f1cf7265c8657534d8077b',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'mdfp15000514',
|
'id': 'mdfp15000514',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
@@ -111,9 +111,8 @@ class NRKTVIE(InfoExtractor):
|
|||||||
description = self._html_search_meta('description', page, 'description')
|
description = self._html_search_meta('description', page, 'description')
|
||||||
thumbnail = self._html_search_regex(r'data-posterimage="([^"]+)"', page, 'thumbnail', fatal=False)
|
thumbnail = self._html_search_regex(r'data-posterimage="([^"]+)"', page, 'thumbnail', fatal=False)
|
||||||
upload_date = unified_strdate(self._html_search_meta('rightsfrom', page, 'upload date', fatal=False))
|
upload_date = unified_strdate(self._html_search_meta('rightsfrom', page, 'upload date', fatal=False))
|
||||||
duration = self._html_search_regex(r'data-duration="([^"]+)"', page, 'duration', fatal=False)
|
duration = float_or_none(
|
||||||
if duration:
|
self._html_search_regex(r'data-duration="([^"]+)"', page, 'duration', fatal=False))
|
||||||
duration = float(duration)
|
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
|
|
||||||
|
@@ -5,7 +5,6 @@ import re
|
|||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
|
||||||
unescapeHTML
|
unescapeHTML
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@@ -3,6 +3,11 @@ from __future__ import unicode_literals
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
parse_duration,
|
||||||
|
unified_strdate,
|
||||||
|
compat_urllib_request,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class NuvidIE(InfoExtractor):
|
class NuvidIE(InfoExtractor):
|
||||||
@@ -13,8 +18,10 @@ class NuvidIE(InfoExtractor):
|
|||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '1310741',
|
'id': '1310741',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
"title": "Horny babes show their awesome bodeis and",
|
'title': 'Horny babes show their awesome bodeis and',
|
||||||
"age_limit": 18,
|
'duration': 129,
|
||||||
|
'upload_date': '20140508',
|
||||||
|
'age_limit': 18,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,27 +29,41 @@ class NuvidIE(InfoExtractor):
|
|||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
video_id = mobj.group('id')
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
murl = url.replace('://www.', '://m.')
|
formats = []
|
||||||
webpage = self._download_webpage(murl, video_id)
|
|
||||||
|
|
||||||
|
for dwnld_speed, format_id in [(0, '3gp'), (5, 'mp4')]:
|
||||||
|
request = compat_urllib_request.Request(
|
||||||
|
'http://m.nuvid.com/play/%s' % video_id)
|
||||||
|
request.add_header('Cookie', 'skip_download_page=1; dwnld_speed=%d; adv_show=1' % dwnld_speed)
|
||||||
|
webpage = self._download_webpage(
|
||||||
|
request, video_id, 'Downloading %s page' % format_id)
|
||||||
|
video_url = self._html_search_regex(
|
||||||
|
r'<a href="([^"]+)"\s*>Continue to watch video', webpage, '%s video URL' % format_id, fatal=False)
|
||||||
|
if not video_url:
|
||||||
|
continue
|
||||||
|
formats.append({
|
||||||
|
'url': video_url,
|
||||||
|
'format_id': format_id,
|
||||||
|
})
|
||||||
|
|
||||||
|
webpage = self._download_webpage(
|
||||||
|
'http://m.nuvid.com/video/%s' % video_id, video_id, 'Downloading video page')
|
||||||
title = self._html_search_regex(
|
title = self._html_search_regex(
|
||||||
r'<div class="title">\s+<h2[^>]*>([^<]+)</h2>',
|
r'<div class="title">\s+<h2[^>]*>([^<]+)</h2>', webpage, 'title').strip()
|
||||||
webpage, 'title').strip()
|
|
||||||
|
|
||||||
url_end = self._html_search_regex(
|
|
||||||
r'href="(/[^"]+)"[^>]*data-link_type="mp4"',
|
|
||||||
webpage, 'video_url')
|
|
||||||
video_url = 'http://m.nuvid.com' + url_end
|
|
||||||
|
|
||||||
thumbnail = self._html_search_regex(
|
thumbnail = self._html_search_regex(
|
||||||
r'href="(/thumbs/[^"]+)"[^>]*data-link_type="thumbs"',
|
r'href="(/thumbs/[^"]+)"[^>]*data-link_type="thumbs"',
|
||||||
webpage, 'thumbnail URL', fatal=False)
|
webpage, 'thumbnail URL', fatal=False)
|
||||||
|
duration = parse_duration(self._html_search_regex(
|
||||||
|
r'Length:\s*<span>(\d{2}:\d{2})</span>',webpage, 'duration', fatal=False))
|
||||||
|
upload_date = unified_strdate(self._html_search_regex(
|
||||||
|
r'Added:\s*<span>(\d{4}-\d{2}-\d{2})</span>', webpage, 'upload date', fatal=False))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'url': video_url,
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': title,
|
'title': title,
|
||||||
'thumbnail': thumbnail,
|
'thumbnail': 'http://m.nuvid.com%s' % thumbnail,
|
||||||
|
'duration': duration,
|
||||||
|
'upload_date': upload_date,
|
||||||
'age_limit': 18,
|
'age_limit': 18,
|
||||||
|
'formats': formats,
|
||||||
}
|
}
|
@@ -158,19 +158,19 @@ class ProSiebenSat1IE(InfoExtractor):
|
|||||||
_CLIPID_REGEXES = [
|
_CLIPID_REGEXES = [
|
||||||
r'"clip_id"\s*:\s+"(\d+)"',
|
r'"clip_id"\s*:\s+"(\d+)"',
|
||||||
r'clipid: "(\d+)"',
|
r'clipid: "(\d+)"',
|
||||||
r'clipId=(\d+)',
|
r'clip[iI]d=(\d+)',
|
||||||
]
|
]
|
||||||
_TITLE_REGEXES = [
|
_TITLE_REGEXES = [
|
||||||
r'<h2 class="subtitle" itemprop="name">\s*(.+?)</h2>',
|
r'<h2 class="subtitle" itemprop="name">\s*(.+?)</h2>',
|
||||||
r'<header class="clearfix">\s*<h3>(.+?)</h3>',
|
r'<header class="clearfix">\s*<h3>(.+?)</h3>',
|
||||||
r'<!-- start video -->\s*<h1>(.+?)</h1>',
|
r'<!-- start video -->\s*<h1>(.+?)</h1>',
|
||||||
r'<div class="ep-femvideos-pi4-video-txt">\s*<h2>(.+?)</h2>',
|
r'<h1 class="att-name">\s*(.+?)</h1>',
|
||||||
]
|
]
|
||||||
_DESCRIPTION_REGEXES = [
|
_DESCRIPTION_REGEXES = [
|
||||||
r'<p itemprop="description">\s*(.+?)</p>',
|
r'<p itemprop="description">\s*(.+?)</p>',
|
||||||
r'<div class="videoDecription">\s*<p><strong>Beschreibung</strong>: (.+?)</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'<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">',
|
r'<p class="att-description">\s*(.+?)\s*</p>',
|
||||||
]
|
]
|
||||||
_UPLOAD_DATE_REGEXES = [
|
_UPLOAD_DATE_REGEXES = [
|
||||||
r'<meta property="og:published_time" content="(.+?)">',
|
r'<meta property="og:published_time" content="(.+?)">',
|
||||||
|
@@ -46,7 +46,7 @@ class PyvideoIE(InfoExtractor):
|
|||||||
return self.url_result(m_youtube.group(1), 'Youtube')
|
return self.url_result(m_youtube.group(1), 'Youtube')
|
||||||
|
|
||||||
title = self._html_search_regex(
|
title = self._html_search_regex(
|
||||||
r'<div class="section">.*?<h3(?:\s+class="[^"]*")?>([^>]+?)</h3>',
|
r'<div class="section">\s*<h3(?:\s+class="[^"]*"[^>]*)?>([^>]+?)</h3>',
|
||||||
webpage, 'title', flags=re.DOTALL)
|
webpage, 'title', flags=re.DOTALL)
|
||||||
video_url = self._search_regex(
|
video_url = self._search_regex(
|
||||||
[r'<source src="(.*?)"', r'<dt>Download</dt>.*?<a href="(.+?)"'],
|
[r'<source src="(.*?)"', r'<dt>Download</dt>.*?<a href="(.+?)"'],
|
||||||
|
122
youtube_dl/extractor/rai.py
Normal file
122
youtube_dl/extractor/rai.py
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .subtitles import SubtitlesInfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
parse_duration,
|
||||||
|
unified_strdate,
|
||||||
|
compat_urllib_parse,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class RaiIE(SubtitlesInfoExtractor):
|
||||||
|
_VALID_URL = r'(?P<url>http://(?:.+?\.)?(?:rai\.it|rai\.tv|rainews\.it)/dl/.+?-(?P<id>[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12})(?:-.+?)?\.html)'
|
||||||
|
_TESTS = [
|
||||||
|
{
|
||||||
|
'url': 'http://www.rai.tv/dl/RaiTV/programmi/media/ContentItem-cb27157f-9dd0-4aee-b788-b1f67643a391.html',
|
||||||
|
'md5': 'c064c0b2d09c278fb293116ef5d0a32d',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'cb27157f-9dd0-4aee-b788-b1f67643a391',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Report del 07/04/2014',
|
||||||
|
'description': 'md5:f27c544694cacb46a078db84ec35d2d9',
|
||||||
|
'upload_date': '20140407',
|
||||||
|
'duration': 6160,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://www.raisport.rai.it/dl/raiSport/media/rassegna-stampa-04a9f4bd-b563-40cf-82a6-aad3529cb4a9.html',
|
||||||
|
'md5': '8bb9c151924ce241b74dd52ef29ceafa',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '04a9f4bd-b563-40cf-82a6-aad3529cb4a9',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'TG PRIMO TEMPO',
|
||||||
|
'description': '',
|
||||||
|
'upload_date': '20140612',
|
||||||
|
'duration': 1758,
|
||||||
|
},
|
||||||
|
'skip': 'Error 404',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://www.rainews.it/dl/rainews/media/state-of-the-net-Antonella-La-Carpia-regole-virali-7aafdea9-0e5d-49d5-88a6-7e65da67ae13.html',
|
||||||
|
'md5': '35cf7c229f22eeef43e48b5cf923bef0',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '7aafdea9-0e5d-49d5-88a6-7e65da67ae13',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'State of the Net, Antonella La Carpia: regole virali',
|
||||||
|
'description': 'md5:b0ba04a324126903e3da7763272ae63c',
|
||||||
|
'upload_date': '20140613',
|
||||||
|
},
|
||||||
|
'skip': 'Error 404',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://www.rai.tv/dl/RaiTV/programmi/media/ContentItem-b4a49761-e0cc-4b14-8736-2729f6f73132-tg2.html',
|
||||||
|
'md5': '35694f062977fe6619943f08ed935730',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'b4a49761-e0cc-4b14-8736-2729f6f73132',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Alluvione in Sardegna e dissesto idrogeologico',
|
||||||
|
'description': 'Edizione delle ore 20:30 ',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
media = self._download_json('%s?json' % mobj.group('url'), video_id, 'Downloading video JSON')
|
||||||
|
|
||||||
|
title = media.get('name')
|
||||||
|
description = media.get('desc')
|
||||||
|
thumbnail = media.get('image_300') or media.get('image_medium') or media.get('image')
|
||||||
|
duration = parse_duration(media.get('length'))
|
||||||
|
uploader = media.get('author')
|
||||||
|
upload_date = unified_strdate(media.get('date'))
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
|
||||||
|
for format_id in ['wmv', 'm3u8', 'mediaUri', 'h264']:
|
||||||
|
media_url = media.get(format_id)
|
||||||
|
if not media_url:
|
||||||
|
continue
|
||||||
|
formats.append({
|
||||||
|
'url': media_url,
|
||||||
|
'format_id': format_id,
|
||||||
|
'ext': 'mp4',
|
||||||
|
})
|
||||||
|
|
||||||
|
if self._downloader.params.get('listsubtitles', False):
|
||||||
|
page = self._download_webpage(url, video_id)
|
||||||
|
self._list_available_subtitles(video_id, page)
|
||||||
|
return
|
||||||
|
|
||||||
|
subtitles = {}
|
||||||
|
if self._have_to_download_any_subtitles:
|
||||||
|
page = self._download_webpage(url, video_id)
|
||||||
|
subtitles = self.extract_subtitles(video_id, page)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'uploader': uploader,
|
||||||
|
'upload_date': upload_date,
|
||||||
|
'duration': duration,
|
||||||
|
'formats': formats,
|
||||||
|
'subtitles': subtitles,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_available_subtitles(self, video_id, webpage):
|
||||||
|
subtitles = {}
|
||||||
|
m = re.search(r'<meta name="closedcaption" content="(?P<captions>[^"]+)"', webpage)
|
||||||
|
if m:
|
||||||
|
captions = m.group('captions')
|
||||||
|
STL_EXT = '.stl'
|
||||||
|
SRT_EXT = '.srt'
|
||||||
|
if captions.endswith(STL_EXT):
|
||||||
|
captions = captions[:-len(STL_EXT)] + SRT_EXT
|
||||||
|
subtitles['it'] = 'http://www.rai.tv%s' % compat_urllib_parse.quote(captions)
|
||||||
|
return subtitles
|
45
youtube_dl/extractor/reverbnation.py
Normal file
45
youtube_dl/extractor/reverbnation.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import strip_jsonp
|
||||||
|
|
||||||
|
|
||||||
|
class ReverbNationIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'^https?://(?:www\.)?reverbnation\.com/.*?/song/(?P<id>\d+).*?$'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://www.reverbnation.com/alkilados/song/16965047-mona-lisa',
|
||||||
|
'file': '16965047.mp3',
|
||||||
|
'md5': '3da12ebca28c67c111a7f8b262d3f7a7',
|
||||||
|
'info_dict': {
|
||||||
|
"title": "MONA LISA",
|
||||||
|
"uploader": "ALKILADOS",
|
||||||
|
"uploader_id": 216429,
|
||||||
|
"thumbnail": "//gp1.wac.edgecastcdn.net/802892/production_public/Photo/13761700/image/1366002176_AVATAR_MONA_LISA.jpg"
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
song_id = mobj.group('id')
|
||||||
|
|
||||||
|
api_res = self._download_json(
|
||||||
|
'https://api.reverbnation.com/song/%s?callback=api_response_5&_=%d'
|
||||||
|
% (song_id, int(time.time() * 1000)),
|
||||||
|
song_id,
|
||||||
|
transform_source=strip_jsonp,
|
||||||
|
note='Downloading information of song %s' % song_id
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': song_id,
|
||||||
|
'title': api_res.get('name'),
|
||||||
|
'url': api_res.get('url'),
|
||||||
|
'uploader': api_res.get('artist', {}).get('name'),
|
||||||
|
'uploader_id': api_res.get('artist', {}).get('id'),
|
||||||
|
'thumbnail': api_res.get('image', api_res.get('thumbnail')),
|
||||||
|
'ext': 'mp3',
|
||||||
|
'vcodec': 'none',
|
||||||
|
}
|
46
youtube_dl/extractor/ruhd.py
Normal file
46
youtube_dl/extractor/ruhd.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
|
class RUHDIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'http://(?:www\.)?ruhd\.ru/play\.php\?vid=(?P<id>\d+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.ruhd.ru/play.php?vid=207',
|
||||||
|
'md5': 'd1a9ec4edf8598e3fbd92bb16072ba83',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '207',
|
||||||
|
'ext': 'divx',
|
||||||
|
'title': 'КОТ бааааам',
|
||||||
|
'description': 'классный кот)',
|
||||||
|
'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_url = self._html_search_regex(
|
||||||
|
r'<param name="src" value="([^"]+)"', webpage, 'video url')
|
||||||
|
title = self._html_search_regex(
|
||||||
|
r'<title>([^<]+) RUHD.ru - Видео Высокого качества №1 в России!</title>', webpage, 'title')
|
||||||
|
description = self._html_search_regex(
|
||||||
|
r'(?s)<div id="longdesc">(.+?)<span id="showlink">', webpage, 'description', fatal=False)
|
||||||
|
thumbnail = self._html_search_regex(
|
||||||
|
r'<param name="previewImage" value="([^"]+)"', webpage, 'thumbnail', fatal=False)
|
||||||
|
if thumbnail:
|
||||||
|
thumbnail = 'http://www.ruhd.ru' + thumbnail
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'url': video_url,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
}
|
112
youtube_dl/extractor/screencast.py
Normal file
112
youtube_dl/extractor/screencast.py
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
ExtractorError,
|
||||||
|
compat_parse_qs,
|
||||||
|
compat_urllib_request,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ScreencastIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://www\.screencast\.com/t/(?P<id>[a-zA-Z0-9]+)'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://www.screencast.com/t/3ZEjQXlT',
|
||||||
|
'md5': '917df1c13798a3e96211dd1561fded83',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '3ZEjQXlT',
|
||||||
|
'ext': 'm4v',
|
||||||
|
'title': 'Color Measurement with Ocean Optics Spectrometers',
|
||||||
|
'description': 'md5:240369cde69d8bed61349a199c5fb153',
|
||||||
|
'thumbnail': 're:^https?://.*\.(?:gif|jpg)$',
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.screencast.com/t/V2uXehPJa1ZI',
|
||||||
|
'md5': 'e8e4b375a7660a9e7e35c33973410d34',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'V2uXehPJa1ZI',
|
||||||
|
'ext': 'mov',
|
||||||
|
'title': 'The Amadeus Spectrometer',
|
||||||
|
'description': 're:^In this video, our friends at.*To learn more about Amadeus, visit',
|
||||||
|
'thumbnail': 're:^https?://.*\.(?:gif|jpg)$',
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.screencast.com/t/aAB3iowa',
|
||||||
|
'md5': 'dedb2734ed00c9755761ccaee88527cd',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'aAB3iowa',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Google Earth Export',
|
||||||
|
'description': 'Provides a demo of a CommunityViz export to Google Earth, one of the 3D viewing options.',
|
||||||
|
'thumbnail': 're:^https?://.*\.(?:gif|jpg)$',
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.screencast.com/t/X3ddTrYh',
|
||||||
|
'md5': '669ee55ff9c51988b4ebc0877cc8b159',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'X3ddTrYh',
|
||||||
|
'ext': 'wmv',
|
||||||
|
'title': 'Toolkit 6 User Group Webinar (2014-03-04) - Default Judgment and First Impression',
|
||||||
|
'description': 'md5:7b9f393bc92af02326a5c5889639eab0',
|
||||||
|
'thumbnail': 're:^https?://.*\.(?:gif|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_url = self._html_search_regex(
|
||||||
|
r'<embed name="Video".*?src="([^"]+)"', webpage,
|
||||||
|
'QuickTime embed', default=None)
|
||||||
|
|
||||||
|
if video_url is None:
|
||||||
|
flash_vars_s = self._html_search_regex(
|
||||||
|
r'<param name="flashVars" value="([^"]+)"', webpage, 'flash vars',
|
||||||
|
default=None)
|
||||||
|
if not flash_vars_s:
|
||||||
|
flash_vars_s = self._html_search_regex(
|
||||||
|
r'<param name="initParams" value="([^"]+)"', webpage, 'flash vars',
|
||||||
|
default=None)
|
||||||
|
if flash_vars_s:
|
||||||
|
flash_vars_s = flash_vars_s.replace(',', '&')
|
||||||
|
if flash_vars_s:
|
||||||
|
flash_vars = compat_parse_qs(flash_vars_s)
|
||||||
|
video_url_raw = compat_urllib_request.quote(
|
||||||
|
flash_vars['content'][0])
|
||||||
|
video_url = video_url_raw.replace('http%3A', 'http:')
|
||||||
|
|
||||||
|
if video_url is None:
|
||||||
|
video_meta = self._html_search_meta(
|
||||||
|
'og:video', webpage, default=None)
|
||||||
|
if video_meta:
|
||||||
|
video_url = self._search_regex(
|
||||||
|
r'src=(.*?)(?:$|&)', video_meta,
|
||||||
|
'meta tag video URL', default=None)
|
||||||
|
|
||||||
|
if video_url is None:
|
||||||
|
raise ExtractorError('Cannot find video')
|
||||||
|
|
||||||
|
title = self._og_search_title(webpage, default=None)
|
||||||
|
if title is None:
|
||||||
|
title = self._html_search_regex(
|
||||||
|
[r'<b>Title:</b> ([^<]*)</div>',
|
||||||
|
r'class="tabSeperator">></span><span class="tabText">(.*?)<'],
|
||||||
|
webpage, 'title')
|
||||||
|
thumbnail = self._og_search_thumbnail(webpage)
|
||||||
|
description = self._og_search_description(webpage, default=None)
|
||||||
|
if description is None:
|
||||||
|
description = self._html_search_meta('description', webpage)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'url': video_url,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
}
|
@@ -3,9 +3,6 @@ from __future__ import unicode_literals
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
|
||||||
ExtractorError,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class SlutloadIE(InfoExtractor):
|
class SlutloadIE(InfoExtractor):
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import json
|
|
||||||
import re
|
import re
|
||||||
import itertools
|
import itertools
|
||||||
|
|
||||||
@@ -12,6 +11,7 @@ from ..utils import (
|
|||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
|
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
|
int_or_none,
|
||||||
unified_strdate,
|
unified_strdate,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -44,7 +44,8 @@ class SoundcloudIE(InfoExtractor):
|
|||||||
"upload_date": "20121011",
|
"upload_date": "20121011",
|
||||||
"description": "No Downloads untill we record the finished version this weekend, i was too pumped n i had to post it , earl is prolly gonna b hella p.o'd",
|
"description": "No Downloads untill we record the finished version this weekend, i was too pumped n i had to post it , earl is prolly gonna b hella p.o'd",
|
||||||
"uploader": "E.T. ExTerrestrial Music",
|
"uploader": "E.T. ExTerrestrial Music",
|
||||||
"title": "Lostin Powers - She so Heavy (SneakPreview) Adrian Ackers Blueprint 1"
|
"title": "Lostin Powers - She so Heavy (SneakPreview) Adrian Ackers Blueprint 1",
|
||||||
|
"duration": 143,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
# not streamable song
|
# not streamable song
|
||||||
@@ -57,6 +58,7 @@ class SoundcloudIE(InfoExtractor):
|
|||||||
'description': 'From Stockholm Sweden\r\nPovel / Magnus / Filip / David\r\nwww.theroyalconcept.com',
|
'description': 'From Stockholm Sweden\r\nPovel / Magnus / Filip / David\r\nwww.theroyalconcept.com',
|
||||||
'uploader': 'The Royal Concept',
|
'uploader': 'The Royal Concept',
|
||||||
'upload_date': '20120521',
|
'upload_date': '20120521',
|
||||||
|
'duration': 227,
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
# rtmp
|
# rtmp
|
||||||
@@ -74,19 +76,21 @@ class SoundcloudIE(InfoExtractor):
|
|||||||
'uploader': 'jaimeMF',
|
'uploader': 'jaimeMF',
|
||||||
'description': 'test chars: \"\'/\\ä↭',
|
'description': 'test chars: \"\'/\\ä↭',
|
||||||
'upload_date': '20131209',
|
'upload_date': '20131209',
|
||||||
|
'duration': 9,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
# downloadable song
|
# downloadable song
|
||||||
{
|
{
|
||||||
'url': 'https://soundcloud.com/simgretina/just-your-problem-baby-1',
|
'url': 'https://soundcloud.com/oddsamples/bus-brakes',
|
||||||
'md5': '56a8b69568acaa967b4c49f9d1d52d19',
|
'md5': 'fee7b8747b09bb755cefd4b853e7249a',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '105614606',
|
'id': '128590877',
|
||||||
'ext': 'wav',
|
'ext': 'wav',
|
||||||
'title': 'Just Your Problem Baby (Acapella)',
|
'title': 'Bus Brakes',
|
||||||
'description': 'Vocals',
|
'description': 'md5:0170be75dd395c96025d210d261c784e',
|
||||||
'uploader': 'Sim Gretina',
|
'uploader': 'oddsamples',
|
||||||
'upload_date': '20130815',
|
'upload_date': '20140109',
|
||||||
|
'duration': 17,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -119,6 +123,7 @@ class SoundcloudIE(InfoExtractor):
|
|||||||
'title': info['title'],
|
'title': info['title'],
|
||||||
'description': info['description'],
|
'description': info['description'],
|
||||||
'thumbnail': thumbnail,
|
'thumbnail': thumbnail,
|
||||||
|
'duration': int_or_none(info.get('duration'), 1000),
|
||||||
}
|
}
|
||||||
formats = []
|
formats = []
|
||||||
if info.get('downloadable', False):
|
if info.get('downloadable', False):
|
||||||
@@ -250,7 +255,7 @@ class SoundcloudSetIE(SoundcloudIE):
|
|||||||
|
|
||||||
|
|
||||||
class SoundcloudUserIE(SoundcloudIE):
|
class SoundcloudUserIE(SoundcloudIE):
|
||||||
_VALID_URL = r'https?://(www\.)?soundcloud\.com/(?P<user>[^/]+)(/?(tracks/)?)?(\?.*)?$'
|
_VALID_URL = r'https?://(www\.)?soundcloud\.com/(?P<user>[^/]+)/?((?P<rsrc>tracks|likes)/?)?(\?.*)?$'
|
||||||
IE_NAME = 'soundcloud:user'
|
IE_NAME = 'soundcloud:user'
|
||||||
|
|
||||||
# it's in tests/test_playlists.py
|
# it's in tests/test_playlists.py
|
||||||
@@ -259,24 +264,31 @@ class SoundcloudUserIE(SoundcloudIE):
|
|||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
uploader = mobj.group('user')
|
uploader = mobj.group('user')
|
||||||
|
resource = mobj.group('rsrc')
|
||||||
|
if resource is None:
|
||||||
|
resource = 'tracks'
|
||||||
|
elif resource == 'likes':
|
||||||
|
resource = 'favorites'
|
||||||
|
|
||||||
url = 'http://soundcloud.com/%s/' % uploader
|
url = 'http://soundcloud.com/%s/' % uploader
|
||||||
resolv_url = self._resolv_url(url)
|
resolv_url = self._resolv_url(url)
|
||||||
user = self._download_json(
|
user = self._download_json(
|
||||||
resolv_url, uploader, 'Downloading user info')
|
resolv_url, uploader, 'Downloading user info')
|
||||||
base_url = 'http://api.soundcloud.com/users/%s/tracks.json?' % uploader
|
base_url = 'http://api.soundcloud.com/users/%s/%s.json?' % (uploader, resource)
|
||||||
|
|
||||||
entries = []
|
entries = []
|
||||||
for i in itertools.count():
|
for i in itertools.count():
|
||||||
data = compat_urllib_parse.urlencode({
|
data = compat_urllib_parse.urlencode({
|
||||||
'offset': i * 50,
|
'offset': i * 50,
|
||||||
|
'limit': 50,
|
||||||
'client_id': self._CLIENT_ID,
|
'client_id': self._CLIENT_ID,
|
||||||
})
|
})
|
||||||
new_entries = self._download_json(
|
new_entries = self._download_json(
|
||||||
base_url + data, uploader, 'Downloading track page %s' % (i + 1))
|
base_url + data, uploader, 'Downloading track page %s' % (i + 1))
|
||||||
entries.extend(self._extract_info_dict(e, quiet=True) for e in new_entries)
|
if len(new_entries) == 0:
|
||||||
if len(new_entries) < 50:
|
self.to_screen('%s: End page received' % uploader)
|
||||||
break
|
break
|
||||||
|
entries.extend(self._extract_info_dict(e, quiet=True) for e in new_entries)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'_type': 'playlist',
|
'_type': 'playlist',
|
||||||
|
40
youtube_dl/extractor/soundgasm.py
Normal file
40
youtube_dl/extractor/soundgasm.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
|
class SoundgasmIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?soundgasm\.net/u/(?P<user>[0-9a-zA-Z_\-]+)/(?P<title>[0-9a-zA-Z_\-]+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://soundgasm.net/u/ytdl/Piano-sample',
|
||||||
|
'md5': '010082a2c802c5275bb00030743e75ad',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '88abd86ea000cafe98f96321b23cc1206cbcbcc9',
|
||||||
|
'ext': 'm4a',
|
||||||
|
'title': 'ytdl_Piano-sample',
|
||||||
|
'description': 'Royalty Free Sample Music'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
display_id = mobj.group('title')
|
||||||
|
audio_title = mobj.group('user') + '_' + mobj.group('title')
|
||||||
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
audio_url = self._html_search_regex(
|
||||||
|
r'(?s)m4a\:\s"([^"]+)"', webpage, 'audio URL')
|
||||||
|
audio_id = re.split('\/|\.', audio_url)[-2]
|
||||||
|
description = self._html_search_regex(
|
||||||
|
r'(?s)<li>Description:\s(.*?)<\/li>', webpage, 'description',
|
||||||
|
fatal=False)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': audio_id,
|
||||||
|
'display_id': display_id,
|
||||||
|
'url': audio_url,
|
||||||
|
'title': audio_title,
|
||||||
|
'description': description
|
||||||
|
}
|
@@ -3,24 +3,24 @@ from __future__ import unicode_literals
|
|||||||
from .mtv import MTVServicesInfoExtractor
|
from .mtv import MTVServicesInfoExtractor
|
||||||
|
|
||||||
|
|
||||||
class SouthParkStudiosIE(MTVServicesInfoExtractor):
|
class SouthParkIE(MTVServicesInfoExtractor):
|
||||||
IE_NAME = 'southparkstudios.com'
|
IE_NAME = 'southpark.cc.com'
|
||||||
_VALID_URL = r'https?://(www\.)?(?P<url>southparkstudios\.com/(clips|full-episodes)/(?P<id>.+?)(\?|#|$))'
|
_VALID_URL = r'https?://(www\.)?(?P<url>southpark\.cc\.com/(clips|full-episodes)/(?P<id>.+?)(\?|#|$))'
|
||||||
|
|
||||||
_FEED_URL = 'http://www.southparkstudios.com/feeds/video-player/mrss'
|
_FEED_URL = 'http://www.southparkstudios.com/feeds/video-player/mrss'
|
||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.southparkstudios.com/clips/104437/bat-daded#tab=featured',
|
'url': 'http://southpark.cc.com/clips/104437/bat-daded#tab=featured',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'a7bff6c2-ed00-11e0-aca6-0026b9414f30',
|
'id': 'a7bff6c2-ed00-11e0-aca6-0026b9414f30',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Bat Daded',
|
'title': 'South Park|Bat Daded',
|
||||||
'description': 'Randy disqualifies South Park by getting into a fight with Bat Dad.',
|
'description': 'Randy disqualifies South Park by getting into a fight with Bat Dad.',
|
||||||
},
|
},
|
||||||
}]
|
}]
|
||||||
|
|
||||||
|
|
||||||
class SouthparkDeIE(SouthParkStudiosIE):
|
class SouthparkDeIE(SouthParkIE):
|
||||||
IE_NAME = 'southpark.de'
|
IE_NAME = 'southpark.de'
|
||||||
_VALID_URL = r'https?://(www\.)?(?P<url>southpark\.de/(clips|alle-episoden)/(?P<id>.+?)(\?|#|$))'
|
_VALID_URL = r'https?://(www\.)?(?P<url>southpark\.de/(clips|alle-episoden)/(?P<id>.+?)(\?|#|$))'
|
||||||
_FEED_URL = 'http://www.southpark.de/feeds/video-player/mrss/'
|
_FEED_URL = 'http://www.southpark.de/feeds/video-player/mrss/'
|
@@ -1,3 +1,4 @@
|
|||||||
|
# encoding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
@@ -9,18 +10,33 @@ class SpiegelIE(InfoExtractor):
|
|||||||
_VALID_URL = r'https?://(?:www\.)?spiegel\.de/video/[^/]*-(?P<videoID>[0-9]+)(?:\.html)?(?:#.*)?$'
|
_VALID_URL = r'https?://(?:www\.)?spiegel\.de/video/[^/]*-(?P<videoID>[0-9]+)(?:\.html)?(?:#.*)?$'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.spiegel.de/video/vulkan-tungurahua-in-ecuador-ist-wieder-aktiv-video-1259285.html',
|
'url': 'http://www.spiegel.de/video/vulkan-tungurahua-in-ecuador-ist-wieder-aktiv-video-1259285.html',
|
||||||
'file': '1259285.mp4',
|
|
||||||
'md5': '2c2754212136f35fb4b19767d242f66e',
|
'md5': '2c2754212136f35fb4b19767d242f66e',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'id': '1259285',
|
||||||
|
'ext': 'mp4',
|
||||||
'title': 'Vulkanausbruch in Ecuador: Der "Feuerschlund" ist wieder aktiv',
|
'title': 'Vulkanausbruch in Ecuador: Der "Feuerschlund" ist wieder aktiv',
|
||||||
|
'description': 'md5:8029d8310232196eb235d27575a8b9f4',
|
||||||
|
'duration': 49,
|
||||||
},
|
},
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
'url': 'http://www.spiegel.de/video/schach-wm-videoanalyse-des-fuenften-spiels-video-1309159.html',
|
'url': 'http://www.spiegel.de/video/schach-wm-videoanalyse-des-fuenften-spiels-video-1309159.html',
|
||||||
'file': '1309159.mp4',
|
|
||||||
'md5': 'f2cdf638d7aa47654e251e1aee360af1',
|
'md5': 'f2cdf638d7aa47654e251e1aee360af1',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'id': '1309159',
|
||||||
|
'ext': 'mp4',
|
||||||
'title': 'Schach-WM in der Videoanalyse: Carlsen nutzt die Fehlgriffe des Titelverteidigers',
|
'title': 'Schach-WM in der Videoanalyse: Carlsen nutzt die Fehlgriffe des Titelverteidigers',
|
||||||
|
'description': 'md5:c2322b65e58f385a820c10fa03b2d088',
|
||||||
|
'duration': 983,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.spiegel.de/video/johann-westhauser-videobotschaft-des-hoehlenforschers-video-1502367.html',
|
||||||
|
'md5': '54f58ba0e752e3c07bc2a26222dd0acf',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '1502367',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Videobotschaft: Höhlenforscher Westhauser dankt seinen Rettern',
|
||||||
|
'description': 'md5:c6f1ec11413ebd1088b6813943e5fc91',
|
||||||
|
'duration': 42,
|
||||||
},
|
},
|
||||||
}]
|
}]
|
||||||
|
|
||||||
@@ -30,18 +46,20 @@ class SpiegelIE(InfoExtractor):
|
|||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
video_title = self._html_search_regex(
|
title = self._html_search_regex(
|
||||||
r'<div class="module-title">(.*?)</div>', webpage, 'title')
|
r'<div class="module-title">(.*?)</div>', webpage, 'title')
|
||||||
|
description = self._html_search_meta('description', webpage, 'description')
|
||||||
|
|
||||||
xml_url = 'http://video2.spiegel.de/flash/' + video_id + '.xml'
|
base_url = self._search_regex(
|
||||||
idoc = self._download_xml(
|
r'var\s+server\s*=\s*"([^"]+)\"', webpage, 'server URL')
|
||||||
xml_url, video_id,
|
|
||||||
note='Downloading XML', errnote='Failed to download XML')
|
xml_url = base_url + video_id + '.xml'
|
||||||
|
idoc = self._download_xml(xml_url, video_id)
|
||||||
|
|
||||||
formats = [
|
formats = [
|
||||||
{
|
{
|
||||||
'format_id': n.tag.rpartition('type')[2],
|
'format_id': n.tag.rpartition('type')[2],
|
||||||
'url': 'http://video2.spiegel.de/flash/' + n.find('./filename').text,
|
'url': base_url + n.find('./filename').text,
|
||||||
'width': int(n.find('./width').text),
|
'width': int(n.find('./width').text),
|
||||||
'height': int(n.find('./height').text),
|
'height': int(n.find('./height').text),
|
||||||
'abr': int(n.find('./audiobitrate').text),
|
'abr': int(n.find('./audiobitrate').text),
|
||||||
@@ -59,7 +77,8 @@ class SpiegelIE(InfoExtractor):
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': video_title,
|
'title': title,
|
||||||
|
'description': description,
|
||||||
'duration': duration,
|
'duration': duration,
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
}
|
}
|
||||||
|
81
youtube_dl/extractor/spiegeltv.py
Normal file
81
youtube_dl/extractor/spiegeltv.py
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
|
class SpiegeltvIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?spiegel\.tv/filme/(?P<id>[\-a-z0-9]+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.spiegel.tv/filme/flug-mh370/',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'flug-mh370',
|
||||||
|
'ext': 'm4v',
|
||||||
|
'title': 'Flug MH370',
|
||||||
|
'description': 'Das Rätsel um die Boeing 777 der Malaysia-Airlines',
|
||||||
|
'thumbnail': 're:http://.*\.jpg$',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# rtmp download
|
||||||
|
'skip_download': True,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
title = self._html_search_regex(r'<h1.*?>(.*?)</h1>', webpage, 'title')
|
||||||
|
|
||||||
|
apihost = 'http://spiegeltv-ivms2-restapi.s3.amazonaws.com'
|
||||||
|
version_json = self._download_json(
|
||||||
|
'%s/version.json' % apihost, video_id,
|
||||||
|
note='Downloading version information')
|
||||||
|
version_name = version_json['version_name']
|
||||||
|
|
||||||
|
slug_json = self._download_json(
|
||||||
|
'%s/%s/restapi/slugs/%s.json' % (apihost, version_name, video_id),
|
||||||
|
video_id,
|
||||||
|
note='Downloading object information')
|
||||||
|
oid = slug_json['object_id']
|
||||||
|
|
||||||
|
media_json = self._download_json(
|
||||||
|
'%s/%s/restapi/media/%s.json' % (apihost, version_name, oid),
|
||||||
|
video_id, note='Downloading media information')
|
||||||
|
uuid = media_json['uuid']
|
||||||
|
is_wide = media_json['is_wide']
|
||||||
|
|
||||||
|
server_json = self._download_json(
|
||||||
|
'http://www.spiegel.tv/streaming_servers/', video_id,
|
||||||
|
note='Downloading server information')
|
||||||
|
server = server_json[0]['endpoint']
|
||||||
|
|
||||||
|
thumbnails = []
|
||||||
|
for image in media_json['images']:
|
||||||
|
thumbnails.append({
|
||||||
|
'url': image['url'],
|
||||||
|
'width': image['width'],
|
||||||
|
'height': image['height'],
|
||||||
|
})
|
||||||
|
|
||||||
|
description = media_json['subtitle']
|
||||||
|
duration = media_json['duration_in_ms'] / 1000.
|
||||||
|
|
||||||
|
if is_wide:
|
||||||
|
format = '16x9'
|
||||||
|
else:
|
||||||
|
format = '4x3'
|
||||||
|
|
||||||
|
url = server + 'mp4:' + uuid + '_spiegeltv_0500_' + format + '.m4v'
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'url': url,
|
||||||
|
'ext': 'm4v',
|
||||||
|
'description': description,
|
||||||
|
'duration': duration,
|
||||||
|
'thumbnails': thumbnails
|
||||||
|
}
|
@@ -53,7 +53,7 @@ class SteamIE(InfoExtractor):
|
|||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'upload_date': '20140329',
|
'upload_date': '20140329',
|
||||||
'title': 'FRONTIERS - Final Greenlight Trailer',
|
'title': 'FRONTIERS - Final Greenlight Trailer',
|
||||||
'description': "The final trailer for the Steam Greenlight launch. Hooray, progress! Here's the official Greenlight page: http://steamcommunity.com/sharedfiles/filedetails/?id=242472205",
|
'description': 'md5:6df4fe8dd494ae811869672b0767e025',
|
||||||
'uploader': 'AAD Productions',
|
'uploader': 'AAD Productions',
|
||||||
'uploader_id': 'AtomicAgeDogGames',
|
'uploader_id': 'AtomicAgeDogGames',
|
||||||
}
|
}
|
||||||
|
79
youtube_dl/extractor/tagesschau.py
Normal file
79
youtube_dl/extractor/tagesschau.py
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
|
class TagesschauIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?tagesschau\.de/multimedia/video/video(?P<id>-?[0-9]+)\.html'
|
||||||
|
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://www.tagesschau.de/multimedia/video/video1399128.html',
|
||||||
|
'md5': 'bcdeac2194fb296d599ce7929dfa4009',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '1399128',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Harald Range, Generalbundesanwalt, zu den Ermittlungen',
|
||||||
|
'description': 'md5:69da3c61275b426426d711bde96463ab',
|
||||||
|
'thumbnail': 're:^http:.*\.jpg$',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.tagesschau.de/multimedia/video/video-5964.html',
|
||||||
|
'md5': '66652566900963a3f962333579eeffcf',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '5964',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Nahost-Konflikt: Israel bombadiert Ziele im Gazastreifen und Westjordanland',
|
||||||
|
'description': 'md5:07bfc78c48eec3145ed4805299a1900a',
|
||||||
|
'thumbnail': 're:http://.*\.jpg',
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
|
_FORMATS = {
|
||||||
|
's': {'width': 256, 'height': 144, 'quality': 1},
|
||||||
|
'm': {'width': 512, 'height': 288, 'quality': 2},
|
||||||
|
'l': {'width': 960, 'height': 544, 'quality': 3},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
if video_id.startswith('-'):
|
||||||
|
display_id = video_id.strip('-')
|
||||||
|
else:
|
||||||
|
display_id = video_id
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
|
||||||
|
playerpage = self._download_webpage(
|
||||||
|
'http://www.tagesschau.de/multimedia/video/video%s~player_autoplay-true.html' % video_id,
|
||||||
|
display_id, 'Downloading player page')
|
||||||
|
|
||||||
|
medias = re.findall(
|
||||||
|
r'"(http://media.+?)", type:"video/(.+?)", quality:"(.+?)"',
|
||||||
|
playerpage)
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
for url, ext, res in medias:
|
||||||
|
f = {
|
||||||
|
'format_id': res + '_' + ext,
|
||||||
|
'url': url,
|
||||||
|
'ext': ext,
|
||||||
|
}
|
||||||
|
f.update(self._FORMATS.get(res, {}))
|
||||||
|
formats.append(f)
|
||||||
|
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
thumbnail = re.findall(r'"(/multimedia/.+?\.jpg)"', playerpage)[-1]
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': display_id,
|
||||||
|
'title': self._og_search_title(webpage).strip(),
|
||||||
|
'thumbnail': 'http://www.tagesschau.de' + thumbnail,
|
||||||
|
'formats': formats,
|
||||||
|
'description': self._og_search_description(webpage).strip(),
|
||||||
|
}
|
124
youtube_dl/extractor/teachertube.py
Normal file
124
youtube_dl/extractor/teachertube.py
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
qualities,
|
||||||
|
determine_ext,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TeacherTubeIE(InfoExtractor):
|
||||||
|
IE_NAME = 'teachertube'
|
||||||
|
IE_DESC = 'teachertube.com videos'
|
||||||
|
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?teachertube\.com/(viewVideo\.php\?video_id=|music\.php\?music_id=|video/(?:[\da-z-]+-)?|audio/)(?P<id>\d+)'
|
||||||
|
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://www.teachertube.com/viewVideo.php?video_id=339997',
|
||||||
|
'md5': 'f9434ef992fd65936d72999951ee254c',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '339997',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Measures of dispersion from a frequency table',
|
||||||
|
'description': 'Measures of dispersion from a frequency table',
|
||||||
|
'thumbnail': 're:http://.*\.jpg',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.teachertube.com/viewVideo.php?video_id=340064',
|
||||||
|
'md5': '0d625ec6bc9bf50f70170942ad580676',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '340064',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'How to Make Paper Dolls _ Paper Art Projects',
|
||||||
|
'description': 'Learn how to make paper dolls in this simple',
|
||||||
|
'thumbnail': 're:http://.*\.jpg',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.teachertube.com/music.php?music_id=8805',
|
||||||
|
'md5': '01e8352006c65757caf7b961f6050e21',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '8805',
|
||||||
|
'ext': 'mp3',
|
||||||
|
'title': 'PER ASPERA AD ASTRA',
|
||||||
|
'description': 'RADIJSKA EMISIJA ZRAKOPLOVNE TEHNI?KE ?KOLE P',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.teachertube.com/video/intro-video-schleicher-297790',
|
||||||
|
'md5': '9c79fbb2dd7154823996fc28d4a26998',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '297790',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Intro Video - Schleicher',
|
||||||
|
'description': 'Intro Video - Why to flip, how flipping will',
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
title = self._html_search_meta('title', webpage, 'title')
|
||||||
|
TITLE_SUFFIX = ' - TeacherTube'
|
||||||
|
if title.endswith(TITLE_SUFFIX):
|
||||||
|
title = title[:-len(TITLE_SUFFIX)].strip()
|
||||||
|
|
||||||
|
description = self._html_search_meta('description', webpage, 'description')
|
||||||
|
if description:
|
||||||
|
description = description.strip()
|
||||||
|
|
||||||
|
quality = qualities(['mp3', 'flv', 'mp4'])
|
||||||
|
|
||||||
|
media_urls = re.findall(r'data-contenturl="([^"]+)"', webpage)
|
||||||
|
media_urls.extend(re.findall(r'var\s+filePath\s*=\s*"([^"]+)"', webpage))
|
||||||
|
media_urls.extend(re.findall(r'\'file\'\s*:\s*["\']([^"\']+)["\'],', webpage))
|
||||||
|
|
||||||
|
formats = [
|
||||||
|
{
|
||||||
|
'url': media_url,
|
||||||
|
'quality': quality(determine_ext(media_url))
|
||||||
|
} for media_url in set(media_urls)
|
||||||
|
]
|
||||||
|
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'thumbnail': self._html_search_regex(r'\'image\'\s*:\s*["\']([^"\']+)["\']', webpage, 'thumbnail'),
|
||||||
|
'formats': formats,
|
||||||
|
'description': description,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TeacherTubeUserIE(InfoExtractor):
|
||||||
|
IE_NAME = 'teachertube:user:collection'
|
||||||
|
IE_DESC = 'teachertube.com user and collection videos'
|
||||||
|
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?teachertube\.com/(user/profile|collection)/(?P<user>[0-9a-zA-Z]+)/?'
|
||||||
|
|
||||||
|
_MEDIA_RE = r'(?s)"sidebar_thumb_time">[0-9:]+</div>.+?<a href="(https?://(?:www\.)?teachertube\.com/(?:video|audio)/[^"]+)">'
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
user_id = mobj.group('user')
|
||||||
|
|
||||||
|
urls = []
|
||||||
|
webpage = self._download_webpage(url, user_id)
|
||||||
|
urls.extend(re.findall(self._MEDIA_RE, webpage))
|
||||||
|
|
||||||
|
pages = re.findall(r'/ajax-user/user-videos/%s\?page=([0-9]+)' % user_id, webpage)[1:-1]
|
||||||
|
for p in pages:
|
||||||
|
more = 'http://www.teachertube.com/ajax-user/user-videos/%s?page=%s' % (user_id, p)
|
||||||
|
webpage = self._download_webpage(more, user_id, 'Downloading page %s/%s' % (p, len(pages) + 1))
|
||||||
|
urls.extend(re.findall(self._MEDIA_RE, webpage))
|
||||||
|
|
||||||
|
entries = []
|
||||||
|
for url in urls:
|
||||||
|
entries.append(self.url_result(url, 'TeacherTube'))
|
||||||
|
|
||||||
|
return self.playlist_result(entries, user_id)
|
33
youtube_dl/extractor/teachingchannel.py
Normal file
33
youtube_dl/extractor/teachingchannel.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from .ooyala import OoyalaIE
|
||||||
|
|
||||||
|
|
||||||
|
class TeachingChannelIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://www\.teachingchannel\.org/videos/(?P<title>.+)'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'https://www.teachingchannel.org/videos/teacher-teaming-evolution',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'F3bnlzbToeI6pLEfRyrlfooIILUjz4nM',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'A History of Teaming',
|
||||||
|
'description': 'md5:2a9033db8da81f2edffa4c99888140b3',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# m3u8 download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
title = mobj.group('title')
|
||||||
|
webpage = self._download_webpage(url, title)
|
||||||
|
ooyala_code = self._search_regex(
|
||||||
|
r'data-embed-code=\'(.+?)\'', webpage, 'ooyala code')
|
||||||
|
|
||||||
|
return OoyalaIE._build_url_result(ooyala_code)
|
@@ -27,7 +27,7 @@ class TEDIE(SubtitlesInfoExtractor):
|
|||||||
'''
|
'''
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.ted.com/talks/dan_dennett_on_our_consciousness.html',
|
'url': 'http://www.ted.com/talks/dan_dennett_on_our_consciousness.html',
|
||||||
'md5': '4ea1dada91e4174b53dac2bb8ace429d',
|
'md5': 'fc94ac279feebbce69f21c0c6ee82810',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '102',
|
'id': '102',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
|
84
youtube_dl/extractor/tenplay.py
Normal file
84
youtube_dl/extractor/tenplay.py
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
|
class TenPlayIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?ten(play)?\.com\.au/.+'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://tenplay.com.au/ten-insider/extra/season-2013/tenplay-tv-your-way',
|
||||||
|
#'md5': 'd68703d9f73dc8fccf3320ab34202590',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '2695695426001',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'TENplay: TV your way',
|
||||||
|
'description': 'Welcome to a new TV experience. Enjoy a taste of the TENplay benefits.',
|
||||||
|
'timestamp': 1380150606.889,
|
||||||
|
'upload_date': '20130925',
|
||||||
|
'uploader': 'TENplay',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True, # Requires rtmpdump
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_video_fields = [
|
||||||
|
"id", "name", "shortDescription", "longDescription", "creationDate",
|
||||||
|
"publishedDate", "lastModifiedDate", "customFields", "videoStillURL",
|
||||||
|
"thumbnailURL", "referenceId", "length", "playsTotal",
|
||||||
|
"playsTrailingWeek", "renditions", "captioning", "startDate", "endDate"]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
webpage = self._download_webpage(url, url)
|
||||||
|
video_id = self._html_search_regex(
|
||||||
|
r'videoID: "(\d+?)"', webpage, 'video_id')
|
||||||
|
api_token = self._html_search_regex(
|
||||||
|
r'apiToken: "([a-zA-Z0-9-_\.]+?)"', webpage, 'api_token')
|
||||||
|
title = self._html_search_regex(
|
||||||
|
r'<meta property="og:title" content="\s*(.*?)\s*"\s*/?\s*>',
|
||||||
|
webpage, 'title')
|
||||||
|
|
||||||
|
json = self._download_json('https://api.brightcove.com/services/library?command=find_video_by_id&video_id=%s&token=%s&video_fields=%s' % (video_id, api_token, ','.join(self._video_fields)), title)
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
for rendition in json['renditions']:
|
||||||
|
url = rendition['remoteUrl'] or rendition['url']
|
||||||
|
protocol = 'rtmp' if url.startswith('rtmp') else 'http'
|
||||||
|
ext = 'flv' if protocol == 'rtmp' else rendition['videoContainer'].lower()
|
||||||
|
|
||||||
|
if protocol == 'rtmp':
|
||||||
|
url = url.replace('&mp4:', '')
|
||||||
|
|
||||||
|
formats.append({
|
||||||
|
'format_id': '_'.join(['rtmp', rendition['videoContainer'].lower(), rendition['videoCodec'].lower()]),
|
||||||
|
'width': rendition['frameWidth'],
|
||||||
|
'height': rendition['frameHeight'],
|
||||||
|
'tbr': rendition['encodingRate'] / 1024,
|
||||||
|
'filesize': rendition['size'],
|
||||||
|
'protocol': protocol,
|
||||||
|
'ext': ext,
|
||||||
|
'vcodec': rendition['videoCodec'].lower(),
|
||||||
|
'container': rendition['videoContainer'].lower(),
|
||||||
|
'url': url,
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'display_id': json['referenceId'],
|
||||||
|
'title': json['name'],
|
||||||
|
'description': json['shortDescription'] or json['longDescription'],
|
||||||
|
'formats': formats,
|
||||||
|
'thumbnails': [{
|
||||||
|
'url': json['videoStillURL']
|
||||||
|
}, {
|
||||||
|
'url': json['thumbnailURL']
|
||||||
|
}],
|
||||||
|
'thumbnail': json['videoStillURL'],
|
||||||
|
'duration': json['length'] / 1000,
|
||||||
|
'timestamp': float(json['creationDate']) / 1000,
|
||||||
|
'uploader': json['customFields']['production_company_distributor'] if 'production_company_distributor' in json['customFields'] else 'TENplay',
|
||||||
|
'view_count': json['playsTotal']
|
||||||
|
}
|
@@ -5,6 +5,7 @@ import re
|
|||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from .brightcove import BrightcoveIE
|
from .brightcove import BrightcoveIE
|
||||||
from .discovery import DiscoveryIE
|
from .discovery import DiscoveryIE
|
||||||
|
from ..utils import compat_urlparse
|
||||||
|
|
||||||
|
|
||||||
class TlcIE(DiscoveryIE):
|
class TlcIE(DiscoveryIE):
|
||||||
@@ -51,6 +52,10 @@ class TlcDeIE(InfoExtractor):
|
|||||||
# Otherwise we don't get the correct 'BrightcoveExperience' element,
|
# Otherwise we don't get the correct 'BrightcoveExperience' element,
|
||||||
# example: http://www.tlc.de/sendungen/cake-boss/videos/cake-boss-cannoli-drama/
|
# example: http://www.tlc.de/sendungen/cake-boss/videos/cake-boss-cannoli-drama/
|
||||||
iframe_url = iframe_url.replace('.htm?', '.php?')
|
iframe_url = iframe_url.replace('.htm?', '.php?')
|
||||||
|
url_fragment = compat_urlparse.urlparse(url).fragment
|
||||||
|
if url_fragment:
|
||||||
|
# Since the fragment is not send to the server, we always get the same iframe
|
||||||
|
iframe_url = re.sub(r'playlist=(\d+)', 'playlist=%s' % url_fragment, iframe_url)
|
||||||
iframe = self._download_webpage(iframe_url, title)
|
iframe = self._download_webpage(iframe_url, title)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@@ -1,10 +1,13 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
class ToypicsIE(InfoExtractor):
|
class ToypicsIE(InfoExtractor):
|
||||||
IE_DESC = 'Toypics user profile'
|
IE_DESC = 'Toypics user profile'
|
||||||
_VALID_URL = r'http://videos\.toypics\.net/view/(?P<id>[0-9]+)/.*'
|
_VALID_URL = r'https?://videos\.toypics\.net/view/(?P<id>[0-9]+)/.*'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://videos.toypics.net/view/514/chancebulged,-2-1/',
|
'url': 'http://videos.toypics.net/view/514/chancebulged,-2-1/',
|
||||||
'md5': '16e806ad6d6f58079d210fe30985e08b',
|
'md5': '16e806ad6d6f58079d210fe30985e08b',
|
||||||
@@ -61,7 +64,7 @@ class ToypicsUserIE(InfoExtractor):
|
|||||||
note='Downloading page %d/%d' % (n, page_count))
|
note='Downloading page %d/%d' % (n, page_count))
|
||||||
urls.extend(
|
urls.extend(
|
||||||
re.findall(
|
re.findall(
|
||||||
r'<p class="video-entry-title">\n\s*<a href="(http://videos.toypics.net/view/[^"]+)">',
|
r'<p class="video-entry-title">\s+<a href="(https?://videos.toypics.net/view/[^"]+)">',
|
||||||
lpage))
|
lpage))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@@ -17,9 +17,10 @@ class Tube8IE(InfoExtractor):
|
|||||||
_VALID_URL = r'https?://(?:www\.)?tube8\.com/(?:[^/]+/){2}(?P<id>\d+)'
|
_VALID_URL = r'https?://(?:www\.)?tube8\.com/(?:[^/]+/){2}(?P<id>\d+)'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.tube8.com/teen/kasia-music-video/229795/',
|
'url': 'http://www.tube8.com/teen/kasia-music-video/229795/',
|
||||||
'file': '229795.mp4',
|
'md5': '44bf12b98313827dd52d35b8706a4ea0',
|
||||||
'md5': 'e9e0b0c86734e5e3766e653509475db0',
|
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'id': '229795',
|
||||||
|
'ext': 'mp4',
|
||||||
'description': 'hot teen Kasia grinding',
|
'description': 'hot teen Kasia grinding',
|
||||||
'uploader': 'unknown',
|
'uploader': 'unknown',
|
||||||
'title': 'Kasia music video',
|
'title': 'Kasia music video',
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
@@ -10,14 +11,27 @@ from ..utils import (
|
|||||||
|
|
||||||
class TumblrIE(InfoExtractor):
|
class TumblrIE(InfoExtractor):
|
||||||
_VALID_URL = r'http://(?P<blog_name>.*?)\.tumblr\.com/((post)|(video))/(?P<id>\d*)($|/)'
|
_VALID_URL = r'http://(?P<blog_name>.*?)\.tumblr\.com/((post)|(video))/(?P<id>\d*)($|/)'
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
'url': 'http://tatianamaslanydaily.tumblr.com/post/54196191430/orphan-black-dvd-extra-behind-the-scenes',
|
'url': 'http://tatianamaslanydaily.tumblr.com/post/54196191430/orphan-black-dvd-extra-behind-the-scenes',
|
||||||
'file': '54196191430.mp4',
|
|
||||||
'md5': '479bb068e5b16462f5176a6828829767',
|
'md5': '479bb068e5b16462f5176a6828829767',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
"title": "tatiana maslany news"
|
'id': '54196191430',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'tatiana maslany news, Orphan Black || DVD extra - behind the scenes ↳...',
|
||||||
|
'description': 'md5:dfac39636969fe6bf1caa2d50405f069',
|
||||||
|
'thumbnail': 're:http://.*\.jpg',
|
||||||
}
|
}
|
||||||
}
|
}, {
|
||||||
|
'url': 'http://5sostrum.tumblr.com/post/90208453769/yall-forgetting-the-greatest-keek-of-them-all',
|
||||||
|
'md5': 'bf348ef8c0ef84fbf1cbd6fa6e000359',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '90208453769',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': '5SOS STRUM ;)',
|
||||||
|
'description': 'md5:dba62ac8639482759c8eb10ce474586a',
|
||||||
|
'thumbnail': 're:http://.*\.jpg',
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
m_url = re.match(self._VALID_URL, url)
|
m_url = re.match(self._VALID_URL, url)
|
||||||
@@ -48,6 +62,7 @@ class TumblrIE(InfoExtractor):
|
|||||||
return [{'id': video_id,
|
return [{'id': video_id,
|
||||||
'url': video_url,
|
'url': video_url,
|
||||||
'title': video_title,
|
'title': video_title,
|
||||||
|
'description': self._html_search_meta('description', webpage),
|
||||||
'thumbnail': video_thumbnail,
|
'thumbnail': video_thumbnail,
|
||||||
'ext': ext
|
'ext': ext
|
||||||
}]
|
}]
|
||||||
|
@@ -1,21 +1,21 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import compat_parse_qs
|
||||||
compat_parse_qs,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TutvIE(InfoExtractor):
|
class TutvIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?tu\.tv/videos/(?P<id>[^/?]+)'
|
_VALID_URL = r'https?://(?:www\.)?tu\.tv/videos/(?P<id>[^/?]+)'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://tu.tv/videos/noah-en-pabellon-cuahutemoc',
|
'url': 'http://tu.tv/videos/robots-futbolistas',
|
||||||
'file': '2742556.flv',
|
'md5': '627c7c124ac2a9b5ab6addb94e0e65f7',
|
||||||
'md5': '5eb766671f69b82e528dc1e7769c5cb2',
|
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'title': 'Noah en pabellon cuahutemoc',
|
'id': '2973058',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Robots futbolistas',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,10 +26,9 @@ class TutvIE(InfoExtractor):
|
|||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
internal_id = self._search_regex(r'codVideo=([0-9]+)', webpage, 'internal video ID')
|
internal_id = self._search_regex(r'codVideo=([0-9]+)', webpage, 'internal video ID')
|
||||||
|
|
||||||
data_url = 'http://tu.tv/flvurl.php?codVideo=' + str(internal_id)
|
data_content = self._download_webpage(
|
||||||
data_content = self._download_webpage(data_url, video_id, note='Downloading video info')
|
'http://tu.tv/flvurl.php?codVideo=%s' % internal_id, video_id, 'Downloading video info')
|
||||||
data = compat_parse_qs(data_content)
|
video_url = base64.b64decode(compat_parse_qs(data_content)['kpt'][0]).decode('utf-8')
|
||||||
video_url = base64.b64decode(data['kpt'][0]).decode('utf-8')
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': internal_id,
|
'id': internal_id,
|
||||||
|
@@ -7,6 +7,7 @@ from .common import InfoExtractor
|
|||||||
from ..utils import (
|
from ..utils import (
|
||||||
compat_urllib_request,
|
compat_urllib_request,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
|
ExtractorError,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -48,6 +49,7 @@ class VeohIE(InfoExtractor):
|
|||||||
'description': 'md5:f5a11c51f8fb51d2315bca0937526891',
|
'description': 'md5:f5a11c51f8fb51d2315bca0937526891',
|
||||||
'uploader': 'newsy-videos',
|
'uploader': 'newsy-videos',
|
||||||
},
|
},
|
||||||
|
'skip': 'This video has been deleted.',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -94,8 +96,12 @@ class VeohIE(InfoExtractor):
|
|||||||
if video_id.startswith('v'):
|
if video_id.startswith('v'):
|
||||||
rsp = self._download_xml(
|
rsp = self._download_xml(
|
||||||
r'http://www.veoh.com/api/findByPermalink?permalink=%s' % video_id, video_id, 'Downloading video XML')
|
r'http://www.veoh.com/api/findByPermalink?permalink=%s' % video_id, video_id, 'Downloading video XML')
|
||||||
if rsp.get('stat') == 'ok':
|
stat = rsp.get('stat')
|
||||||
|
if stat == 'ok':
|
||||||
return self._extract_video(rsp.find('./videoList/video'))
|
return self._extract_video(rsp.find('./videoList/video'))
|
||||||
|
elif stat == 'fail':
|
||||||
|
raise ExtractorError(
|
||||||
|
'%s said: %s' % (self.IE_NAME, rsp.find('./errorList/error').get('errorMessage')), expected=True)
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
age_limit = 0
|
age_limit = 0
|
||||||
|
124
youtube_dl/extractor/vh1.py
Normal file
124
youtube_dl/extractor/vh1.py
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .mtv import MTVIE
|
||||||
|
|
||||||
|
import re
|
||||||
|
from ..utils import fix_xml_ampersands
|
||||||
|
|
||||||
|
|
||||||
|
class VH1IE(MTVIE):
|
||||||
|
IE_NAME = 'vh1.com'
|
||||||
|
_FEED_URL = 'http://www.vh1.com/player/embed/AS3/fullepisode/rss/'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://www.vh1.com/video/metal-evolution/full-episodes/progressive-metal/1678612/playlist.jhtml',
|
||||||
|
'playlist': [
|
||||||
|
{
|
||||||
|
'md5': '7827a7505f59633983165bbd2c119b52',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '731565',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Metal Evolution: Ep. 11 Act 1',
|
||||||
|
'description': 'Many rock academics have proclaimed that the truly progressive musicianship of the last 20 years has been found right here in the world of heavy metal, rather than obvious locales such as jazz, fusion or progressive rock. It stands to reason then, that much of this jaw-dropping virtuosity occurs within what\'s known as progressive metal, a genre that takes root with the likes of Rush in the \'70s, Queensryche and Fates Warning in the \'80s, and Dream Theater in the \'90s. Since then, the genre has exploded with creativity, spawning mind-bending, genre-defying acts such as Tool, Mastodon, Coheed And Cambria, Porcupine Tree, Meshuggah, A Perfect Circle and Opeth. Episode 12 looks at the extreme musicianship of these bands, as well as their often extreme literary prowess and conceptual strength, the end result being a rich level of respect and attention such challenging acts have brought upon the world of heavy metal, from a critical community usually dismissive of the form.'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'md5': '34fb4b7321c546b54deda2102a61821f',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '731567',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Metal Evolution: Ep. 11 Act 2',
|
||||||
|
'description': 'Many rock academics have proclaimed that the truly progressive musicianship of the last 20 years has been found right here in the world of heavy metal, rather than obvious locales such as jazz, fusion or progressive rock. It stands to reason then, that much of this jaw-dropping virtuosity occurs within what\'s known as progressive metal, a genre that takes root with the likes of Rush in the \'70s, Queensryche and Fates Warning in the \'80s, and Dream Theater in the \'90s. Since then, the genre has exploded with creativity, spawning mind-bending, genre-defying acts such as Tool, Mastodon, Coheed And Cambria, Porcupine Tree, Meshuggah, A Perfect Circle and Opeth. Episode 11 looks at the extreme musicianship of these bands, as well as their often extreme literary prowess and conceptual strength, the end result being a rich level of respect and attention such challenging acts have brought upon the world of heavy metal, from a critical community usually dismissive of the form.'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'md5': '813f38dba4c1b8647196135ebbf7e048',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '731568',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Metal Evolution: Ep. 11 Act 3',
|
||||||
|
'description': 'Many rock academics have proclaimed that the truly progressive musicianship of the last 20 years has been found right here in the world of heavy metal, rather than obvious locales such as jazz, fusion or progressive rock. It stands to reason then, that much of this jaw-dropping virtuosity occurs within what\'s known as progressive metal, a genre that takes root with the likes of Rush in the \'70s, Queensryche and Fates Warning in the \'80s, and Dream Theater in the \'90s. Since then, the genre has exploded with creativity, spawning mind-bending, genre-defying acts such as Tool, Mastodon, Coheed And Cambria, Porcupine Tree, Meshuggah, A Perfect Circle and Opeth. Episode 11 looks at the extreme musicianship of these bands, as well as their often extreme literary prowess and conceptual strength, the end result being a rich level of respect and attention such challenging acts have brought upon the world of heavy metal, from a critical community usually dismissive of the form.'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'md5': '51adb72439dfaed11c799115d76e497f',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '731569',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Metal Evolution: Ep. 11 Act 4',
|
||||||
|
'description': 'Many rock academics have proclaimed that the truly progressive musicianship of the last 20 years has been found right here in the world of heavy metal, rather than obvious locales such as jazz, fusion or progressive rock. It stands to reason then, that much of this jaw-dropping virtuosity occurs within what\'s known as progressive metal, a genre that takes root with the likes of Rush in the \'70s, Queensryche and Fates Warning in the \'80s, and Dream Theater in the \'90s. Since then, the genre has exploded with creativity, spawning mind-bending, genre-defying acts such as Tool, Mastodon, Coheed And Cambria, Porcupine Tree, Meshuggah, A Perfect Circle and Opeth. Episode 11 looks at the extreme musicianship of these bands, as well as their often extreme literary prowess and conceptual strength, the end result being a rich level of respect and attention such challenging acts have brought upon the world of heavy metal, from a critical community usually dismissive of the form.'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'md5': '93d554aaf79320703b73a95288c76a6e',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '731570',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Metal Evolution: Ep. 11 Act 5',
|
||||||
|
'description': 'Many rock academics have proclaimed that the truly progressive musicianship of the last 20 years has been found right here in the world of heavy metal, rather than obvious locales such as jazz, fusion or progressive rock. It stands to reason then, that much of this jaw-dropping virtuosity occurs within what\'s known as progressive metal, a genre that takes root with the likes of Rush in the \'70s, Queensryche and Fates Warning in the \'80s, and Dream Theater in the \'90s. Since then, the genre has exploded with creativity, spawning mind-bending, genre-defying acts such as Tool, Mastodon, Coheed And Cambria, Porcupine Tree, Meshuggah, A Perfect Circle and Opeth. Episode 11 looks at the extreme musicianship of these bands, as well as their often extreme literary prowess and conceptual strength, the end result being a rich level of respect and attention such challenging acts have brought upon the world of heavy metal, from a critical community usually dismissive of the form.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'skip': 'Blocked outside the US',
|
||||||
|
}, {
|
||||||
|
# Clip
|
||||||
|
'url': 'http://www.vh1.com/video/misc/706675/metal-evolution-episode-1-pre-metal-show-clip.jhtml#id=1674118',
|
||||||
|
'md5': '7d67cf6d9cdc6b4f3d3ac97a55403844',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '706675',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Metal Evolution: Episode 1 Pre-Metal Show Clip',
|
||||||
|
'description': 'The greatest documentary ever made about Heavy Metal begins as our host Sam Dunn travels the globe to seek out the origins and influences that helped create Heavy Metal. Sam speaks to legends like Kirk Hammett, Alice Cooper, Slash, Bill Ward, Geezer Butler, Tom Morello, Ace Frehley, Lemmy Kilmister, Dave Davies, and many many more. This episode is the prologue for the 11 hour series, and Sam goes back to the very beginning to reveal how Heavy Metal was created.'
|
||||||
|
},
|
||||||
|
'skip': 'Blocked outside the US',
|
||||||
|
}, {
|
||||||
|
# Short link
|
||||||
|
'url': 'http://www.vh1.com/video/play.jhtml?id=1678353',
|
||||||
|
'md5': '853192b87ad978732b67dd8e549b266a',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '730355',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Metal Evolution: Episode 11 Progressive Metal Sneak',
|
||||||
|
'description': 'In Metal Evolution\'s finale sneak, Sam sits with Michael Giles of King Crimson and gets feedback from Metallica guitarist Kirk Hammett on why the group was influential.'
|
||||||
|
},
|
||||||
|
'skip': 'Blocked outside the US',
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.vh1.com/video/macklemore-ryan-lewis/900535/cant-hold-us-ft-ray-dalton.jhtml',
|
||||||
|
'md5': 'b1bcb5b4380c9d7f544065589432dee7',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '900535',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Macklemore & Ryan Lewis - "Can\'t Hold Us ft. Ray Dalton"',
|
||||||
|
'description': 'The Heist'
|
||||||
|
},
|
||||||
|
'skip': 'Blocked outside the US',
|
||||||
|
}]
|
||||||
|
|
||||||
|
_VALID_URL = r'''(?x)
|
||||||
|
https?://www\.vh1\.com/video/
|
||||||
|
(?:
|
||||||
|
.+?/full-episodes/.+?/(?P<playlist_id>[^/]+)/playlist\.jhtml
|
||||||
|
|
|
||||||
|
(?:
|
||||||
|
play.jhtml\?id=|
|
||||||
|
misc/.+?/.+?\.jhtml\#id=
|
||||||
|
)
|
||||||
|
(?P<video_id>[0-9]+)$
|
||||||
|
|
|
||||||
|
[^/]+/(?P<music_id>[0-9]+)/[^/]+?
|
||||||
|
)
|
||||||
|
'''
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
if mobj.group('music_id'):
|
||||||
|
id_field = 'vid'
|
||||||
|
video_id = mobj.group('music_id')
|
||||||
|
else:
|
||||||
|
video_id = mobj.group('playlist_id') or mobj.group('video_id')
|
||||||
|
id_field = 'id'
|
||||||
|
doc_url = '%s?%s=%s' % (self._FEED_URL, id_field, video_id)
|
||||||
|
|
||||||
|
idoc = self._download_xml(
|
||||||
|
doc_url, video_id,
|
||||||
|
'Downloading info', transform_source=fix_xml_ampersands)
|
||||||
|
return [self._get_video_info(item) for item in idoc.findall('.//item')]
|
@@ -4,7 +4,10 @@ import re
|
|||||||
import base64
|
import base64
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import unified_strdate
|
from ..utils import (
|
||||||
|
unified_strdate,
|
||||||
|
int_or_none,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class VideoTtIE(InfoExtractor):
|
class VideoTtIE(InfoExtractor):
|
||||||
@@ -50,9 +53,9 @@ class VideoTtIE(InfoExtractor):
|
|||||||
'thumbnail': settings['config']['thumbnail'],
|
'thumbnail': settings['config']['thumbnail'],
|
||||||
'upload_date': unified_strdate(video['added']),
|
'upload_date': unified_strdate(video['added']),
|
||||||
'uploader': video['owner'],
|
'uploader': video['owner'],
|
||||||
'view_count': int(video['view_count']),
|
'view_count': int_or_none(video['view_count']),
|
||||||
'comment_count': int(video['comment_count']),
|
'comment_count': None if video.get('comment_count') == '--' else int_or_none(video['comment_count']),
|
||||||
'like_count': int(video['liked']),
|
'like_count': int_or_none(video['liked']),
|
||||||
'dislike_count': int(video['disliked']),
|
'dislike_count': int_or_none(video['disliked']),
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
}
|
}
|
86
youtube_dl/extractor/vimple.py
Normal file
86
youtube_dl/extractor/vimple.py
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import re
|
||||||
|
import xml.etree.ElementTree
|
||||||
|
import zlib
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import int_or_none
|
||||||
|
|
||||||
|
|
||||||
|
class VimpleIE(InfoExtractor):
|
||||||
|
IE_DESC = 'Vimple.ru'
|
||||||
|
_VALID_URL = r'https?://(player.vimple.ru/iframe|vimple.ru)/(?P<id>[a-f0-9]{10,})'
|
||||||
|
_TESTS = [
|
||||||
|
# Quality: Large, from iframe
|
||||||
|
{
|
||||||
|
'url': 'http://player.vimple.ru/iframe/b132bdfd71b546d3972f9ab9a25f201c',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'b132bdfd71b546d3972f9ab9a25f201c',
|
||||||
|
'title': 'great-escape-minecraft.flv',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'duration': 352,
|
||||||
|
'webpage_url': 'http://vimple.ru/b132bdfd71b546d3972f9ab9a25f201c',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
# Quality: Medium, from mainpage
|
||||||
|
{
|
||||||
|
'url': 'http://vimple.ru/a15950562888453b8e6f9572dc8600cd',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'a15950562888453b8e6f9572dc8600cd',
|
||||||
|
'title': 'DB 01',
|
||||||
|
'ext': 'flv',
|
||||||
|
'duration': 1484,
|
||||||
|
'webpage_url': 'http://vimple.ru/a15950562888453b8e6f9572dc8600cd',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
iframe_url = 'http://player.vimple.ru/iframe/%s' % video_id
|
||||||
|
|
||||||
|
iframe = self._download_webpage(
|
||||||
|
iframe_url, video_id,
|
||||||
|
note='Downloading iframe', errnote='unable to fetch iframe')
|
||||||
|
player_url = self._html_search_regex(
|
||||||
|
r'"(http://player.vimple.ru/flash/.+?)"', iframe, 'player url')
|
||||||
|
|
||||||
|
player = self._request_webpage(
|
||||||
|
player_url, video_id, note='Downloading swf player').read()
|
||||||
|
|
||||||
|
player = zlib.decompress(player[8:])
|
||||||
|
|
||||||
|
xml_pieces = re.findall(b'([a-zA-Z0-9 =+/]{500})', player)
|
||||||
|
xml_pieces = [piece[1:-1] for piece in xml_pieces]
|
||||||
|
|
||||||
|
xml_data = b''.join(xml_pieces)
|
||||||
|
xml_data = base64.b64decode(xml_data)
|
||||||
|
|
||||||
|
xml_data = xml.etree.ElementTree.fromstring(xml_data)
|
||||||
|
|
||||||
|
video = xml_data.find('Video')
|
||||||
|
quality = video.get('quality')
|
||||||
|
q_tag = video.find(quality.capitalize())
|
||||||
|
|
||||||
|
formats = [
|
||||||
|
{
|
||||||
|
'url': q_tag.get('url'),
|
||||||
|
'tbr': int(q_tag.get('bitrate')),
|
||||||
|
'filesize': int(q_tag.get('filesize')),
|
||||||
|
'format_id': quality,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': video.find('Title').text,
|
||||||
|
'formats': formats,
|
||||||
|
'thumbnail': video.find('Poster').get('url'),
|
||||||
|
'duration': int_or_none(video.get('duration')),
|
||||||
|
'webpage_url': video.find('Share').get('videoPageUrl'),
|
||||||
|
}
|
@@ -16,7 +16,7 @@ from ..utils import (
|
|||||||
|
|
||||||
class VKIE(InfoExtractor):
|
class VKIE(InfoExtractor):
|
||||||
IE_NAME = 'vk.com'
|
IE_NAME = 'vk.com'
|
||||||
_VALID_URL = r'https?://vk\.com/(?:video_ext\.php\?.*?\boid=(?P<oid>-?\d+).*?\bid=(?P<id>\d+)|(?:videos.*?\?.*?z=)?video(?P<videoid>.*?)(?:\?|%2F|$))'
|
_VALID_URL = r'https?://(?:m\.)?vk\.com/(?:video_ext\.php\?.*?\boid=(?P<oid>-?\d+).*?\bid=(?P<id>\d+)|(?:.+?\?.*?z=)?video(?P<videoid>.*?)(?:\?|%2F|$))'
|
||||||
_NETRC_MACHINE = 'vk'
|
_NETRC_MACHINE = 'vk'
|
||||||
|
|
||||||
_TESTS = [
|
_TESTS = [
|
||||||
@@ -27,7 +27,7 @@ class VKIE(InfoExtractor):
|
|||||||
'id': '162222515',
|
'id': '162222515',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'title': 'ProtivoGunz - Хуёвая песня',
|
'title': 'ProtivoGunz - Хуёвая песня',
|
||||||
'uploader': 'Noize MC',
|
'uploader': 're:Noize MC.*',
|
||||||
'duration': 195,
|
'duration': 195,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -62,11 +62,47 @@ class VKIE(InfoExtractor):
|
|||||||
'id': '164049491',
|
'id': '164049491',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'uploader': 'Триллеры',
|
'uploader': 'Триллеры',
|
||||||
'title': '► Бойцовский клуб / Fight Club 1999 [HD 720]\u00a0',
|
'title': '► Бойцовский клуб / Fight Club 1999 [HD 720]',
|
||||||
'duration': 8352,
|
'duration': 8352,
|
||||||
},
|
},
|
||||||
'skip': 'Requires vk account credentials',
|
'skip': 'Requires vk account credentials',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://vk.com/feed?z=video-43215063_166094326%2Fbb50cacd3177146d7a',
|
||||||
|
'md5': 'd82c22e449f036282d1d3f7f4d276869',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '166094326',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'uploader': 'Киномания - лучшее из мира кино',
|
||||||
|
'title': 'Запах женщины (1992)',
|
||||||
|
'duration': 9392,
|
||||||
|
},
|
||||||
|
'skip': 'Requires vk account credentials',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://vk.com/hd_kino_mania?z=video-43215063_168067957%2F15c66b9b533119788d',
|
||||||
|
'md5': '4d7a5ef8cf114dfa09577e57b2993202',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '168067957',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'uploader': 'Киномания - лучшее из мира кино',
|
||||||
|
'title': ' ',
|
||||||
|
'duration': 7291,
|
||||||
|
},
|
||||||
|
'skip': 'Requires vk account credentials',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://m.vk.com/video-43215063_169084319?list=125c627d1aa1cebb83&from=wall-43215063_2566540',
|
||||||
|
'md5': '0c45586baa71b7cb1d0784ee3f4e00a6',
|
||||||
|
'note': 'ivi.ru embed',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '60690',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Книга Илая',
|
||||||
|
'duration': 6771,
|
||||||
|
},
|
||||||
|
'skip': 'Only works from Russia',
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
def _login(self):
|
def _login(self):
|
||||||
@@ -110,6 +146,16 @@ class VKIE(InfoExtractor):
|
|||||||
if m_yt is not None:
|
if m_yt is not None:
|
||||||
self.to_screen('Youtube video detected')
|
self.to_screen('Youtube video detected')
|
||||||
return self.url_result(m_yt.group(1), 'Youtube')
|
return self.url_result(m_yt.group(1), 'Youtube')
|
||||||
|
|
||||||
|
m_opts = re.search(r'(?s)var\s+opts\s*=\s*({.*?});', info_page)
|
||||||
|
if m_opts:
|
||||||
|
m_opts_url = re.search(r"url\s*:\s*'([^']+)", m_opts.group(1))
|
||||||
|
if m_opts_url:
|
||||||
|
opts_url = m_opts_url.group(1)
|
||||||
|
if opts_url.startswith('//'):
|
||||||
|
opts_url = 'http:' + opts_url
|
||||||
|
return self.url_result(opts_url)
|
||||||
|
|
||||||
data_json = self._search_regex(r'var vars = ({.*?});', info_page, 'vars')
|
data_json = self._search_regex(r'var vars = ({.*?});', info_page, 'vars')
|
||||||
data = json.loads(data_json)
|
data = json.loads(data_json)
|
||||||
|
|
||||||
|
63
youtube_dl/extractor/vodlocker.py
Normal file
63
youtube_dl/extractor/vodlocker.py
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
compat_urllib_parse,
|
||||||
|
compat_urllib_request,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class VodlockerIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?vodlocker.com/(?P<id>[0-9a-zA-Z]+)(?:\..*?)?'
|
||||||
|
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://vodlocker.com/e8wvyzz4sl42',
|
||||||
|
'md5': 'ce0c2d18fa0735f1bd91b69b0e54aacf',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'e8wvyzz4sl42',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Germany vs Brazil',
|
||||||
|
'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)
|
||||||
|
|
||||||
|
fields = dict(re.findall(r'''(?x)<input\s+
|
||||||
|
type="hidden"\s+
|
||||||
|
name="([^"]+)"\s+
|
||||||
|
(?:id="[^"]+"\s+)?
|
||||||
|
value="([^"]*)"
|
||||||
|
''', webpage))
|
||||||
|
|
||||||
|
if fields['op'] == 'download1':
|
||||||
|
self._sleep(3, video_id) # they do detect when requests happen too fast!
|
||||||
|
post = compat_urllib_parse.urlencode(fields)
|
||||||
|
req = compat_urllib_request.Request(url, post)
|
||||||
|
req.add_header('Content-type', 'application/x-www-form-urlencoded')
|
||||||
|
webpage = self._download_webpage(
|
||||||
|
req, video_id, 'Downloading video page')
|
||||||
|
|
||||||
|
title = self._search_regex(
|
||||||
|
r'id="file_title".*?>\s*(.*?)\s*<span', webpage, 'title')
|
||||||
|
thumbnail = self._search_regex(
|
||||||
|
r'image:\s*"(http[^\"]+)",', webpage, 'thumbnail')
|
||||||
|
url = self._search_regex(
|
||||||
|
r'file:\s*"(http[^\"]+)",', webpage, 'file url')
|
||||||
|
|
||||||
|
formats = [{
|
||||||
|
'format_id': 'sd',
|
||||||
|
'url': url,
|
||||||
|
}]
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'formats': formats,
|
||||||
|
}
|
69
youtube_dl/extractor/vulture.py
Normal file
69
youtube_dl/extractor/vulture.py
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os.path
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
int_or_none,
|
||||||
|
parse_iso8601,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class VultureIE(InfoExtractor):
|
||||||
|
IE_NAME = 'vulture.com'
|
||||||
|
_VALID_URL = r'https?://video\.vulture\.com/video/(?P<display_id>[^/]+)/'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://video.vulture.com/video/Mindy-Kaling-s-Harvard-Speech/player?layout=compact&read_more=1',
|
||||||
|
'md5': '8d997845642a2b5152820f7257871bc8',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '6GHRQL3RV7MSD1H4',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'kaling-speech-2-MAGNIFY STANDARD CONTAINER REVISED',
|
||||||
|
'uploader_id': 'Sarah',
|
||||||
|
'thumbnail': 're:^http://.*\.jpg$',
|
||||||
|
'timestamp': 1401288564,
|
||||||
|
'upload_date': '20140528',
|
||||||
|
'description': 'Uplifting and witty, as predicted.',
|
||||||
|
'duration': 1015,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
display_id = mobj.group('display_id')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
query_string = self._search_regex(
|
||||||
|
r"queryString\s*=\s*'([^']+)'", webpage, 'query string')
|
||||||
|
video_id = self._search_regex(
|
||||||
|
r'content=([^&]+)', query_string, 'video ID')
|
||||||
|
query_url = 'http://video.vulture.com/embed/player/container/1000/1000/?%s' % query_string
|
||||||
|
|
||||||
|
query_webpage = self._download_webpage(
|
||||||
|
query_url, display_id, note='Downloading query page')
|
||||||
|
params_json = self._search_regex(
|
||||||
|
r'(?sm)new MagnifyEmbeddablePlayer\({.*?contentItem:\s*(\{.*?\})\n,\n',
|
||||||
|
query_webpage,
|
||||||
|
'player params')
|
||||||
|
params = json.loads(params_json)
|
||||||
|
|
||||||
|
upload_timestamp = parse_iso8601(params['posted'].replace(' ', 'T'))
|
||||||
|
uploader_id = params.get('user', {}).get('handle')
|
||||||
|
|
||||||
|
media_item = params['media_item']
|
||||||
|
title = os.path.splitext(media_item['title'])[0]
|
||||||
|
duration = int_or_none(media_item.get('duration_seconds'))
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'display_id': display_id,
|
||||||
|
'url': media_item['pipeline_xid'],
|
||||||
|
'title': title,
|
||||||
|
'timestamp': upload_timestamp,
|
||||||
|
'thumbnail': params.get('thumbnail_url'),
|
||||||
|
'uploader_id': uploader_id,
|
||||||
|
'description': params.get('description'),
|
||||||
|
'duration': duration,
|
||||||
|
}
|
@@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
@@ -54,14 +55,14 @@ class WDRIE(InfoExtractor):
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'url': 'http://www.funkhauseuropa.de/av/audiogrenzenlosleckerbaklava101-audioplayer.html',
|
'url': 'http://www.funkhauseuropa.de/av/audiosuepersongsoulbossanova100-audioplayer.html',
|
||||||
'md5': 'cfff440d4ee64114083ac44676df5d15',
|
'md5': '24e83813e832badb0a8d7d1ef9ef0691',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'mdb-363068',
|
'id': 'mdb-463528',
|
||||||
'ext': 'mp3',
|
'ext': 'mp3',
|
||||||
'title': 'Grenzenlos lecker - Baklava',
|
'title': 'Süpersong: Soul Bossa Nova',
|
||||||
'description': 'md5:7b29e97e10dfb6e265238b32fa35b23a',
|
'description': 'md5:7b29e97e10dfb6e265238b32fa35b23a',
|
||||||
'upload_date': '20140311',
|
'upload_date': '20140630',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -127,9 +128,10 @@ class WDRMobileIE(InfoExtractor):
|
|||||||
'info_dict': {
|
'info_dict': {
|
||||||
'title': '4283021',
|
'title': '4283021',
|
||||||
'id': '421735',
|
'id': '421735',
|
||||||
|
'ext': 'mp4',
|
||||||
'age_limit': 0,
|
'age_limit': 0,
|
||||||
},
|
},
|
||||||
'_skip': 'Will be depublicized shortly'
|
'skip': 'Problems with loading data.'
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
@@ -139,6 +141,7 @@ class WDRMobileIE(InfoExtractor):
|
|||||||
'title': mobj.group('title'),
|
'title': mobj.group('title'),
|
||||||
'age_limit': int(mobj.group('age_limit')),
|
'age_limit': int(mobj.group('age_limit')),
|
||||||
'url': url,
|
'url': url,
|
||||||
|
'ext': determine_ext(url),
|
||||||
'user_agent': 'mobile',
|
'user_agent': 'mobile',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
|
||||||
@@ -5,14 +7,16 @@ from .common import InfoExtractor
|
|||||||
|
|
||||||
|
|
||||||
class WistiaIE(InfoExtractor):
|
class WistiaIE(InfoExtractor):
|
||||||
_VALID_URL = r'^https?://(?:fast\.)?wistia\.net/embed/iframe/(?P<id>[a-z0-9]+)'
|
_VALID_URL = r'https?://(?:fast\.)?wistia\.net/embed/iframe/(?P<id>[a-z0-9]+)'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
u"url": u"http://fast.wistia.net/embed/iframe/sh7fpupwlt",
|
'url': 'http://fast.wistia.net/embed/iframe/sh7fpupwlt',
|
||||||
u"file": u"sh7fpupwlt.mov",
|
'md5': 'cafeb56ec0c53c18c97405eecb3133df',
|
||||||
u"md5": u"cafeb56ec0c53c18c97405eecb3133df",
|
'info_dict': {
|
||||||
u"info_dict": {
|
'id': 'sh7fpupwlt',
|
||||||
u"title": u"cfh_resourceful_zdkh_final_1"
|
'ext': 'mov',
|
||||||
|
'title': 'Being Resourceful',
|
||||||
|
'duration': 117,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,7 +26,7 @@ class WistiaIE(InfoExtractor):
|
|||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
data_json = self._html_search_regex(
|
data_json = self._html_search_regex(
|
||||||
r'Wistia.iframeInit\((.*?), {}\);', webpage, u'video data')
|
r'Wistia\.iframeInit\((.*?), {}\);', webpage, 'video data')
|
||||||
|
|
||||||
data = json.loads(data_json)
|
data = json.loads(data_json)
|
||||||
|
|
||||||
@@ -54,4 +58,5 @@ class WistiaIE(InfoExtractor):
|
|||||||
'title': data['name'],
|
'title': data['name'],
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
'thumbnails': thumbnails,
|
'thumbnails': thumbnails,
|
||||||
|
'duration': data.get('duration'),
|
||||||
}
|
}
|
||||||
|
81
youtube_dl/extractor/wrzuta.py
Normal file
81
youtube_dl/extractor/wrzuta.py
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
int_or_none,
|
||||||
|
qualities,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class WrzutaIE(InfoExtractor):
|
||||||
|
IE_NAME = 'wrzuta.pl'
|
||||||
|
|
||||||
|
_VALID_URL = r'https?://(?P<uploader>[0-9a-zA-Z]+)\.wrzuta\.pl/(?P<typ>film|audio)/(?P<id>[0-9a-zA-Z]+)'
|
||||||
|
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://laboratoriumdextera.wrzuta.pl/film/aq4hIZWrkBu/nike_football_the_last_game',
|
||||||
|
'md5': '9e67e05bed7c03b82488d87233a9efe7',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'aq4hIZWrkBu',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Nike Football: The Last Game',
|
||||||
|
'duration': 307,
|
||||||
|
'uploader_id': 'laboratoriumdextera',
|
||||||
|
'description': 'md5:7fb5ef3c21c5893375fda51d9b15d9cd',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://w729.wrzuta.pl/audio/9oXJqdcndqv/david_guetta_amp_showtek_ft._vassy_-_bad',
|
||||||
|
'md5': '1e546a18e1c22ac6e9adce17b8961ff5',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '9oXJqdcndqv',
|
||||||
|
'ext': 'ogg',
|
||||||
|
'title': 'David Guetta & Showtek ft. Vassy - Bad',
|
||||||
|
'duration': 270,
|
||||||
|
'uploader_id': 'w729',
|
||||||
|
'description': 'md5:4628f01c666bbaaecefa83476cfa794a',
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
typ = mobj.group('typ')
|
||||||
|
uploader = mobj.group('uploader')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
quality = qualities(['SD', 'MQ', 'HQ', 'HD'])
|
||||||
|
|
||||||
|
audio_table = {'flv': 'mp3', 'webm': 'ogg'}
|
||||||
|
|
||||||
|
embedpage = self._download_json('http://www.wrzuta.pl/npp/embed/%s/%s' % (uploader, video_id), video_id)
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
for media in embedpage['url']:
|
||||||
|
if typ == 'audio':
|
||||||
|
ext = audio_table[media['type'].split('@')[0]]
|
||||||
|
else:
|
||||||
|
ext = media['type'].split('@')[0]
|
||||||
|
|
||||||
|
formats.append({
|
||||||
|
'format_id': '%s_%s' % (ext, media['quality'].lower()),
|
||||||
|
'url': media['url'],
|
||||||
|
'ext': ext,
|
||||||
|
'quality': quality(media['quality']),
|
||||||
|
})
|
||||||
|
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': self._og_search_title(webpage),
|
||||||
|
'thumbnail': self._og_search_thumbnail(webpage),
|
||||||
|
'formats': formats,
|
||||||
|
'duration': int_or_none(embedpage['duration']),
|
||||||
|
'uploader_id': uploader,
|
||||||
|
'description': self._og_search_description(webpage),
|
||||||
|
'age_limit': embedpage.get('minimalAge', 0),
|
||||||
|
}
|
@@ -5,18 +5,21 @@ import re
|
|||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
|
ExtractorError,
|
||||||
|
clean_html,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class XVideosIE(InfoExtractor):
|
class XVideosIE(InfoExtractor):
|
||||||
_VALID_URL = r'^(?:https?://)?(?:www\.)?xvideos\.com/video([0-9]+)(?:.*)'
|
_VALID_URL = r'^(?:https?://)?(?:www\.)?xvideos\.com/video([0-9]+)(?:.*)'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.xvideos.com/video939581/funny_porns_by_s_-1',
|
'url': 'http://www.xvideos.com/video4588838/biker_takes_his_girl',
|
||||||
'file': '939581.flv',
|
'md5': '4b46ae6ea5e6e9086e714d883313c0c9',
|
||||||
'md5': '1d0c835822f0a71a7bf011855db929d0',
|
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
"title": "Funny Porns By >>>>S<<<<<< -1",
|
'id': '4588838',
|
||||||
"age_limit": 18,
|
'ext': 'flv',
|
||||||
|
'title': 'Biker Takes his Girl',
|
||||||
|
'age_limit': 18,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,6 +31,10 @@ class XVideosIE(InfoExtractor):
|
|||||||
|
|
||||||
self.report_extraction(video_id)
|
self.report_extraction(video_id)
|
||||||
|
|
||||||
|
mobj = re.search(r'<h1 class="inlineError">(.+?)</h1>', webpage)
|
||||||
|
if mobj:
|
||||||
|
raise ExtractorError('%s said: %s' % (self.IE_NAME, clean_html(mobj.group(1))), expected=True)
|
||||||
|
|
||||||
# Extract video URL
|
# Extract video URL
|
||||||
video_url = compat_urllib_parse.unquote(
|
video_url = compat_urllib_parse.unquote(
|
||||||
self._search_regex(r'flv_url=(.+?)&', webpage, 'video URL'))
|
self._search_regex(r'flv_url=(.+?)&', webpage, 'video URL'))
|
||||||
|
@@ -21,7 +21,7 @@ class YahooIE(InfoExtractor):
|
|||||||
'url': 'http://screen.yahoo.com/julian-smith-travis-legg-watch-214727115.html',
|
'url': 'http://screen.yahoo.com/julian-smith-travis-legg-watch-214727115.html',
|
||||||
'md5': '4962b075c08be8690a922ee026d05e69',
|
'md5': '4962b075c08be8690a922ee026d05e69',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '214727115',
|
'id': '2d25e626-2378-391f-ada0-ddaf1417e588',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Julian Smith & Travis Legg Watch Julian Smith',
|
'title': 'Julian Smith & Travis Legg Watch Julian Smith',
|
||||||
'description': 'Julian and Travis watch Julian Smith',
|
'description': 'Julian and Travis watch Julian Smith',
|
||||||
@@ -31,7 +31,7 @@ class YahooIE(InfoExtractor):
|
|||||||
'url': 'http://screen.yahoo.com/wired/codefellas-s1-ep12-cougar-lies-103000935.html',
|
'url': 'http://screen.yahoo.com/wired/codefellas-s1-ep12-cougar-lies-103000935.html',
|
||||||
'md5': 'd6e6fc6e1313c608f316ddad7b82b306',
|
'md5': 'd6e6fc6e1313c608f316ddad7b82b306',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '103000935',
|
'id': 'd1dedf8c-d58c-38c3-8963-e899929ae0a9',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Codefellas - The Cougar Lies with Spanish Moss',
|
'title': 'Codefellas - The Cougar Lies with Spanish Moss',
|
||||||
'description': 'Agent Topple\'s mustache does its dirty work, and Nicole brokers a deal for peace. But why is the NSA collecting millions of Instagram brunch photos? And if your waffles have nothing to hide, what are they so worried about?',
|
'description': 'Agent Topple\'s mustache does its dirty work, and Nicole brokers a deal for peace. But why is the NSA collecting millions of Instagram brunch photos? And if your waffles have nothing to hide, what are they so worried about?',
|
||||||
@@ -58,9 +58,11 @@ class YahooIE(InfoExtractor):
|
|||||||
r'mediaItems: ({.*?})$', webpage, 'items', flags=re.MULTILINE,
|
r'mediaItems: ({.*?})$', webpage, 'items', flags=re.MULTILINE,
|
||||||
default=None)
|
default=None)
|
||||||
if items_json is None:
|
if items_json is None:
|
||||||
long_id = self._search_regex(
|
CONTENT_ID_REGEXES = [
|
||||||
r'YUI\.namespace\("Media"\)\.CONTENT_ID\s*=\s*"([^"]+)"',
|
r'YUI\.namespace\("Media"\)\.CONTENT_ID\s*=\s*"([^"]+)"',
|
||||||
webpage, 'content ID')
|
r'root\.App\.Cache\.context\.videoCache\.curVideo = \{"([^"]+)"'
|
||||||
|
]
|
||||||
|
long_id = self._search_regex(CONTENT_ID_REGEXES, webpage, 'content ID')
|
||||||
video_id = long_id
|
video_id = long_id
|
||||||
else:
|
else:
|
||||||
items = json.loads(items_json)
|
items = json.loads(items_json)
|
||||||
@@ -68,9 +70,9 @@ class YahooIE(InfoExtractor):
|
|||||||
# The 'meta' field is not always in the video webpage, we request it
|
# The 'meta' field is not always in the video webpage, we request it
|
||||||
# from another page
|
# from another page
|
||||||
long_id = info['id']
|
long_id = info['id']
|
||||||
return self._get_info(long_id, video_id)
|
return self._get_info(long_id, video_id, webpage)
|
||||||
|
|
||||||
def _get_info(self, long_id, video_id):
|
def _get_info(self, long_id, video_id, webpage):
|
||||||
query = ('SELECT * FROM yahoo.media.video.streams WHERE id="%s"'
|
query = ('SELECT * FROM yahoo.media.video.streams WHERE id="%s"'
|
||||||
' AND plrs="86Gj0vCaSzV_Iuf6hNylf2" AND region="US"'
|
' AND plrs="86Gj0vCaSzV_Iuf6hNylf2" AND region="US"'
|
||||||
' AND protocol="http"' % long_id)
|
' AND protocol="http"' % long_id)
|
||||||
@@ -113,7 +115,7 @@ class YahooIE(InfoExtractor):
|
|||||||
'title': meta['title'],
|
'title': meta['title'],
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
'description': clean_html(meta['description']),
|
'description': clean_html(meta['description']),
|
||||||
'thumbnail': meta['thumbnail'],
|
'thumbnail': meta['thumbnail'] if meta.get('thumbnail') else self._og_search_thumbnail(webpage),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -137,7 +139,7 @@ class YahooNewsIE(YahooIE):
|
|||||||
video_id = mobj.group('id')
|
video_id = mobj.group('id')
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
long_id = self._search_regex(r'contentId: \'(.+?)\',', webpage, 'long id')
|
long_id = self._search_regex(r'contentId: \'(.+?)\',', webpage, 'long id')
|
||||||
return self._get_info(long_id, video_id)
|
return self._get_info(long_id, video_id, webpage)
|
||||||
|
|
||||||
|
|
||||||
class YahooSearchIE(SearchInfoExtractor):
|
class YahooSearchIE(SearchInfoExtractor):
|
||||||
|
@@ -223,6 +223,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):
|
|||||||
'246': {'ext': 'webm', 'height': 480, 'format_note': 'DASH video', 'acodec': 'none', 'preference': -40},
|
'246': {'ext': 'webm', 'height': 480, 'format_note': 'DASH video', 'acodec': 'none', 'preference': -40},
|
||||||
'247': {'ext': 'webm', 'height': 720, 'format_note': 'DASH video', 'acodec': 'none', 'preference': -40},
|
'247': {'ext': 'webm', 'height': 720, 'format_note': 'DASH video', 'acodec': 'none', 'preference': -40},
|
||||||
'248': {'ext': 'webm', 'height': 1080, 'format_note': 'DASH video', 'acodec': 'none', 'preference': -40},
|
'248': {'ext': 'webm', 'height': 1080, 'format_note': 'DASH video', 'acodec': 'none', 'preference': -40},
|
||||||
|
'271': {'ext': 'webm', 'height': 1440, 'format_note': 'DASH video', 'acodec': 'none', 'preference': -40},
|
||||||
|
'272': {'ext': 'webm', 'height': 2160, 'format_note': 'DASH video', 'acodec': 'none', 'preference': -40},
|
||||||
|
|
||||||
# Dash webm audio
|
# Dash webm audio
|
||||||
'171': {'ext': 'webm', 'vcodec': 'none', 'format_note': 'DASH audio', 'abr': 48, 'preference': -50},
|
'171': {'ext': 'webm', 'vcodec': 'none', 'format_note': 'DASH audio', 'abr': 48, 'preference': -50},
|
||||||
@@ -439,7 +441,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):
|
|||||||
|
|
||||||
def _parse_sig_js(self, jscode):
|
def _parse_sig_js(self, jscode):
|
||||||
funcname = self._search_regex(
|
funcname = self._search_regex(
|
||||||
r'signature=([a-zA-Z]+)', jscode,
|
r'signature=([$a-zA-Z]+)', jscode,
|
||||||
u'Initial JS player signature function name')
|
u'Initial JS player signature function name')
|
||||||
|
|
||||||
jsi = JSInterpreter(jscode)
|
jsi = JSInterpreter(jscode)
|
||||||
@@ -863,71 +865,26 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):
|
|||||||
def _decrypt_signature(self, s, video_id, player_url, age_gate=False):
|
def _decrypt_signature(self, s, video_id, player_url, age_gate=False):
|
||||||
"""Turn the encrypted s field into a working signature"""
|
"""Turn the encrypted s field into a working signature"""
|
||||||
|
|
||||||
if player_url is not None:
|
if player_url is None:
|
||||||
if player_url.startswith(u'//'):
|
raise ExtractorError(u'Cannot decrypt signature without player_url')
|
||||||
player_url = u'https:' + player_url
|
|
||||||
try:
|
|
||||||
player_id = (player_url, len(s))
|
|
||||||
if player_id not in self._player_cache:
|
|
||||||
func = self._extract_signature_function(
|
|
||||||
video_id, player_url, len(s)
|
|
||||||
)
|
|
||||||
self._player_cache[player_id] = func
|
|
||||||
func = self._player_cache[player_id]
|
|
||||||
if self._downloader.params.get('youtube_print_sig_code'):
|
|
||||||
self._print_sig_code(func, len(s))
|
|
||||||
return func(s)
|
|
||||||
except Exception:
|
|
||||||
tb = traceback.format_exc()
|
|
||||||
self._downloader.report_warning(
|
|
||||||
u'Automatic signature extraction failed: ' + tb)
|
|
||||||
|
|
||||||
self._downloader.report_warning(
|
if player_url.startswith(u'//'):
|
||||||
u'Warning: Falling back to static signature algorithm')
|
player_url = u'https:' + player_url
|
||||||
|
try:
|
||||||
return self._static_decrypt_signature(
|
player_id = (player_url, len(s))
|
||||||
s, video_id, player_url, age_gate)
|
if player_id not in self._player_cache:
|
||||||
|
func = self._extract_signature_function(
|
||||||
def _static_decrypt_signature(self, s, video_id, player_url, age_gate):
|
video_id, player_url, len(s)
|
||||||
if age_gate:
|
)
|
||||||
# The videos with age protection use another player, so the
|
self._player_cache[player_id] = func
|
||||||
# algorithms can be different.
|
func = self._player_cache[player_id]
|
||||||
if len(s) == 86:
|
if self._downloader.params.get('youtube_print_sig_code'):
|
||||||
return s[2:63] + s[82] + s[64:82] + s[63]
|
self._print_sig_code(func, len(s))
|
||||||
|
return func(s)
|
||||||
if len(s) == 93:
|
except Exception as e:
|
||||||
return s[86:29:-1] + s[88] + s[28:5:-1]
|
tb = traceback.format_exc()
|
||||||
elif len(s) == 92:
|
raise ExtractorError(
|
||||||
return s[25] + s[3:25] + s[0] + s[26:42] + s[79] + s[43:79] + s[91] + s[80:83]
|
u'Automatic signature extraction failed: ' + tb, cause=e)
|
||||||
elif len(s) == 91:
|
|
||||||
return s[84:27:-1] + s[86] + s[26:5:-1]
|
|
||||||
elif len(s) == 90:
|
|
||||||
return s[25] + s[3:25] + s[2] + s[26:40] + s[77] + s[41:77] + s[89] + s[78:81]
|
|
||||||
elif len(s) == 89:
|
|
||||||
return s[84:78:-1] + s[87] + s[77:60:-1] + s[0] + s[59:3:-1]
|
|
||||||
elif len(s) == 88:
|
|
||||||
return s[7:28] + s[87] + s[29:45] + s[55] + s[46:55] + s[2] + s[56:87] + s[28]
|
|
||||||
elif len(s) == 87:
|
|
||||||
return s[6:27] + s[4] + s[28:39] + s[27] + s[40:59] + s[2] + s[60:]
|
|
||||||
elif len(s) == 86:
|
|
||||||
return s[80:72:-1] + s[16] + s[71:39:-1] + s[72] + s[38:16:-1] + s[82] + s[15::-1]
|
|
||||||
elif len(s) == 85:
|
|
||||||
return s[3:11] + s[0] + s[12:55] + s[84] + s[56:84]
|
|
||||||
elif len(s) == 84:
|
|
||||||
return s[78:70:-1] + s[14] + s[69:37:-1] + s[70] + s[36:14:-1] + s[80] + s[:14][::-1]
|
|
||||||
elif len(s) == 83:
|
|
||||||
return s[80:63:-1] + s[0] + s[62:0:-1] + s[63]
|
|
||||||
elif len(s) == 82:
|
|
||||||
return s[80:37:-1] + s[7] + s[36:7:-1] + s[0] + s[6:0:-1] + s[37]
|
|
||||||
elif len(s) == 81:
|
|
||||||
return s[56] + s[79:56:-1] + s[41] + s[55:41:-1] + s[80] + s[40:34:-1] + s[0] + s[33:29:-1] + s[34] + s[28:9:-1] + s[29] + s[8:0:-1] + s[9]
|
|
||||||
elif len(s) == 80:
|
|
||||||
return s[1:19] + s[0] + s[20:68] + s[19] + s[69:80]
|
|
||||||
elif len(s) == 79:
|
|
||||||
return s[54] + s[77:54:-1] + s[39] + s[53:39:-1] + s[78] + s[38:34:-1] + s[0] + s[33:29:-1] + s[34] + s[28:9:-1] + s[29] + s[8:0:-1] + s[9]
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise ExtractorError(u'Unable to decrypt signature, key length %d not supported; retrying might work' % (len(s)))
|
|
||||||
|
|
||||||
def _get_available_subtitles(self, video_id, webpage):
|
def _get_available_subtitles(self, video_id, webpage):
|
||||||
try:
|
try:
|
||||||
@@ -1140,7 +1097,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):
|
|||||||
mobj = re.search(r'(?s)id="eow-date.*?>(.*?)</span>', video_webpage)
|
mobj = re.search(r'(?s)id="eow-date.*?>(.*?)</span>', video_webpage)
|
||||||
if mobj is None:
|
if mobj is None:
|
||||||
mobj = re.search(
|
mobj = re.search(
|
||||||
r'(?s)id="watch-uploader-info".*?>.*?(?:Published|Uploaded) on (.*?)</strong>',
|
r'(?s)id="watch-uploader-info".*?>.*?(?:Published|Uploaded|Streamed live) on (.*?)</strong>',
|
||||||
video_webpage)
|
video_webpage)
|
||||||
if mobj is not None:
|
if mobj is not None:
|
||||||
upload_date = ' '.join(re.sub(r'[/,-]', r' ', mobj.group(1)).split())
|
upload_date = ' '.join(re.sub(r'[/,-]', r' ', mobj.group(1)).split())
|
||||||
@@ -1385,13 +1342,13 @@ class YoutubePlaylistIE(YoutubeBaseInfoExtractor):
|
|||||||
| p/
|
| p/
|
||||||
)
|
)
|
||||||
(
|
(
|
||||||
(?:PL|EC|UU|FL|RD)?[0-9A-Za-z-_]{10,}
|
(?:PL|LL|EC|UU|FL|RD)?[0-9A-Za-z-_]{10,}
|
||||||
# Top tracks, they can also include dots
|
# Top tracks, they can also include dots
|
||||||
|(?:MC)[\w\.]*
|
|(?:MC)[\w\.]*
|
||||||
)
|
)
|
||||||
.*
|
.*
|
||||||
|
|
|
|
||||||
((?:PL|EC|UU|FL|RD)[0-9A-Za-z-_]{10,})
|
((?:PL|LL|EC|UU|FL|RD)[0-9A-Za-z-_]{10,})
|
||||||
)"""
|
)"""
|
||||||
_TEMPLATE_URL = 'https://www.youtube.com/playlist?list=%s'
|
_TEMPLATE_URL = 'https://www.youtube.com/playlist?list=%s'
|
||||||
_MORE_PAGES_INDICATOR = r'data-link-type="next"'
|
_MORE_PAGES_INDICATOR = r'data-link-type="next"'
|
||||||
@@ -1414,11 +1371,9 @@ class YoutubePlaylistIE(YoutubeBaseInfoExtractor):
|
|||||||
title_span = (search_title('playlist-title') or
|
title_span = (search_title('playlist-title') or
|
||||||
search_title('title long-title') or search_title('title'))
|
search_title('title long-title') or search_title('title'))
|
||||||
title = clean_html(title_span)
|
title = clean_html(title_span)
|
||||||
video_re = r'''(?x)data-video-username="(.*?)".*?
|
video_re = r'''(?x)data-video-username=".*?".*?
|
||||||
href="/watch\?v=([0-9A-Za-z_-]{11})&[^"]*?list=%s''' % re.escape(playlist_id)
|
href="/watch\?v=([0-9A-Za-z_-]{11})&[^"]*?list=%s''' % re.escape(playlist_id)
|
||||||
matches = orderedSet(re.findall(video_re, webpage, flags=re.DOTALL))
|
ids = 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)
|
url_results = self._ids_to_results(ids)
|
||||||
|
|
||||||
return self.playlist_result(url_results, playlist_id, title)
|
return self.playlist_result(url_results, playlist_id, title)
|
||||||
@@ -1698,14 +1653,14 @@ class YoutubeSearchURLIE(InfoExtractor):
|
|||||||
|
|
||||||
webpage = self._download_webpage(url, query)
|
webpage = self._download_webpage(url, query)
|
||||||
result_code = self._search_regex(
|
result_code = self._search_regex(
|
||||||
r'(?s)<ol id="search-results"(.*?)</ol>', webpage, u'result HTML')
|
r'(?s)<ol class="item-section"(.*?)</ol>', webpage, u'result HTML')
|
||||||
|
|
||||||
part_codes = re.findall(
|
part_codes = re.findall(
|
||||||
r'(?s)<h3 class="yt-lockup-title">(.*?)</h3>', result_code)
|
r'(?s)<h3 class="yt-lockup-title">(.*?)</h3>', result_code)
|
||||||
entries = []
|
entries = []
|
||||||
for part_code in part_codes:
|
for part_code in part_codes:
|
||||||
part_title = self._html_search_regex(
|
part_title = self._html_search_regex(
|
||||||
r'(?s)title="([^"]+)"', part_code, 'item title', fatal=False)
|
[r'(?s)title="([^"]+)"', r'>([^<]+)</a>'], part_code, 'item title', fatal=False)
|
||||||
part_url_snippet = self._html_search_regex(
|
part_url_snippet = self._html_search_regex(
|
||||||
r'(?s)href="([^"]+)"', part_code, 'item URL')
|
r'(?s)href="([^"]+)"', part_code, 'item URL')
|
||||||
part_url = compat_urlparse.urljoin(
|
part_url = compat_urlparse.urljoin(
|
||||||
@@ -1825,10 +1780,21 @@ class YoutubeTruncatedURLIE(InfoExtractor):
|
|||||||
IE_NAME = 'youtube:truncated_url'
|
IE_NAME = 'youtube:truncated_url'
|
||||||
IE_DESC = False # Do not list
|
IE_DESC = False # Do not list
|
||||||
_VALID_URL = r'''(?x)
|
_VALID_URL = r'''(?x)
|
||||||
(?:https?://)?[^/]+/watch\?(?:feature=[a-z_]+)?$|
|
(?:https?://)?[^/]+/watch\?(?:
|
||||||
|
feature=[a-z_]+|
|
||||||
|
annotation_id=annotation_[^&]+
|
||||||
|
)?$|
|
||||||
(?:https?://)?(?:www\.)?youtube\.com/attribution_link\?a=[^&]+$
|
(?:https?://)?(?:www\.)?youtube\.com/attribution_link\?a=[^&]+$
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://www.youtube.com/watch?annotation_id=annotation_3951667041',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.youtube.com/watch?',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
raise ExtractorError(
|
raise ExtractorError(
|
||||||
u'Did you forget to quote the URL? Remember that & is a meta '
|
u'Did you forget to quote the URL? Remember that & is a meta '
|
||||||
|
@@ -11,6 +11,7 @@ class JSInterpreter(object):
|
|||||||
def __init__(self, code):
|
def __init__(self, code):
|
||||||
self.code = code
|
self.code = code
|
||||||
self._functions = {}
|
self._functions = {}
|
||||||
|
self._objects = {}
|
||||||
|
|
||||||
def interpret_statement(self, stmt, local_vars, allow_recursion=20):
|
def interpret_statement(self, stmt, local_vars, allow_recursion=20):
|
||||||
if allow_recursion < 0:
|
if allow_recursion < 0:
|
||||||
@@ -55,11 +56,23 @@ class JSInterpreter(object):
|
|||||||
m = re.match(r'^(?P<in>[a-z]+)\.(?P<member>.*)$', expr)
|
m = re.match(r'^(?P<in>[a-z]+)\.(?P<member>.*)$', expr)
|
||||||
if m:
|
if m:
|
||||||
member = m.group('member')
|
member = m.group('member')
|
||||||
val = local_vars[m.group('in')]
|
variable = m.group('in')
|
||||||
|
|
||||||
|
if variable not in local_vars:
|
||||||
|
if variable not in self._objects:
|
||||||
|
self._objects[variable] = self.extract_object(variable)
|
||||||
|
obj = self._objects[variable]
|
||||||
|
key, args = member.split('(', 1)
|
||||||
|
args = args.strip(')')
|
||||||
|
argvals = [int(v) if v.isdigit() else local_vars[v]
|
||||||
|
for v in args.split(',')]
|
||||||
|
return obj[key](argvals)
|
||||||
|
|
||||||
|
val = local_vars[variable]
|
||||||
if member == 'split("")':
|
if member == 'split("")':
|
||||||
return list(val)
|
return list(val)
|
||||||
if member == 'join("")':
|
if member == 'join("")':
|
||||||
return u''.join(val)
|
return ''.join(val)
|
||||||
if member == 'length':
|
if member == 'length':
|
||||||
return len(val)
|
return len(val)
|
||||||
if member == 'reverse()':
|
if member == 'reverse()':
|
||||||
@@ -97,9 +110,28 @@ class JSInterpreter(object):
|
|||||||
return self._functions[fname](argvals)
|
return self._functions[fname](argvals)
|
||||||
raise ExtractorError('Unsupported JS expression %r' % expr)
|
raise ExtractorError('Unsupported JS expression %r' % expr)
|
||||||
|
|
||||||
|
def extract_object(self, objname):
|
||||||
|
obj = {}
|
||||||
|
obj_m = re.search(
|
||||||
|
(r'(?:var\s+)?%s\s*=\s*\{' % re.escape(objname)) +
|
||||||
|
r'\s*(?P<fields>([a-zA-Z$]+\s*:\s*function\(.*?\)\s*\{.*?\})*)' +
|
||||||
|
r'\}\s*;',
|
||||||
|
self.code)
|
||||||
|
fields = obj_m.group('fields')
|
||||||
|
# Currently, it only supports function definitions
|
||||||
|
fields_m = re.finditer(
|
||||||
|
r'(?P<key>[a-zA-Z$]+)\s*:\s*function'
|
||||||
|
r'\((?P<args>[a-z,]+)\){(?P<code>[^}]+)}',
|
||||||
|
fields)
|
||||||
|
for f in fields_m:
|
||||||
|
argnames = f.group('args').split(',')
|
||||||
|
obj[f.group('key')] = self.build_function(argnames, f.group('code'))
|
||||||
|
|
||||||
|
return obj
|
||||||
|
|
||||||
def extract_function(self, funcname):
|
def extract_function(self, funcname):
|
||||||
func_m = re.search(
|
func_m = re.search(
|
||||||
(r'(?:function %s|%s\s*=\s*function)' % (
|
(r'(?:function %s|[{;]%s\s*=\s*function)' % (
|
||||||
re.escape(funcname), re.escape(funcname))) +
|
re.escape(funcname), re.escape(funcname))) +
|
||||||
r'\((?P<args>[a-z,]+)\){(?P<code>[^}]+)}',
|
r'\((?P<args>[a-z,]+)\){(?P<code>[^}]+)}',
|
||||||
self.code)
|
self.code)
|
||||||
@@ -107,10 +139,12 @@ class JSInterpreter(object):
|
|||||||
raise ExtractorError('Could not find JS function %r' % funcname)
|
raise ExtractorError('Could not find JS function %r' % funcname)
|
||||||
argnames = func_m.group('args').split(',')
|
argnames = func_m.group('args').split(',')
|
||||||
|
|
||||||
|
return self.build_function(argnames, func_m.group('code'))
|
||||||
|
|
||||||
|
def build_function(self, argnames, code):
|
||||||
def resf(args):
|
def resf(args):
|
||||||
local_vars = dict(zip(argnames, args))
|
local_vars = dict(zip(argnames, args))
|
||||||
for stmt in func_m.group('code').split(';'):
|
for stmt in code.split(';'):
|
||||||
res = self.interpret_statement(stmt, local_vars)
|
res = self.interpret_statement(stmt, local_vars)
|
||||||
return res
|
return res
|
||||||
return resf
|
return resf
|
||||||
|
|
||||||
|
@@ -775,7 +775,7 @@ class YoutubeDLHandler(compat_urllib_request.HTTPHandler):
|
|||||||
https_response = http_response
|
https_response = http_response
|
||||||
|
|
||||||
|
|
||||||
def parse_iso8601(date_str):
|
def parse_iso8601(date_str, delimiter='T'):
|
||||||
""" Return a UNIX timestamp from the given date """
|
""" Return a UNIX timestamp from the given date """
|
||||||
|
|
||||||
if date_str is None:
|
if date_str is None:
|
||||||
@@ -795,8 +795,8 @@ def parse_iso8601(date_str):
|
|||||||
timezone = datetime.timedelta(
|
timezone = datetime.timedelta(
|
||||||
hours=sign * int(m.group('hours')),
|
hours=sign * int(m.group('hours')),
|
||||||
minutes=sign * int(m.group('minutes')))
|
minutes=sign * int(m.group('minutes')))
|
||||||
|
date_format = '%Y-%m-%d{0}%H:%M:%S'.format(delimiter)
|
||||||
dt = datetime.datetime.strptime(date_str, '%Y-%m-%dT%H:%M:%S') - timezone
|
dt = datetime.datetime.strptime(date_str, date_format) - timezone
|
||||||
return calendar.timegm(dt.timetuple())
|
return calendar.timegm(dt.timetuple())
|
||||||
|
|
||||||
|
|
||||||
@@ -816,6 +816,9 @@ def unified_strdate(date_str):
|
|||||||
'%d %b %Y',
|
'%d %b %Y',
|
||||||
'%B %d %Y',
|
'%B %d %Y',
|
||||||
'%b %d %Y',
|
'%b %d %Y',
|
||||||
|
'%b %dst %Y %I:%M%p',
|
||||||
|
'%b %dnd %Y %I:%M%p',
|
||||||
|
'%b %dth %Y %I:%M%p',
|
||||||
'%Y-%m-%d',
|
'%Y-%m-%d',
|
||||||
'%d.%m.%Y',
|
'%d.%m.%Y',
|
||||||
'%d/%m/%Y',
|
'%d/%m/%Y',
|
||||||
@@ -1425,7 +1428,7 @@ US_RATINGS = {
|
|||||||
|
|
||||||
|
|
||||||
def strip_jsonp(code):
|
def strip_jsonp(code):
|
||||||
return re.sub(r'(?s)^[a-zA-Z_]+\s*\(\s*(.*)\);\s*?\s*$', r'\1', code)
|
return re.sub(r'(?s)^[a-zA-Z0-9_]+\s*\(\s*(.*)\);?\s*?\s*$', r'\1', code)
|
||||||
|
|
||||||
|
|
||||||
def qualities(quality_ids):
|
def qualities(quality_ids):
|
||||||
|
@@ -1,2 +1,2 @@
|
|||||||
|
|
||||||
__version__ = '2014.06.02'
|
__version__ = '2014.07.15'
|
||||||
|
Reference in New Issue
Block a user