Merge branch 'master' into use-other-downloaders
This commit is contained in:
commit
b6dfeb910e
@ -92,12 +92,14 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
ownloads/%(uploader)s/%(title)s-%(id)s.%(ext)s' .
|
ownloads/%(uploader)s/%(title)s-%(id)s.%(ext)s' .
|
||||||
--autonumber-size NUMBER Specifies the number of digits in %(autonumber)s
|
--autonumber-size NUMBER Specifies the number of digits in %(autonumber)s
|
||||||
when it is present in output filename template or
|
when it is present in output filename template or
|
||||||
--autonumber option is given
|
--auto-number option is given
|
||||||
--restrict-filenames Restrict filenames to only ASCII characters, and
|
--restrict-filenames Restrict filenames to only ASCII characters, and
|
||||||
avoid "&" and spaces in filenames
|
avoid "&" and spaces in filenames
|
||||||
-a, --batch-file FILE file containing URLs to download ('-' for stdin)
|
-a, --batch-file FILE file containing URLs to download ('-' for stdin)
|
||||||
-w, --no-overwrites do not overwrite files
|
-w, --no-overwrites do not overwrite files
|
||||||
-c, --continue resume partially downloaded files
|
-c, --continue force resume of partially downloaded files. By
|
||||||
|
default, youtube-dl will resume downloads if
|
||||||
|
possible.
|
||||||
--no-continue do not resume partially downloaded files (restart
|
--no-continue do not resume partially downloaded files (restart
|
||||||
from beginning)
|
from beginning)
|
||||||
--cookies FILE file to read cookies from and dump cookie jar in
|
--cookies FILE file to read cookies from and dump cookie jar in
|
||||||
|
@ -128,6 +128,18 @@ class TestFormatSelection(unittest.TestCase):
|
|||||||
downloaded = ydl.downloaded_info_dicts[0]
|
downloaded = ydl.downloaded_info_dicts[0]
|
||||||
self.assertEqual(downloaded['format_id'], u'35')
|
self.assertEqual(downloaded['format_id'], u'35')
|
||||||
|
|
||||||
|
def test_add_extra_info(self):
|
||||||
|
test_dict = {
|
||||||
|
'extractor': 'Foo',
|
||||||
|
}
|
||||||
|
extra_info = {
|
||||||
|
'extractor': 'Bar',
|
||||||
|
'playlist': 'funny videos',
|
||||||
|
}
|
||||||
|
YDL.add_extra_info(test_dict, extra_info)
|
||||||
|
self.assertEqual(test_dict['extractor'], 'Foo')
|
||||||
|
self.assertEqual(test_dict['playlist'], 'funny videos')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -1,70 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
# Allow direct execution
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import unittest
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
||||||
|
|
||||||
from test.helper import FakeYDL, global_setup, md5
|
|
||||||
global_setup()
|
|
||||||
|
|
||||||
|
|
||||||
from youtube_dl.extractor import DailymotionIE
|
|
||||||
|
|
||||||
class TestDailymotionSubtitles(unittest.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.DL = FakeYDL()
|
|
||||||
self.url = 'http://www.dailymotion.com/video/xczg00'
|
|
||||||
def getInfoDict(self):
|
|
||||||
IE = DailymotionIE(self.DL)
|
|
||||||
info_dict = IE.extract(self.url)
|
|
||||||
return info_dict
|
|
||||||
def getSubtitles(self):
|
|
||||||
info_dict = self.getInfoDict()
|
|
||||||
return info_dict['subtitles']
|
|
||||||
def test_no_writesubtitles(self):
|
|
||||||
subtitles = self.getSubtitles()
|
|
||||||
self.assertEqual(subtitles, None)
|
|
||||||
def test_subtitles(self):
|
|
||||||
self.DL.params['writesubtitles'] = True
|
|
||||||
subtitles = self.getSubtitles()
|
|
||||||
self.assertEqual(md5(subtitles['en']), '976553874490cba125086bbfea3ff76f')
|
|
||||||
def test_subtitles_lang(self):
|
|
||||||
self.DL.params['writesubtitles'] = True
|
|
||||||
self.DL.params['subtitleslangs'] = ['fr']
|
|
||||||
subtitles = self.getSubtitles()
|
|
||||||
self.assertEqual(md5(subtitles['fr']), '594564ec7d588942e384e920e5341792')
|
|
||||||
def test_allsubtitles(self):
|
|
||||||
self.DL.params['writesubtitles'] = True
|
|
||||||
self.DL.params['allsubtitles'] = True
|
|
||||||
subtitles = self.getSubtitles()
|
|
||||||
self.assertEqual(len(subtitles.keys()), 5)
|
|
||||||
def test_list_subtitles(self):
|
|
||||||
self.DL.expect_warning(u'Automatic Captions not supported by this server')
|
|
||||||
self.DL.params['listsubtitles'] = True
|
|
||||||
info_dict = self.getInfoDict()
|
|
||||||
self.assertEqual(info_dict, None)
|
|
||||||
def test_automatic_captions(self):
|
|
||||||
self.DL.expect_warning(u'Automatic Captions not supported by this server')
|
|
||||||
self.DL.params['writeautomaticsub'] = True
|
|
||||||
self.DL.params['subtitleslang'] = ['en']
|
|
||||||
subtitles = self.getSubtitles()
|
|
||||||
self.assertTrue(len(subtitles.keys()) == 0)
|
|
||||||
def test_nosubtitles(self):
|
|
||||||
self.DL.expect_warning(u'video doesn\'t have subtitles')
|
|
||||||
self.url = 'http://www.dailymotion.com/video/x12u166_le-zapping-tele-star-du-08-aout-2013_tv'
|
|
||||||
self.DL.params['writesubtitles'] = True
|
|
||||||
self.DL.params['allsubtitles'] = True
|
|
||||||
subtitles = self.getSubtitles()
|
|
||||||
self.assertEqual(len(subtitles), 0)
|
|
||||||
def test_multiple_langs(self):
|
|
||||||
self.DL.params['writesubtitles'] = True
|
|
||||||
langs = ['es', 'fr', 'de']
|
|
||||||
self.DL.params['subtitleslangs'] = langs
|
|
||||||
subtitles = self.getSubtitles()
|
|
||||||
for lang in langs:
|
|
||||||
self.assertTrue(subtitles.get(lang) is not None, u'Subtitles for \'%s\' not extracted' % lang)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
unittest.main()
|
|
@ -31,6 +31,7 @@ from youtube_dl.utils import (
|
|||||||
ExtractorError,
|
ExtractorError,
|
||||||
UnavailableVideoError,
|
UnavailableVideoError,
|
||||||
)
|
)
|
||||||
|
from youtube_dl.extractor import get_info_extractor
|
||||||
|
|
||||||
RETRIES = 3
|
RETRIES = 3
|
||||||
|
|
||||||
@ -63,9 +64,10 @@ def generator(test_case):
|
|||||||
|
|
||||||
def test_template(self):
|
def test_template(self):
|
||||||
ie = youtube_dl.extractor.get_info_extractor(test_case['name'])
|
ie = youtube_dl.extractor.get_info_extractor(test_case['name'])
|
||||||
|
other_ies = [get_info_extractor(ie_key) for ie_key in test_case.get('add_ie', [])]
|
||||||
def print_skipping(reason):
|
def print_skipping(reason):
|
||||||
print('Skipping %s: %s' % (test_case['name'], reason))
|
print('Skipping %s: %s' % (test_case['name'], reason))
|
||||||
if not ie._WORKING:
|
if not ie.working():
|
||||||
print_skipping('IE marked as not _WORKING')
|
print_skipping('IE marked as not _WORKING')
|
||||||
return
|
return
|
||||||
if 'playlist' not in test_case:
|
if 'playlist' not in test_case:
|
||||||
@ -77,6 +79,10 @@ def generator(test_case):
|
|||||||
if 'skip' in test_case:
|
if 'skip' in test_case:
|
||||||
print_skipping(test_case['skip'])
|
print_skipping(test_case['skip'])
|
||||||
return
|
return
|
||||||
|
for other_ie in other_ies:
|
||||||
|
if not other_ie.working():
|
||||||
|
print_skipping(u'test depends on %sIE, marked as not WORKING' % other_ie.ie_key())
|
||||||
|
return
|
||||||
|
|
||||||
params = get_params(test_case.get('params', {}))
|
params = get_params(test_case.get('params', {}))
|
||||||
|
|
||||||
@ -148,6 +154,9 @@ def generator(test_case):
|
|||||||
# Check for the presence of mandatory fields
|
# Check for the presence of mandatory fields
|
||||||
for key in ('id', 'url', 'title', 'ext'):
|
for key in ('id', 'url', 'title', 'ext'):
|
||||||
self.assertTrue(key in info_dict.keys() and info_dict[key])
|
self.assertTrue(key in info_dict.keys() and info_dict[key])
|
||||||
|
# Check for mandatory fields that are automatically set by YoutubeDL
|
||||||
|
for key in ['webpage_url', 'extractor', 'extractor_key']:
|
||||||
|
self.assertTrue(info_dict.get(key), u'Missing field: %s' % key)
|
||||||
finally:
|
finally:
|
||||||
try_rm_tcs_files()
|
try_rm_tcs_files()
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ from youtube_dl.extractor import (
|
|||||||
DailymotionUserIE,
|
DailymotionUserIE,
|
||||||
VimeoChannelIE,
|
VimeoChannelIE,
|
||||||
UstreamChannelIE,
|
UstreamChannelIE,
|
||||||
|
SoundcloudSetIE,
|
||||||
SoundcloudUserIE,
|
SoundcloudUserIE,
|
||||||
LivestreamIE,
|
LivestreamIE,
|
||||||
NHLVideocenterIE,
|
NHLVideocenterIE,
|
||||||
@ -61,6 +62,14 @@ class TestPlaylists(unittest.TestCase):
|
|||||||
self.assertEqual(result['id'], u'5124905')
|
self.assertEqual(result['id'], u'5124905')
|
||||||
self.assertTrue(len(result['entries']) >= 11)
|
self.assertTrue(len(result['entries']) >= 11)
|
||||||
|
|
||||||
|
def test_soundcloud_set(self):
|
||||||
|
dl = FakeYDL()
|
||||||
|
ie = SoundcloudSetIE(dl)
|
||||||
|
result = ie.extract('https://soundcloud.com/the-concept-band/sets/the-royal-concept-ep')
|
||||||
|
self.assertIsPlaylist(result)
|
||||||
|
self.assertEqual(result['title'], u'The Royal Concept EP')
|
||||||
|
self.assertTrue(len(result['entries']) >= 6)
|
||||||
|
|
||||||
def test_soundcloud_user(self):
|
def test_soundcloud_user(self):
|
||||||
dl = FakeYDL()
|
dl = FakeYDL()
|
||||||
ie = SoundcloudUserIE(dl)
|
ie = SoundcloudUserIE(dl)
|
||||||
|
211
test/test_subtitles.py
Normal file
211
test/test_subtitles.py
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# Allow direct execution
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
from test.helper import FakeYDL, global_setup, md5
|
||||||
|
global_setup()
|
||||||
|
|
||||||
|
|
||||||
|
from youtube_dl.extractor import (
|
||||||
|
YoutubeIE,
|
||||||
|
DailymotionIE,
|
||||||
|
TEDIE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BaseTestSubtitles(unittest.TestCase):
|
||||||
|
url = None
|
||||||
|
IE = None
|
||||||
|
def setUp(self):
|
||||||
|
self.DL = FakeYDL()
|
||||||
|
self.ie = self.IE(self.DL)
|
||||||
|
|
||||||
|
def getInfoDict(self):
|
||||||
|
info_dict = self.ie.extract(self.url)
|
||||||
|
return info_dict
|
||||||
|
|
||||||
|
def getSubtitles(self):
|
||||||
|
info_dict = self.getInfoDict()
|
||||||
|
return info_dict['subtitles']
|
||||||
|
|
||||||
|
|
||||||
|
class TestYoutubeSubtitles(BaseTestSubtitles):
|
||||||
|
url = 'QRS8MkLhQmM'
|
||||||
|
IE = YoutubeIE
|
||||||
|
|
||||||
|
def getSubtitles(self):
|
||||||
|
info_dict = self.getInfoDict()
|
||||||
|
return info_dict[0]['subtitles']
|
||||||
|
|
||||||
|
def test_youtube_no_writesubtitles(self):
|
||||||
|
self.DL.params['writesubtitles'] = False
|
||||||
|
subtitles = self.getSubtitles()
|
||||||
|
self.assertEqual(subtitles, None)
|
||||||
|
|
||||||
|
def test_youtube_subtitles(self):
|
||||||
|
self.DL.params['writesubtitles'] = True
|
||||||
|
subtitles = self.getSubtitles()
|
||||||
|
self.assertEqual(md5(subtitles['en']), '4cd9278a35ba2305f47354ee13472260')
|
||||||
|
|
||||||
|
def test_youtube_subtitles_lang(self):
|
||||||
|
self.DL.params['writesubtitles'] = True
|
||||||
|
self.DL.params['subtitleslangs'] = ['it']
|
||||||
|
subtitles = self.getSubtitles()
|
||||||
|
self.assertEqual(md5(subtitles['it']), '164a51f16f260476a05b50fe4c2f161d')
|
||||||
|
|
||||||
|
def test_youtube_allsubtitles(self):
|
||||||
|
self.DL.params['writesubtitles'] = True
|
||||||
|
self.DL.params['allsubtitles'] = True
|
||||||
|
subtitles = self.getSubtitles()
|
||||||
|
self.assertEqual(len(subtitles.keys()), 13)
|
||||||
|
|
||||||
|
def test_youtube_subtitles_sbv_format(self):
|
||||||
|
self.DL.params['writesubtitles'] = True
|
||||||
|
self.DL.params['subtitlesformat'] = 'sbv'
|
||||||
|
subtitles = self.getSubtitles()
|
||||||
|
self.assertEqual(md5(subtitles['en']), '13aeaa0c245a8bed9a451cb643e3ad8b')
|
||||||
|
|
||||||
|
def test_youtube_subtitles_vtt_format(self):
|
||||||
|
self.DL.params['writesubtitles'] = True
|
||||||
|
self.DL.params['subtitlesformat'] = 'vtt'
|
||||||
|
subtitles = self.getSubtitles()
|
||||||
|
self.assertEqual(md5(subtitles['en']), '356cdc577fde0c6783b9b822e7206ff7')
|
||||||
|
|
||||||
|
def test_youtube_list_subtitles(self):
|
||||||
|
self.DL.expect_warning(u'Video doesn\'t have automatic captions')
|
||||||
|
self.DL.params['listsubtitles'] = True
|
||||||
|
info_dict = self.getInfoDict()
|
||||||
|
self.assertEqual(info_dict, None)
|
||||||
|
|
||||||
|
def test_youtube_automatic_captions(self):
|
||||||
|
self.url = '8YoUxe5ncPo'
|
||||||
|
self.DL.params['writeautomaticsub'] = True
|
||||||
|
self.DL.params['subtitleslangs'] = ['it']
|
||||||
|
subtitles = self.getSubtitles()
|
||||||
|
self.assertTrue(subtitles['it'] is not None)
|
||||||
|
|
||||||
|
def test_youtube_nosubtitles(self):
|
||||||
|
self.DL.expect_warning(u'video doesn\'t have subtitles')
|
||||||
|
self.url = 'sAjKT8FhjI8'
|
||||||
|
self.DL.params['writesubtitles'] = True
|
||||||
|
self.DL.params['allsubtitles'] = True
|
||||||
|
subtitles = self.getSubtitles()
|
||||||
|
self.assertEqual(len(subtitles), 0)
|
||||||
|
|
||||||
|
def test_youtube_multiple_langs(self):
|
||||||
|
self.url = 'QRS8MkLhQmM'
|
||||||
|
self.DL.params['writesubtitles'] = True
|
||||||
|
langs = ['it', 'fr', 'de']
|
||||||
|
self.DL.params['subtitleslangs'] = langs
|
||||||
|
subtitles = self.getSubtitles()
|
||||||
|
for lang in langs:
|
||||||
|
self.assertTrue(subtitles.get(lang) is not None, u'Subtitles for \'%s\' not extracted' % lang)
|
||||||
|
|
||||||
|
|
||||||
|
class TestDailymotionSubtitles(BaseTestSubtitles):
|
||||||
|
url = 'http://www.dailymotion.com/video/xczg00'
|
||||||
|
IE = DailymotionIE
|
||||||
|
|
||||||
|
def test_no_writesubtitles(self):
|
||||||
|
subtitles = self.getSubtitles()
|
||||||
|
self.assertEqual(subtitles, None)
|
||||||
|
|
||||||
|
def test_subtitles(self):
|
||||||
|
self.DL.params['writesubtitles'] = True
|
||||||
|
subtitles = self.getSubtitles()
|
||||||
|
self.assertEqual(md5(subtitles['en']), '976553874490cba125086bbfea3ff76f')
|
||||||
|
|
||||||
|
def test_subtitles_lang(self):
|
||||||
|
self.DL.params['writesubtitles'] = True
|
||||||
|
self.DL.params['subtitleslangs'] = ['fr']
|
||||||
|
subtitles = self.getSubtitles()
|
||||||
|
self.assertEqual(md5(subtitles['fr']), '594564ec7d588942e384e920e5341792')
|
||||||
|
|
||||||
|
def test_allsubtitles(self):
|
||||||
|
self.DL.params['writesubtitles'] = True
|
||||||
|
self.DL.params['allsubtitles'] = True
|
||||||
|
subtitles = self.getSubtitles()
|
||||||
|
self.assertEqual(len(subtitles.keys()), 5)
|
||||||
|
|
||||||
|
def test_list_subtitles(self):
|
||||||
|
self.DL.expect_warning(u'Automatic Captions not supported by this server')
|
||||||
|
self.DL.params['listsubtitles'] = True
|
||||||
|
info_dict = self.getInfoDict()
|
||||||
|
self.assertEqual(info_dict, None)
|
||||||
|
|
||||||
|
def test_automatic_captions(self):
|
||||||
|
self.DL.expect_warning(u'Automatic Captions not supported by this server')
|
||||||
|
self.DL.params['writeautomaticsub'] = True
|
||||||
|
self.DL.params['subtitleslang'] = ['en']
|
||||||
|
subtitles = self.getSubtitles()
|
||||||
|
self.assertTrue(len(subtitles.keys()) == 0)
|
||||||
|
|
||||||
|
def test_nosubtitles(self):
|
||||||
|
self.DL.expect_warning(u'video doesn\'t have subtitles')
|
||||||
|
self.url = 'http://www.dailymotion.com/video/x12u166_le-zapping-tele-star-du-08-aout-2013_tv'
|
||||||
|
self.DL.params['writesubtitles'] = True
|
||||||
|
self.DL.params['allsubtitles'] = True
|
||||||
|
subtitles = self.getSubtitles()
|
||||||
|
self.assertEqual(len(subtitles), 0)
|
||||||
|
|
||||||
|
def test_multiple_langs(self):
|
||||||
|
self.DL.params['writesubtitles'] = True
|
||||||
|
langs = ['es', 'fr', 'de']
|
||||||
|
self.DL.params['subtitleslangs'] = langs
|
||||||
|
subtitles = self.getSubtitles()
|
||||||
|
for lang in langs:
|
||||||
|
self.assertTrue(subtitles.get(lang) is not None, u'Subtitles for \'%s\' not extracted' % lang)
|
||||||
|
|
||||||
|
|
||||||
|
class TestTedSubtitles(BaseTestSubtitles):
|
||||||
|
url = 'http://www.ted.com/talks/dan_dennett_on_our_consciousness.html'
|
||||||
|
IE = TEDIE
|
||||||
|
|
||||||
|
def test_no_writesubtitles(self):
|
||||||
|
subtitles = self.getSubtitles()
|
||||||
|
self.assertEqual(subtitles, None)
|
||||||
|
|
||||||
|
def test_subtitles(self):
|
||||||
|
self.DL.params['writesubtitles'] = True
|
||||||
|
subtitles = self.getSubtitles()
|
||||||
|
self.assertEqual(md5(subtitles['en']), '2154f31ff9b9f89a0aa671537559c21d')
|
||||||
|
|
||||||
|
def test_subtitles_lang(self):
|
||||||
|
self.DL.params['writesubtitles'] = True
|
||||||
|
self.DL.params['subtitleslangs'] = ['fr']
|
||||||
|
subtitles = self.getSubtitles()
|
||||||
|
self.assertEqual(md5(subtitles['fr']), '7616cbc6df20ec2c1204083c83871cf6')
|
||||||
|
|
||||||
|
def test_allsubtitles(self):
|
||||||
|
self.DL.params['writesubtitles'] = True
|
||||||
|
self.DL.params['allsubtitles'] = True
|
||||||
|
subtitles = self.getSubtitles()
|
||||||
|
self.assertEqual(len(subtitles.keys()), 28)
|
||||||
|
|
||||||
|
def test_list_subtitles(self):
|
||||||
|
self.DL.expect_warning(u'Automatic Captions not supported by this server')
|
||||||
|
self.DL.params['listsubtitles'] = True
|
||||||
|
info_dict = self.getInfoDict()
|
||||||
|
self.assertEqual(info_dict, None)
|
||||||
|
|
||||||
|
def test_automatic_captions(self):
|
||||||
|
self.DL.expect_warning(u'Automatic Captions not supported by this server')
|
||||||
|
self.DL.params['writeautomaticsub'] = True
|
||||||
|
self.DL.params['subtitleslang'] = ['en']
|
||||||
|
subtitles = self.getSubtitles()
|
||||||
|
self.assertTrue(len(subtitles.keys()) == 0)
|
||||||
|
|
||||||
|
def test_multiple_langs(self):
|
||||||
|
self.DL.params['writesubtitles'] = True
|
||||||
|
langs = ['es', 'fr', 'de']
|
||||||
|
self.DL.params['subtitleslangs'] = langs
|
||||||
|
subtitles = self.getSubtitles()
|
||||||
|
for lang in langs:
|
||||||
|
self.assertTrue(subtitles.get(lang) is not None, u'Subtitles for \'%s\' not extracted' % lang)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
@ -1,95 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
# Allow direct execution
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import unittest
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
||||||
|
|
||||||
from test.helper import FakeYDL, global_setup, md5
|
|
||||||
global_setup()
|
|
||||||
|
|
||||||
|
|
||||||
from youtube_dl.extractor import YoutubeIE
|
|
||||||
|
|
||||||
|
|
||||||
class TestYoutubeSubtitles(unittest.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.DL = FakeYDL()
|
|
||||||
self.url = 'QRS8MkLhQmM'
|
|
||||||
|
|
||||||
def getInfoDict(self):
|
|
||||||
IE = YoutubeIE(self.DL)
|
|
||||||
info_dict = IE.extract(self.url)
|
|
||||||
return info_dict
|
|
||||||
|
|
||||||
def getSubtitles(self):
|
|
||||||
info_dict = self.getInfoDict()
|
|
||||||
return info_dict[0]['subtitles']
|
|
||||||
|
|
||||||
def test_youtube_no_writesubtitles(self):
|
|
||||||
self.DL.params['writesubtitles'] = False
|
|
||||||
subtitles = self.getSubtitles()
|
|
||||||
self.assertEqual(subtitles, None)
|
|
||||||
|
|
||||||
def test_youtube_subtitles(self):
|
|
||||||
self.DL.params['writesubtitles'] = True
|
|
||||||
subtitles = self.getSubtitles()
|
|
||||||
self.assertEqual(md5(subtitles['en']), '4cd9278a35ba2305f47354ee13472260')
|
|
||||||
|
|
||||||
def test_youtube_subtitles_lang(self):
|
|
||||||
self.DL.params['writesubtitles'] = True
|
|
||||||
self.DL.params['subtitleslangs'] = ['it']
|
|
||||||
subtitles = self.getSubtitles()
|
|
||||||
self.assertEqual(md5(subtitles['it']), '164a51f16f260476a05b50fe4c2f161d')
|
|
||||||
|
|
||||||
def test_youtube_allsubtitles(self):
|
|
||||||
self.DL.params['writesubtitles'] = True
|
|
||||||
self.DL.params['allsubtitles'] = True
|
|
||||||
subtitles = self.getSubtitles()
|
|
||||||
self.assertEqual(len(subtitles.keys()), 13)
|
|
||||||
|
|
||||||
def test_youtube_subtitles_sbv_format(self):
|
|
||||||
self.DL.params['writesubtitles'] = True
|
|
||||||
self.DL.params['subtitlesformat'] = 'sbv'
|
|
||||||
subtitles = self.getSubtitles()
|
|
||||||
self.assertEqual(md5(subtitles['en']), '13aeaa0c245a8bed9a451cb643e3ad8b')
|
|
||||||
|
|
||||||
def test_youtube_subtitles_vtt_format(self):
|
|
||||||
self.DL.params['writesubtitles'] = True
|
|
||||||
self.DL.params['subtitlesformat'] = 'vtt'
|
|
||||||
subtitles = self.getSubtitles()
|
|
||||||
self.assertEqual(md5(subtitles['en']), '356cdc577fde0c6783b9b822e7206ff7')
|
|
||||||
|
|
||||||
def test_youtube_list_subtitles(self):
|
|
||||||
self.DL.expect_warning(u'Video doesn\'t have automatic captions')
|
|
||||||
self.DL.params['listsubtitles'] = True
|
|
||||||
info_dict = self.getInfoDict()
|
|
||||||
self.assertEqual(info_dict, None)
|
|
||||||
|
|
||||||
def test_youtube_automatic_captions(self):
|
|
||||||
self.url = '8YoUxe5ncPo'
|
|
||||||
self.DL.params['writeautomaticsub'] = True
|
|
||||||
self.DL.params['subtitleslangs'] = ['it']
|
|
||||||
subtitles = self.getSubtitles()
|
|
||||||
self.assertTrue(subtitles['it'] is not None)
|
|
||||||
|
|
||||||
def test_youtube_nosubtitles(self):
|
|
||||||
self.DL.expect_warning(u'video doesn\'t have subtitles')
|
|
||||||
self.url = 'sAjKT8FhjI8'
|
|
||||||
self.DL.params['writesubtitles'] = True
|
|
||||||
self.DL.params['allsubtitles'] = True
|
|
||||||
subtitles = self.getSubtitles()
|
|
||||||
self.assertEqual(len(subtitles), 0)
|
|
||||||
|
|
||||||
def test_youtube_multiple_langs(self):
|
|
||||||
self.url = 'QRS8MkLhQmM'
|
|
||||||
self.DL.params['writesubtitles'] = True
|
|
||||||
langs = ['it', 'fr', 'de']
|
|
||||||
self.DL.params['subtitleslangs'] = langs
|
|
||||||
subtitles = self.getSubtitles()
|
|
||||||
for lang in langs:
|
|
||||||
self.assertTrue(subtitles.get(lang) is not None, u'Subtitles for \'%s\' not extracted' % lang)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
unittest.main()
|
|
@ -4,12 +4,19 @@ import re
|
|||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import traceback
|
|
||||||
|
|
||||||
if os.name == 'nt':
|
if os.name == 'nt':
|
||||||
import ctypes
|
import ctypes
|
||||||
|
|
||||||
from .utils import *
|
from .utils import (
|
||||||
|
compat_urllib_error,
|
||||||
|
compat_urllib_request,
|
||||||
|
ContentTooShortError,
|
||||||
|
determine_ext,
|
||||||
|
encodeFilename,
|
||||||
|
sanitize_open,
|
||||||
|
timeconvert,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class FileDownloader(object):
|
class FileDownloader(object):
|
||||||
@ -194,7 +201,7 @@ class FileDownloader(object):
|
|||||||
if old_filename == new_filename:
|
if old_filename == new_filename:
|
||||||
return
|
return
|
||||||
os.rename(encodeFilename(old_filename), encodeFilename(new_filename))
|
os.rename(encodeFilename(old_filename), encodeFilename(new_filename))
|
||||||
except (IOError, OSError) as err:
|
except (IOError, OSError):
|
||||||
self.report_error(u'unable to rename file')
|
self.report_error(u'unable to rename file')
|
||||||
|
|
||||||
def try_utime(self, filename, last_modified_hdr):
|
def try_utime(self, filename, last_modified_hdr):
|
||||||
@ -227,8 +234,14 @@ class FileDownloader(object):
|
|||||||
if self.params.get('noprogress', False):
|
if self.params.get('noprogress', False):
|
||||||
return
|
return
|
||||||
clear_line = (u'\x1b[K' if sys.stderr.isatty() and os.name != 'nt' else u'')
|
clear_line = (u'\x1b[K' if sys.stderr.isatty() and os.name != 'nt' else u'')
|
||||||
|
if eta is not None:
|
||||||
eta_str = self.format_eta(eta)
|
eta_str = self.format_eta(eta)
|
||||||
|
else:
|
||||||
|
eta_str = 'Unknown ETA'
|
||||||
|
if percent is not None:
|
||||||
percent_str = self.format_percent(percent)
|
percent_str = self.format_percent(percent)
|
||||||
|
else:
|
||||||
|
percent_str = 'Unknown %'
|
||||||
speed_str = self.format_speed(speed)
|
speed_str = self.format_speed(speed)
|
||||||
if self.params.get('progress_with_newline', False):
|
if self.params.get('progress_with_newline', False):
|
||||||
self.to_screen(u'[download] %s of %s at %s ETA %s' %
|
self.to_screen(u'[download] %s of %s at %s ETA %s' %
|
||||||
@ -251,7 +264,7 @@ class FileDownloader(object):
|
|||||||
"""Report file has already been fully downloaded."""
|
"""Report file has already been fully downloaded."""
|
||||||
try:
|
try:
|
||||||
self.to_screen(u'[download] %s has already been downloaded' % file_name)
|
self.to_screen(u'[download] %s has already been downloaded' % file_name)
|
||||||
except (UnicodeEncodeError) as err:
|
except UnicodeEncodeError:
|
||||||
self.to_screen(u'[download] The file has already been downloaded')
|
self.to_screen(u'[download] The file has already been downloaded')
|
||||||
|
|
||||||
def report_unable_to_resume(self):
|
def report_unable_to_resume(self):
|
||||||
@ -267,7 +280,7 @@ class FileDownloader(object):
|
|||||||
self.to_screen(u'\r%s[download] 100%% of %s in %s' %
|
self.to_screen(u'\r%s[download] 100%% of %s in %s' %
|
||||||
(clear_line, data_len_str, self.format_seconds(tot_time)))
|
(clear_line, data_len_str, self.format_seconds(tot_time)))
|
||||||
|
|
||||||
def _download_with_rtmpdump(self, filename, url, player_url, page_url, play_path, tc_url):
|
def _download_with_rtmpdump(self, filename, url, player_url, page_url, play_path, tc_url, live):
|
||||||
self.report_destination(filename)
|
self.report_destination(filename)
|
||||||
tmpfilename = self.temp_name(filename)
|
tmpfilename = self.temp_name(filename)
|
||||||
test = self.params.get('test', False)
|
test = self.params.get('test', False)
|
||||||
@ -294,6 +307,8 @@ class FileDownloader(object):
|
|||||||
basic_args += ['--tcUrl', url]
|
basic_args += ['--tcUrl', url]
|
||||||
if test:
|
if test:
|
||||||
basic_args += ['--stop', '1']
|
basic_args += ['--stop', '1']
|
||||||
|
if live:
|
||||||
|
basic_args += ['--live']
|
||||||
args = basic_args + [[], ['--resume', '--skip', '1']][self.params.get('continuedl', False)]
|
args = basic_args + [[], ['--resume', '--skip', '1']][self.params.get('continuedl', False)]
|
||||||
if self.params.get('verbose', False):
|
if self.params.get('verbose', False):
|
||||||
try:
|
try:
|
||||||
@ -423,15 +438,20 @@ class FileDownloader(object):
|
|||||||
self.report_destination(filename)
|
self.report_destination(filename)
|
||||||
tmpfilename = self.temp_name(filename)
|
tmpfilename = self.temp_name(filename)
|
||||||
|
|
||||||
args = ['ffmpeg', '-y', '-i', url, '-f', 'mp4', tmpfilename]
|
args = ['-y', '-i', url, '-f', 'mp4', '-c', 'copy',
|
||||||
# Check for ffmpeg first
|
'-bsf:a', 'aac_adtstoasc', tmpfilename]
|
||||||
try:
|
|
||||||
subprocess.call(['ffmpeg', '-h'], stdout=(open(os.path.devnull, 'w')), stderr=subprocess.STDOUT)
|
|
||||||
except (OSError, IOError):
|
|
||||||
self.report_error(u'm3u8 download detected but "%s" could not be run' % args[0] )
|
|
||||||
return False
|
|
||||||
|
|
||||||
retval = subprocess.call(args)
|
for program in ['avconv', 'ffmpeg']:
|
||||||
|
try:
|
||||||
|
subprocess.call([program, '-version'], stdout=(open(os.path.devnull, 'w')), stderr=subprocess.STDOUT)
|
||||||
|
break
|
||||||
|
except (OSError, IOError):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self.report_error(u'm3u8 download detected but ffmpeg or avconv could not be found')
|
||||||
|
cmd = [program] + args
|
||||||
|
|
||||||
|
retval = subprocess.call(cmd)
|
||||||
if retval == 0:
|
if retval == 0:
|
||||||
fsize = os.path.getsize(encodeFilename(tmpfilename))
|
fsize = os.path.getsize(encodeFilename(tmpfilename))
|
||||||
self.to_screen(u'\r[%s] %s bytes' % (args[0], fsize))
|
self.to_screen(u'\r[%s] %s bytes' % (args[0], fsize))
|
||||||
@ -468,7 +488,8 @@ class FileDownloader(object):
|
|||||||
info_dict.get('player_url', None),
|
info_dict.get('player_url', None),
|
||||||
info_dict.get('page_url', None),
|
info_dict.get('page_url', None),
|
||||||
info_dict.get('play_path', None),
|
info_dict.get('play_path', None),
|
||||||
info_dict.get('tc_url', None))
|
info_dict.get('tc_url', None),
|
||||||
|
info_dict.get('rtmp_live', False))
|
||||||
|
|
||||||
# Attempt to download using mplayer
|
# Attempt to download using mplayer
|
||||||
if url.startswith('mms') or url.startswith('rtsp'):
|
if url.startswith('mms') or url.startswith('rtsp'):
|
||||||
@ -609,8 +630,7 @@ class FileDownloader(object):
|
|||||||
# Progress message
|
# Progress message
|
||||||
speed = self.calc_speed(start, time.time(), byte_counter - resume_len)
|
speed = self.calc_speed(start, time.time(), byte_counter - resume_len)
|
||||||
if data_len is None:
|
if data_len is None:
|
||||||
self.report_progress('Unknown %', data_len_str, speed_str, 'Unknown ETA')
|
eta = percent = None
|
||||||
eta = None
|
|
||||||
else:
|
else:
|
||||||
percent = self.calc_percent(byte_counter, data_len)
|
percent = self.calc_percent(byte_counter, data_len)
|
||||||
eta = self.calc_eta(start, time.time(), data_len - resume_len, byte_counter - resume_len)
|
eta = self.calc_eta(start, time.time(), data_len - resume_len, byte_counter - resume_len)
|
||||||
|
@ -318,6 +318,12 @@ class YoutubeDL(object):
|
|||||||
% info_dict)
|
% info_dict)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def add_extra_info(info_dict, extra_info):
|
||||||
|
'''Set the keys from extra_info in info dict if they are missing'''
|
||||||
|
for key, value in extra_info.items():
|
||||||
|
info_dict.setdefault(key, value)
|
||||||
|
|
||||||
def extract_info(self, url, download=True, ie_key=None, extra_info={}):
|
def extract_info(self, url, download=True, ie_key=None, extra_info={}):
|
||||||
'''
|
'''
|
||||||
Returns a list with a dictionary for each video we find.
|
Returns a list with a dictionary for each video we find.
|
||||||
@ -344,17 +350,17 @@ class YoutubeDL(object):
|
|||||||
break
|
break
|
||||||
if isinstance(ie_result, list):
|
if isinstance(ie_result, list):
|
||||||
# Backwards compatibility: old IE result format
|
# Backwards compatibility: old IE result format
|
||||||
for result in ie_result:
|
|
||||||
result.update(extra_info)
|
|
||||||
ie_result = {
|
ie_result = {
|
||||||
'_type': 'compat_list',
|
'_type': 'compat_list',
|
||||||
'entries': ie_result,
|
'entries': ie_result,
|
||||||
}
|
}
|
||||||
else:
|
self.add_extra_info(ie_result,
|
||||||
ie_result.update(extra_info)
|
{
|
||||||
if 'extractor' not in ie_result:
|
'extractor': ie.IE_NAME,
|
||||||
ie_result['extractor'] = ie.IE_NAME
|
'webpage_url': url,
|
||||||
return self.process_ie_result(ie_result, download=download)
|
'extractor_key': ie.ie_key(),
|
||||||
|
})
|
||||||
|
return self.process_ie_result(ie_result, download, extra_info)
|
||||||
except ExtractorError as de: # An error we somewhat expected
|
except ExtractorError as de: # An error we somewhat expected
|
||||||
self.report_error(compat_str(de), de.format_traceback())
|
self.report_error(compat_str(de), de.format_traceback())
|
||||||
break
|
break
|
||||||
@ -378,7 +384,7 @@ class YoutubeDL(object):
|
|||||||
|
|
||||||
result_type = ie_result.get('_type', 'video') # If not given we suppose it's a video, support the default old system
|
result_type = ie_result.get('_type', 'video') # If not given we suppose it's a video, support the default old system
|
||||||
if result_type == 'video':
|
if result_type == 'video':
|
||||||
ie_result.update(extra_info)
|
self.add_extra_info(ie_result, extra_info)
|
||||||
return self.process_video_result(ie_result)
|
return self.process_video_result(ie_result)
|
||||||
elif result_type == 'url':
|
elif result_type == 'url':
|
||||||
# We have to add extra_info to the results because it may be
|
# We have to add extra_info to the results because it may be
|
||||||
@ -388,6 +394,7 @@ class YoutubeDL(object):
|
|||||||
ie_key=ie_result.get('ie_key'),
|
ie_key=ie_result.get('ie_key'),
|
||||||
extra_info=extra_info)
|
extra_info=extra_info)
|
||||||
elif result_type == 'playlist':
|
elif result_type == 'playlist':
|
||||||
|
self.add_extra_info(ie_result, extra_info)
|
||||||
# We process each entry in the playlist
|
# We process each entry in the playlist
|
||||||
playlist = ie_result.get('title', None) or ie_result.get('id', None)
|
playlist = ie_result.get('title', None) or ie_result.get('id', None)
|
||||||
self.to_screen(u'[download] Downloading playlist: %s' % playlist)
|
self.to_screen(u'[download] Downloading playlist: %s' % playlist)
|
||||||
@ -413,12 +420,10 @@ class YoutubeDL(object):
|
|||||||
extra = {
|
extra = {
|
||||||
'playlist': playlist,
|
'playlist': playlist,
|
||||||
'playlist_index': i + playliststart,
|
'playlist_index': i + playliststart,
|
||||||
|
'extractor': ie_result['extractor'],
|
||||||
|
'webpage_url': ie_result['webpage_url'],
|
||||||
|
'extractor_key': ie_result['extractor_key'],
|
||||||
}
|
}
|
||||||
if not 'extractor' in entry:
|
|
||||||
# We set the extractor, if it's an url it will be set then to
|
|
||||||
# the new extractor, but if it's already a video we must make
|
|
||||||
# sure it's present: see issue #877
|
|
||||||
entry['extractor'] = ie_result['extractor']
|
|
||||||
entry_result = self.process_ie_result(entry,
|
entry_result = self.process_ie_result(entry,
|
||||||
download=download,
|
download=download,
|
||||||
extra_info=extra)
|
extra_info=extra)
|
||||||
@ -427,10 +432,15 @@ class YoutubeDL(object):
|
|||||||
return ie_result
|
return ie_result
|
||||||
elif result_type == 'compat_list':
|
elif result_type == 'compat_list':
|
||||||
def _fixup(r):
|
def _fixup(r):
|
||||||
r.setdefault('extractor', ie_result['extractor'])
|
self.add_extra_info(r,
|
||||||
|
{
|
||||||
|
'extractor': ie_result['extractor'],
|
||||||
|
'webpage_url': ie_result['webpage_url'],
|
||||||
|
'extractor_key': ie_result['extractor_key'],
|
||||||
|
})
|
||||||
return r
|
return r
|
||||||
ie_result['entries'] = [
|
ie_result['entries'] = [
|
||||||
self.process_ie_result(_fixup(r), download=download)
|
self.process_ie_result(_fixup(r), download, extra_info)
|
||||||
for r in ie_result['entries']
|
for r in ie_result['entries']
|
||||||
]
|
]
|
||||||
return ie_result
|
return ie_result
|
||||||
@ -772,7 +782,7 @@ class YoutubeDL(object):
|
|||||||
|
|
||||||
def list_formats(self, info_dict):
|
def list_formats(self, info_dict):
|
||||||
def line(format):
|
def line(format):
|
||||||
return (u'%-15s%-10s%-12s%s' % (
|
return (u'%-20s%-10s%-12s%s' % (
|
||||||
format['format_id'],
|
format['format_id'],
|
||||||
format['ext'],
|
format['ext'],
|
||||||
self.format_resolution(format),
|
self.format_resolution(format),
|
||||||
|
@ -349,7 +349,7 @@ def parseOpts(overrideArguments=None):
|
|||||||
'for example with -o \'/my/downloads/%(uploader)s/%(title)s-%(id)s.%(ext)s\' .'))
|
'for example with -o \'/my/downloads/%(uploader)s/%(title)s-%(id)s.%(ext)s\' .'))
|
||||||
filesystem.add_option('--autonumber-size',
|
filesystem.add_option('--autonumber-size',
|
||||||
dest='autonumber_size', metavar='NUMBER',
|
dest='autonumber_size', metavar='NUMBER',
|
||||||
help='Specifies the number of digits in %(autonumber)s when it is present in output filename template or --autonumber option is given')
|
help='Specifies the number of digits in %(autonumber)s when it is present in output filename template or --auto-number option is given')
|
||||||
filesystem.add_option('--restrict-filenames',
|
filesystem.add_option('--restrict-filenames',
|
||||||
action='store_true', dest='restrictfilenames',
|
action='store_true', dest='restrictfilenames',
|
||||||
help='Restrict filenames to only ASCII characters, and avoid "&" and spaces in filenames', default=False)
|
help='Restrict filenames to only ASCII characters, and avoid "&" and spaces in filenames', default=False)
|
||||||
@ -358,7 +358,7 @@ def parseOpts(overrideArguments=None):
|
|||||||
filesystem.add_option('-w', '--no-overwrites',
|
filesystem.add_option('-w', '--no-overwrites',
|
||||||
action='store_true', dest='nooverwrites', help='do not overwrite files', default=False)
|
action='store_true', dest='nooverwrites', help='do not overwrite files', default=False)
|
||||||
filesystem.add_option('-c', '--continue',
|
filesystem.add_option('-c', '--continue',
|
||||||
action='store_true', dest='continue_dl', help='resume partially downloaded files', default=True)
|
action='store_true', dest='continue_dl', help='force resume of partially downloaded files. By default, youtube-dl will resume downloads if possible.', default=True)
|
||||||
filesystem.add_option('--no-continue',
|
filesystem.add_option('--no-continue',
|
||||||
action='store_false', dest='continue_dl',
|
action='store_false', dest='continue_dl',
|
||||||
help='do not resume partially downloaded files (restart from beginning)')
|
help='do not resume partially downloaded files (restart from beginning)')
|
||||||
|
@ -38,8 +38,10 @@ from .defense import DefenseGouvFrIE
|
|||||||
from .ebaumsworld import EbaumsWorldIE
|
from .ebaumsworld import EbaumsWorldIE
|
||||||
from .ehow import EHowIE
|
from .ehow import EHowIE
|
||||||
from .eighttracks import EightTracksIE
|
from .eighttracks import EightTracksIE
|
||||||
|
from .eitb import EitbIE
|
||||||
from .escapist import EscapistIE
|
from .escapist import EscapistIE
|
||||||
from .exfm import ExfmIE
|
from .exfm import ExfmIE
|
||||||
|
from .extremetube import ExtremeTubeIE
|
||||||
from .facebook import FacebookIE
|
from .facebook import FacebookIE
|
||||||
from .faz import FazIE
|
from .faz import FazIE
|
||||||
from .fktv import (
|
from .fktv import (
|
||||||
@ -82,6 +84,7 @@ from .metacafe import MetacafeIE
|
|||||||
from .metacritic import MetacriticIE
|
from .metacritic import MetacriticIE
|
||||||
from .mit import TechTVMITIE, MITIE
|
from .mit import TechTVMITIE, MITIE
|
||||||
from .mixcloud import MixcloudIE
|
from .mixcloud import MixcloudIE
|
||||||
|
from .mofosex import MofosexIE
|
||||||
from .mtv import MTVIE
|
from .mtv import MTVIE
|
||||||
from .muzu import MuzuTVIE
|
from .muzu import MuzuTVIE
|
||||||
from .myspace import MySpaceIE
|
from .myspace import MySpaceIE
|
||||||
@ -113,6 +116,7 @@ from .slideshare import SlideshareIE
|
|||||||
from .sohu import SohuIE
|
from .sohu import SohuIE
|
||||||
from .soundcloud import SoundcloudIE, SoundcloudSetIE, SoundcloudUserIE
|
from .soundcloud import SoundcloudIE, SoundcloudSetIE, SoundcloudUserIE
|
||||||
from .southparkstudios import SouthParkStudiosIE
|
from .southparkstudios import SouthParkStudiosIE
|
||||||
|
from .space import SpaceIE
|
||||||
from .spankwire import SpankwireIE
|
from .spankwire import SpankwireIE
|
||||||
from .spiegel import SpiegelIE
|
from .spiegel import SpiegelIE
|
||||||
from .stanfordoc import StanfordOpenClassroomIE
|
from .stanfordoc import StanfordOpenClassroomIE
|
||||||
@ -152,6 +156,7 @@ from .worldstarhiphop import WorldStarHipHopIE
|
|||||||
from .xhamster import XHamsterIE
|
from .xhamster import XHamsterIE
|
||||||
from .xnxx import XNXXIE
|
from .xnxx import XNXXIE
|
||||||
from .xvideos import XVideosIE
|
from .xvideos import XVideosIE
|
||||||
|
from .xtube import XTubeIE
|
||||||
from .yahoo import YahooIE, YahooSearchIE
|
from .yahoo import YahooIE, YahooSearchIE
|
||||||
from .youjizz import YouJizzIE
|
from .youjizz import YouJizzIE
|
||||||
from .youku import YoukuIE
|
from .youku import YoukuIE
|
||||||
@ -160,6 +165,7 @@ from .youtube import (
|
|||||||
YoutubeIE,
|
YoutubeIE,
|
||||||
YoutubePlaylistIE,
|
YoutubePlaylistIE,
|
||||||
YoutubeSearchIE,
|
YoutubeSearchIE,
|
||||||
|
YoutubeSearchDateIE,
|
||||||
YoutubeUserIE,
|
YoutubeUserIE,
|
||||||
YoutubeChannelIE,
|
YoutubeChannelIE,
|
||||||
YoutubeShowIE,
|
YoutubeShowIE,
|
||||||
|
@ -10,6 +10,7 @@ from ..utils import (
|
|||||||
unified_strdate,
|
unified_strdate,
|
||||||
determine_ext,
|
determine_ext,
|
||||||
get_element_by_id,
|
get_element_by_id,
|
||||||
|
compat_str,
|
||||||
)
|
)
|
||||||
|
|
||||||
# There are different sources of video in arte.tv, the extraction process
|
# There are different sources of video in arte.tv, the extraction process
|
||||||
@ -181,20 +182,30 @@ class ArteTVPlus7IE(InfoExtractor):
|
|||||||
formats = all_formats
|
formats = all_formats
|
||||||
else:
|
else:
|
||||||
raise ExtractorError(u'The formats list is empty')
|
raise ExtractorError(u'The formats list is empty')
|
||||||
# We order the formats by quality
|
|
||||||
if re.match(r'[A-Z]Q', formats[0]['quality']) is not None:
|
if re.match(r'[A-Z]Q', formats[0]['quality']) is not None:
|
||||||
sort_key = lambda f: ['HQ', 'MQ', 'EQ', 'SQ'].index(f['quality'])
|
def sort_key(f):
|
||||||
|
return ['HQ', 'MQ', 'EQ', 'SQ'].index(f['quality'])
|
||||||
else:
|
else:
|
||||||
sort_key = lambda f: int(f.get('height',-1))
|
def sort_key(f):
|
||||||
|
return (
|
||||||
|
# Sort first by quality
|
||||||
|
int(f.get('height',-1)),
|
||||||
|
int(f.get('bitrate',-1)),
|
||||||
|
# The original version with subtitles has lower relevance
|
||||||
|
re.match(r'VO-ST(F|A)', f.get('versionCode', '')) is None,
|
||||||
|
# The version with sourds/mal subtitles has also lower relevance
|
||||||
|
re.match(r'VO?(F|A)-STM\1', f.get('versionCode', '')) is None,
|
||||||
|
)
|
||||||
formats = sorted(formats, key=sort_key)
|
formats = sorted(formats, key=sort_key)
|
||||||
# Prefer videos without subtitles in the same language
|
|
||||||
formats = sorted(formats, key=lambda f: re.match(r'VO(F|A)-STM\1', f.get('versionCode', '')) is None)
|
|
||||||
# Pick the best quality
|
|
||||||
def _format(format_info):
|
def _format(format_info):
|
||||||
quality = format_info['quality']
|
quality = ''
|
||||||
m_quality = re.match(r'\w*? - (\d*)p', quality)
|
height = format_info.get('height')
|
||||||
if m_quality is not None:
|
if height is not None:
|
||||||
quality = m_quality.group(1)
|
quality = compat_str(height)
|
||||||
|
bitrate = format_info.get('bitrate')
|
||||||
|
if bitrate is not None:
|
||||||
|
quality += '-%d' % bitrate
|
||||||
if format_info.get('versionCode') is not None:
|
if format_info.get('versionCode') is not None:
|
||||||
format_id = u'%s-%s' % (quality, format_info['versionCode'])
|
format_id = u'%s-%s' % (quality, format_info['versionCode'])
|
||||||
else:
|
else:
|
||||||
@ -203,7 +214,7 @@ class ArteTVPlus7IE(InfoExtractor):
|
|||||||
'format_id': format_id,
|
'format_id': format_id,
|
||||||
'format_note': format_info.get('versionLibelle'),
|
'format_note': format_info.get('versionLibelle'),
|
||||||
'width': format_info.get('width'),
|
'width': format_info.get('width'),
|
||||||
'height': format_info.get('height'),
|
'height': height,
|
||||||
}
|
}
|
||||||
if format_info['mediaType'] == u'rtmp':
|
if format_info['mediaType'] == u'rtmp':
|
||||||
info['url'] = format_info['streamer']
|
info['url'] = format_info['streamer']
|
||||||
|
@ -9,10 +9,13 @@ from ..utils import (
|
|||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
find_xpath_attr,
|
find_xpath_attr,
|
||||||
compat_urlparse,
|
compat_urlparse,
|
||||||
|
compat_str,
|
||||||
|
compat_urllib_request,
|
||||||
|
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class BrightcoveIE(InfoExtractor):
|
class BrightcoveIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://.*brightcove\.com/(services|viewer).*\?(?P<query>.*)'
|
_VALID_URL = r'https?://.*brightcove\.com/(services|viewer).*\?(?P<query>.*)'
|
||||||
_FEDERATED_URL_TEMPLATE = 'http://c.brightcove.com/services/viewer/htmlFederated?%s'
|
_FEDERATED_URL_TEMPLATE = 'http://c.brightcove.com/services/viewer/htmlFederated?%s'
|
||||||
@ -41,6 +44,17 @@ class BrightcoveIE(InfoExtractor):
|
|||||||
u'uploader': u'Oracle',
|
u'uploader': u'Oracle',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
# From http://mashable.com/2013/10/26/thermoelectric-bracelet-lets-you-control-your-body-temperature/
|
||||||
|
u'url': u'http://c.brightcove.com/services/viewer/federated_f9?&playerID=1265504713001&publisherID=AQ%7E%7E%2CAAABBzUwv1E%7E%2CxP-xFHVUstiMFlNYfvF4G9yFnNaqCw_9&videoID=2750934548001',
|
||||||
|
u'info_dict': {
|
||||||
|
u'id': u'2750934548001',
|
||||||
|
u'ext': u'mp4',
|
||||||
|
u'title': u'This Bracelet Acts as a Personal Thermostat',
|
||||||
|
u'description': u'md5:547b78c64f4112766ccf4e151c20b6a0',
|
||||||
|
u'uploader': u'Mashable',
|
||||||
|
},
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -68,24 +82,48 @@ class BrightcoveIE(InfoExtractor):
|
|||||||
videoPlayer = find_xpath_attr(object_doc, './param', 'name', '@videoPlayer')
|
videoPlayer = find_xpath_attr(object_doc, './param', 'name', '@videoPlayer')
|
||||||
if videoPlayer is not None:
|
if videoPlayer is not None:
|
||||||
params['@videoPlayer'] = videoPlayer.attrib['value']
|
params['@videoPlayer'] = videoPlayer.attrib['value']
|
||||||
|
linkBase = find_xpath_attr(object_doc, './param', 'name', 'linkBaseURL')
|
||||||
|
if linkBase is not None:
|
||||||
|
params['linkBaseURL'] = linkBase.attrib['value']
|
||||||
data = compat_urllib_parse.urlencode(params)
|
data = compat_urllib_parse.urlencode(params)
|
||||||
return cls._FEDERATED_URL_TEMPLATE % data
|
return cls._FEDERATED_URL_TEMPLATE % data
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _extract_brightcove_url(cls, webpage):
|
||||||
|
"""Try to extract the brightcove url from the wepbage, returns None
|
||||||
|
if it can't be found
|
||||||
|
"""
|
||||||
|
m_brightcove = re.search(
|
||||||
|
r'<object[^>]+?class=([\'"])[^>]*?BrightcoveExperience.*?\1.+?</object>',
|
||||||
|
webpage, re.DOTALL)
|
||||||
|
if m_brightcove is not None:
|
||||||
|
return cls._build_brighcove_url(m_brightcove.group())
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
|
# Change the 'videoId' and others field to '@videoPlayer'
|
||||||
|
url = re.sub(r'(?<=[?&])(videoI(d|D)|bctid)', '%40videoPlayer', url)
|
||||||
|
# Change bckey (used by bcove.me urls) to playerKey
|
||||||
|
url = re.sub(r'(?<=[?&])bckey', 'playerKey', url)
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
query_str = mobj.group('query')
|
query_str = mobj.group('query')
|
||||||
query = compat_urlparse.parse_qs(query_str)
|
query = compat_urlparse.parse_qs(query_str)
|
||||||
|
|
||||||
videoPlayer = query.get('@videoPlayer')
|
videoPlayer = query.get('@videoPlayer')
|
||||||
if videoPlayer:
|
if videoPlayer:
|
||||||
return self._get_video_info(videoPlayer[0], query_str)
|
return self._get_video_info(videoPlayer[0], query_str, query)
|
||||||
else:
|
else:
|
||||||
player_key = query['playerKey']
|
player_key = query['playerKey']
|
||||||
return self._get_playlist_info(player_key[0])
|
return self._get_playlist_info(player_key[0])
|
||||||
|
|
||||||
def _get_video_info(self, video_id, query):
|
def _get_video_info(self, video_id, query_str, query):
|
||||||
request_url = self._FEDERATED_URL_TEMPLATE % query
|
request_url = self._FEDERATED_URL_TEMPLATE % query_str
|
||||||
webpage = self._download_webpage(request_url, video_id)
|
req = compat_urllib_request.Request(request_url)
|
||||||
|
linkBase = query.get('linkBaseURL')
|
||||||
|
if linkBase is not None:
|
||||||
|
req.add_header('Referer', linkBase[0])
|
||||||
|
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')
|
||||||
@ -109,7 +147,7 @@ class BrightcoveIE(InfoExtractor):
|
|||||||
|
|
||||||
def _extract_video_info(self, video_info):
|
def _extract_video_info(self, video_info):
|
||||||
info = {
|
info = {
|
||||||
'id': video_info['id'],
|
'id': compat_str(video_info['id']),
|
||||||
'title': video_info['displayName'],
|
'title': video_info['displayName'],
|
||||||
'description': video_info.get('shortDescription'),
|
'description': video_info.get('shortDescription'),
|
||||||
'thumbnail': video_info.get('videoStillURL') or video_info.get('thumbnailURL'),
|
'thumbnail': video_info.get('videoStillURL') or video_info.get('thumbnailURL'),
|
||||||
@ -119,10 +157,11 @@ 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'])
|
renditions = sorted(renditions, key=lambda r: r['size'])
|
||||||
best_format = renditions[-1]
|
info['formats'] = [{
|
||||||
info.update({
|
'url': rend['defaultURL'],
|
||||||
'url': best_format['defaultURL'],
|
'height': rend.get('frameHeight'),
|
||||||
})
|
'width': rend.get('frameWidth'),
|
||||||
|
} for rend in renditions]
|
||||||
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'],
|
||||||
|
@ -6,7 +6,7 @@ from .common import InfoExtractor
|
|||||||
|
|
||||||
class Canalc2IE(InfoExtractor):
|
class Canalc2IE(InfoExtractor):
|
||||||
IE_NAME = 'canalc2.tv'
|
IE_NAME = 'canalc2.tv'
|
||||||
_VALID_URL = r'http://.*?\.canalc2\.tv/video\.asp\?idVideo=(\d+)&voir=oui'
|
_VALID_URL = r'http://.*?\.canalc2\.tv/video\.asp\?.*?idVideo=(?P<id>\d+)'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
u'url': u'http://www.canalc2.tv/video.asp?idVideo=12163&voir=oui',
|
u'url': u'http://www.canalc2.tv/video.asp?idVideo=12163&voir=oui',
|
||||||
@ -18,7 +18,9 @@ class Canalc2IE(InfoExtractor):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = re.match(self._VALID_URL, url).group(1)
|
video_id = re.match(self._VALID_URL, url).group('id')
|
||||||
|
# We need to set the voir field for getting the file name
|
||||||
|
url = 'http://www.canalc2.tv/video.asp?idVideo=%s&voir=oui' % video_id
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
file_name = self._search_regex(
|
file_name = self._search_regex(
|
||||||
r"so\.addVariable\('file','(.*?)'\);",
|
r"so\.addVariable\('file','(.*?)'\);",
|
||||||
|
@ -41,7 +41,7 @@ class CinemassacreIE(InfoExtractor):
|
|||||||
webpage_url = u'http://' + mobj.group('url')
|
webpage_url = u'http://' + mobj.group('url')
|
||||||
webpage = self._download_webpage(webpage_url, None) # Don't know video id yet
|
webpage = self._download_webpage(webpage_url, None) # Don't know video id yet
|
||||||
video_date = mobj.group('date_Y') + mobj.group('date_m') + mobj.group('date_d')
|
video_date = mobj.group('date_Y') + mobj.group('date_m') + mobj.group('date_d')
|
||||||
mobj = re.search(r'src="(?P<embed_url>http://player\.screenwavemedia\.com/play/(?:embed|player)\.php\?id=(?:Cinemassacre-)?(?P<video_id>.+?))"', webpage)
|
mobj = re.search(r'src="(?P<embed_url>http://player\.screenwavemedia\.com/play/[a-zA-Z]+\.php\?id=(?:Cinemassacre-)?(?P<video_id>.+?))"', webpage)
|
||||||
if not mobj:
|
if not mobj:
|
||||||
raise ExtractorError(u'Can\'t extract embed url and video id')
|
raise ExtractorError(u'Can\'t extract embed url and video id')
|
||||||
playerdata_url = mobj.group(u'embed_url')
|
playerdata_url = mobj.group(u'embed_url')
|
||||||
@ -65,6 +65,7 @@ class CinemassacreIE(InfoExtractor):
|
|||||||
{
|
{
|
||||||
'url': url,
|
'url': url,
|
||||||
'play_path': 'mp4:' + sd_file,
|
'play_path': 'mp4:' + sd_file,
|
||||||
|
'rtmp_live': True, # workaround
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'format': 'sd',
|
'format': 'sd',
|
||||||
'format_id': 'sd',
|
'format_id': 'sd',
|
||||||
@ -72,6 +73,7 @@ class CinemassacreIE(InfoExtractor):
|
|||||||
{
|
{
|
||||||
'url': url,
|
'url': url,
|
||||||
'play_path': 'mp4:' + hd_file,
|
'play_path': 'mp4:' + hd_file,
|
||||||
|
'rtmp_live': True, # workaround
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'format': 'hd',
|
'format': 'hd',
|
||||||
'format_id': 'hd',
|
'format_id': 'hd',
|
||||||
|
@ -6,7 +6,7 @@ from ..utils import determine_ext
|
|||||||
|
|
||||||
|
|
||||||
class CNNIE(InfoExtractor):
|
class CNNIE(InfoExtractor):
|
||||||
_VALID_URL = r'''(?x)https?://(edition\.)?cnn\.com/video/(data/.+?|\?)/
|
_VALID_URL = r'''(?x)https?://((edition|www)\.)?cnn\.com/video/(data/.+?|\?)/
|
||||||
(?P<path>.+?/(?P<title>[^/]+?)(?:\.cnn|(?=&)))'''
|
(?P<path>.+?/(?P<title>[^/]+?)(?:\.cnn|(?=&)))'''
|
||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
|
@ -71,6 +71,9 @@ class InfoExtractor(object):
|
|||||||
("3D" or "DASH video")
|
("3D" or "DASH video")
|
||||||
* width Width of the video, if known
|
* width Width of the video, if known
|
||||||
* height Height of the video, if known
|
* height Height of the video, if known
|
||||||
|
webpage_url: The url to the video webpage, if given to youtube-dl it
|
||||||
|
should allow to get the same result again. (It will be set
|
||||||
|
by YoutubeDL if it's missing)
|
||||||
|
|
||||||
Unless mentioned otherwise, the fields should be Unicode strings.
|
Unless mentioned otherwise, the fields should be Unicode strings.
|
||||||
|
|
||||||
@ -319,7 +322,9 @@ class InfoExtractor(object):
|
|||||||
if name is None:
|
if name is None:
|
||||||
name = 'OpenGraph %s' % prop
|
name = 'OpenGraph %s' % prop
|
||||||
escaped = self._search_regex(self._og_regex(prop), html, name, flags=re.DOTALL, **kargs)
|
escaped = self._search_regex(self._og_regex(prop), html, name, flags=re.DOTALL, **kargs)
|
||||||
|
if not escaped is None:
|
||||||
return unescapeHTML(escaped)
|
return unescapeHTML(escaped)
|
||||||
|
return None
|
||||||
|
|
||||||
def _og_search_thumbnail(self, html, **kargs):
|
def _og_search_thumbnail(self, html, **kargs):
|
||||||
return self._og_search_property('image', html, u'thumbnail url', fatal=False, **kargs)
|
return self._og_search_property('image', html, u'thumbnail url', fatal=False, **kargs)
|
||||||
|
@ -141,9 +141,9 @@ class DailymotionIE(DailymotionBaseInfoExtractor, SubtitlesInfoExtractor):
|
|||||||
raise ExtractorError(u'Unable to extract video URL')
|
raise ExtractorError(u'Unable to extract video URL')
|
||||||
|
|
||||||
# subtitles
|
# subtitles
|
||||||
video_subtitles = self.extract_subtitles(video_id)
|
video_subtitles = self.extract_subtitles(video_id, webpage)
|
||||||
if self._downloader.params.get('listsubtitles', False):
|
if self._downloader.params.get('listsubtitles', False):
|
||||||
self._list_available_subtitles(video_id)
|
self._list_available_subtitles(video_id, webpage)
|
||||||
return
|
return
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -157,7 +157,7 @@ class DailymotionIE(DailymotionBaseInfoExtractor, SubtitlesInfoExtractor):
|
|||||||
'age_limit': age_limit,
|
'age_limit': age_limit,
|
||||||
}
|
}
|
||||||
|
|
||||||
def _get_available_subtitles(self, video_id):
|
def _get_available_subtitles(self, video_id, webpage):
|
||||||
try:
|
try:
|
||||||
sub_list = self._download_webpage(
|
sub_list = self._download_webpage(
|
||||||
'https://api.dailymotion.com/video/%s/subtitles?fields=id,language,url' % video_id,
|
'https://api.dailymotion.com/video/%s/subtitles?fields=id,language,url' % video_id,
|
||||||
|
37
youtube_dl/extractor/eitb.py
Normal file
37
youtube_dl/extractor/eitb.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# encoding: utf-8
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from .brightcove import BrightcoveIE
|
||||||
|
from ..utils import ExtractorError
|
||||||
|
|
||||||
|
|
||||||
|
class EitbIE(InfoExtractor):
|
||||||
|
IE_NAME = u'eitb.tv'
|
||||||
|
_VALID_URL = r'https?://www\.eitb\.tv/(eu/bideoa|es/video)/[^/]+/(?P<playlist_id>\d+)/(?P<chapter_id>\d+)'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
u'add_ie': ['Brightcove'],
|
||||||
|
u'url': u'http://www.eitb.tv/es/video/60-minutos-60-minutos-2013-2014/2677100210001/2743577154001/lasa-y-zabala-30-anos/',
|
||||||
|
u'md5': u'edf4436247185adee3ea18ce64c47998',
|
||||||
|
u'info_dict': {
|
||||||
|
u'id': u'2743577154001',
|
||||||
|
u'ext': u'mp4',
|
||||||
|
u'title': u'60 minutos (Lasa y Zabala, 30 años)',
|
||||||
|
# All videos from eitb has this description in the brightcove info
|
||||||
|
u'description': u'.',
|
||||||
|
u'uploader': u'Euskal Telebista',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
chapter_id = mobj.group('chapter_id')
|
||||||
|
webpage = self._download_webpage(url, chapter_id)
|
||||||
|
bc_url = BrightcoveIE._extract_brightcove_url(webpage)
|
||||||
|
if bc_url is None:
|
||||||
|
raise ExtractorError(u'Could not extract the Brightcove url')
|
||||||
|
# The BrightcoveExperience object doesn't contain the video id, we set
|
||||||
|
# it manually
|
||||||
|
bc_url += '&%40videoPlayer={0}'.format(chapter_id)
|
||||||
|
return self.url_result(bc_url, BrightcoveIE.ie_key())
|
50
youtube_dl/extractor/extremetube.py
Normal file
50
youtube_dl/extractor/extremetube.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
compat_urllib_parse_urlparse,
|
||||||
|
compat_urllib_request,
|
||||||
|
compat_urllib_parse,
|
||||||
|
)
|
||||||
|
|
||||||
|
class ExtremeTubeIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'^(?:https?://)?(?:www\.)?(?P<url>extremetube\.com/video/.+?(?P<videoid>[0-9]+))(?:[/?&]|$)'
|
||||||
|
_TEST = {
|
||||||
|
u'url': u'http://www.extremetube.com/video/music-video-14-british-euro-brit-european-cumshots-swallow-652431',
|
||||||
|
u'file': u'652431.mp4',
|
||||||
|
u'md5': u'1fb9228f5e3332ec8c057d6ac36f33e0',
|
||||||
|
u'info_dict': {
|
||||||
|
u"title": u"Music Video 14 british euro brit european cumshots swallow",
|
||||||
|
u"uploader": u"unknown",
|
||||||
|
u"age_limit": 18,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('videoid')
|
||||||
|
url = 'http://www.' + mobj.group('url')
|
||||||
|
|
||||||
|
req = compat_urllib_request.Request(url)
|
||||||
|
req.add_header('Cookie', 'age_verified=1')
|
||||||
|
webpage = self._download_webpage(req, video_id)
|
||||||
|
|
||||||
|
video_title = self._html_search_regex(r'<h1 [^>]*?title="([^"]+)"[^>]*>\1<', webpage, u'title')
|
||||||
|
uploader = self._html_search_regex(r'>Posted by:(?=<)(?:\s|<[^>]*>)*(.+?)\|', webpage, u'uploader', fatal=False)
|
||||||
|
video_url = compat_urllib_parse.unquote(self._html_search_regex(r'video_url=(.+?)&', webpage, u'video_url'))
|
||||||
|
path = compat_urllib_parse_urlparse(video_url).path
|
||||||
|
extension = os.path.splitext(path)[1][1:]
|
||||||
|
format = path.split('/')[5].split('_')[:2]
|
||||||
|
format = "-".join(format)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': video_title,
|
||||||
|
'uploader': uploader,
|
||||||
|
'url': video_url,
|
||||||
|
'ext': extension,
|
||||||
|
'format': format,
|
||||||
|
'format_id': format,
|
||||||
|
'age_limit': 18,
|
||||||
|
}
|
@ -33,6 +33,7 @@ class GenericIE(InfoExtractor):
|
|||||||
},
|
},
|
||||||
# embedded vimeo video
|
# embedded vimeo video
|
||||||
{
|
{
|
||||||
|
u'add_ie': ['Vimeo'],
|
||||||
u'url': u'http://skillsmatter.com/podcast/home/move-semanticsperfect-forwarding-and-rvalue-references',
|
u'url': u'http://skillsmatter.com/podcast/home/move-semanticsperfect-forwarding-and-rvalue-references',
|
||||||
u'file': u'22444065.mp4',
|
u'file': u'22444065.mp4',
|
||||||
u'md5': u'2903896e23df39722c33f015af0666e2',
|
u'md5': u'2903896e23df39722c33f015af0666e2',
|
||||||
@ -44,6 +45,7 @@ class GenericIE(InfoExtractor):
|
|||||||
},
|
},
|
||||||
# bandcamp page with custom domain
|
# bandcamp page with custom domain
|
||||||
{
|
{
|
||||||
|
u'add_ie': ['Bandcamp'],
|
||||||
u'url': u'http://bronyrock.com/track/the-pony-mash',
|
u'url': u'http://bronyrock.com/track/the-pony-mash',
|
||||||
u'file': u'3235767654.mp3',
|
u'file': u'3235767654.mp3',
|
||||||
u'info_dict': {
|
u'info_dict': {
|
||||||
@ -52,6 +54,23 @@ class GenericIE(InfoExtractor):
|
|||||||
},
|
},
|
||||||
u'skip': u'There is a limit of 200 free downloads / month for the test song',
|
u'skip': u'There is a limit of 200 free downloads / month for the test song',
|
||||||
},
|
},
|
||||||
|
# embedded brightcove video
|
||||||
|
# it also tests brightcove videos that need to set the 'Referer' in the
|
||||||
|
# http requests
|
||||||
|
{
|
||||||
|
u'add_ie': ['Brightcove'],
|
||||||
|
u'url': u'http://www.bfmtv.com/video/bfmbusiness/cours-bourse/cours-bourse-l-analyse-technique-154522/',
|
||||||
|
u'info_dict': {
|
||||||
|
u'id': u'2765128793001',
|
||||||
|
u'ext': u'mp4',
|
||||||
|
u'title': u'Le cours de bourse : l’analyse technique',
|
||||||
|
u'description': u'md5:7e9ad046e968cb2d1114004aba466fd9',
|
||||||
|
u'uploader': u'BFM BUSINESS',
|
||||||
|
},
|
||||||
|
u'params': {
|
||||||
|
u'skip_download': True,
|
||||||
|
},
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
def report_download_webpage(self, video_id):
|
def report_download_webpage(self, video_id):
|
||||||
@ -144,10 +163,9 @@ class GenericIE(InfoExtractor):
|
|||||||
|
|
||||||
self.report_extraction(video_id)
|
self.report_extraction(video_id)
|
||||||
# Look for BrightCove:
|
# Look for BrightCove:
|
||||||
m_brightcove = re.search(r'<object[^>]+?class=([\'"])[^>]*?BrightcoveExperience.*?\1.+?</object>', webpage, re.DOTALL)
|
bc_url = BrightcoveIE._extract_brightcove_url(webpage)
|
||||||
if m_brightcove is not None:
|
if bc_url is not None:
|
||||||
self.to_screen(u'Brightcove video detected.')
|
self.to_screen(u'Brightcove video detected.')
|
||||||
bc_url = BrightcoveIE._build_brighcove_url(m_brightcove.group())
|
|
||||||
return self.url_result(bc_url, 'Brightcove')
|
return self.url_result(bc_url, 'Brightcove')
|
||||||
|
|
||||||
# Look for embedded Vimeo player
|
# Look for embedded Vimeo player
|
||||||
@ -160,9 +178,9 @@ class GenericIE(InfoExtractor):
|
|||||||
|
|
||||||
# Look for embedded YouTube player
|
# Look for embedded YouTube player
|
||||||
mobj = re.search(
|
mobj = re.search(
|
||||||
r'<iframe[^>]+?src="(https?://(?:www\.)?youtube.com/embed/.+?)"', webpage)
|
r'<iframe[^>]+?src=(["\'])(?P<url>https?://(?:www\.)?youtube.com/embed/.+?)\1', webpage)
|
||||||
if mobj:
|
if mobj:
|
||||||
surl = unescapeHTML(mobj.group(1))
|
surl = unescapeHTML(mobj.group(u'url'))
|
||||||
return self.url_result(surl, 'Youtube')
|
return self.url_result(surl, 'Youtube')
|
||||||
|
|
||||||
# Look for Bandcamp pages with custom domain
|
# Look for Bandcamp pages with custom domain
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import re
|
import re
|
||||||
|
import hashlib
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import determine_ext
|
from ..utils import determine_ext
|
||||||
|
|
||||||
|
_md5 = lambda s: hashlib.md5(s.encode('utf-8')).hexdigest()
|
||||||
|
|
||||||
class KankanIE(InfoExtractor):
|
class KankanIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:.*?\.)?kankan\.com/.+?/(?P<id>\d+)\.shtml'
|
_VALID_URL = r'https?://(?:.*?\.)?kankan\.com/.+?/(?P<id>\d+)\.shtml'
|
||||||
@ -30,7 +32,10 @@ class KankanIE(InfoExtractor):
|
|||||||
video_id, u'Downloading video url info')
|
video_id, u'Downloading video url info')
|
||||||
ip = self._search_regex(r'ip:"(.+?)"', video_info_page, u'video url ip')
|
ip = self._search_regex(r'ip:"(.+?)"', video_info_page, u'video url ip')
|
||||||
path = self._search_regex(r'path:"(.+?)"', video_info_page, u'video url path')
|
path = self._search_regex(r'path:"(.+?)"', video_info_page, u'video url path')
|
||||||
video_url = 'http://%s%s' % (ip, path)
|
param1 = self._search_regex(r'param1:(\d+)', video_info_page, u'param1')
|
||||||
|
param2 = self._search_regex(r'param2:(\d+)', video_info_page, u'param2')
|
||||||
|
key = _md5('xl_mp43651' + param1 + param2)
|
||||||
|
video_url = 'http://%s%s?key=%s&key1=%s' % (ip, path, key, param2)
|
||||||
|
|
||||||
return {'id': video_id,
|
return {'id': video_id,
|
||||||
'title': title,
|
'title': title,
|
||||||
|
49
youtube_dl/extractor/mofosex.py
Normal file
49
youtube_dl/extractor/mofosex.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
compat_urllib_parse_urlparse,
|
||||||
|
compat_urllib_request,
|
||||||
|
compat_urllib_parse,
|
||||||
|
)
|
||||||
|
|
||||||
|
class MofosexIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'^(?:https?://)?(?:www\.)?(?P<url>mofosex\.com/videos/(?P<videoid>[0-9]+)/.*?\.html)'
|
||||||
|
_TEST = {
|
||||||
|
u'url': u'http://www.mofosex.com/videos/5018/japanese-teen-music-video.html',
|
||||||
|
u'file': u'5018.mp4',
|
||||||
|
u'md5': u'1b2eb47ac33cc75d4a80e3026b613c5a',
|
||||||
|
u'info_dict': {
|
||||||
|
u"title": u"Japanese Teen Music Video",
|
||||||
|
u"age_limit": 18,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('videoid')
|
||||||
|
url = 'http://www.' + mobj.group('url')
|
||||||
|
|
||||||
|
req = compat_urllib_request.Request(url)
|
||||||
|
req.add_header('Cookie', 'age_verified=1')
|
||||||
|
webpage = self._download_webpage(req, video_id)
|
||||||
|
|
||||||
|
video_title = self._html_search_regex(r'<h1>(.+?)<', webpage, u'title')
|
||||||
|
video_url = compat_urllib_parse.unquote(self._html_search_regex(r'flashvars.video_url = \'([^\']+)', webpage, u'video_url'))
|
||||||
|
path = compat_urllib_parse_urlparse(video_url).path
|
||||||
|
extension = os.path.splitext(path)[1][1:]
|
||||||
|
format = path.split('/')[5].split('_')[:2]
|
||||||
|
format = "-".join(format)
|
||||||
|
|
||||||
|
age_limit = self._rta_search(webpage)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': video_title,
|
||||||
|
'url': video_url,
|
||||||
|
'ext': extension,
|
||||||
|
'format': format,
|
||||||
|
'format_id': format,
|
||||||
|
'age_limit': age_limit,
|
||||||
|
}
|
@ -26,6 +26,7 @@ class MTVIE(InfoExtractor):
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
u'add_ie': ['Vevo'],
|
||||||
u'url': u'http://www.mtv.com/videos/taylor-swift/916187/everything-has-changed-ft-ed-sheeran.jhtml',
|
u'url': u'http://www.mtv.com/videos/taylor-swift/916187/everything-has-changed-ft-ed-sheeran.jhtml',
|
||||||
u'file': u'USCJY1331283.mp4',
|
u'file': u'USCJY1331283.mp4',
|
||||||
u'md5': u'73b4e7fcadd88929292fe52c3ced8caf',
|
u'md5': u'73b4e7fcadd88929292fe52c3ced8caf',
|
||||||
|
@ -7,6 +7,7 @@ class SlashdotIE(InfoExtractor):
|
|||||||
_VALID_URL = r'https?://tv.slashdot.org/video/\?embed=(?P<id>.*?)(&|$)'
|
_VALID_URL = r'https?://tv.slashdot.org/video/\?embed=(?P<id>.*?)(&|$)'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
|
u'add_ie': ['Ooyala'],
|
||||||
u'url': u'http://tv.slashdot.org/video/?embed=JscHMzZDplD0p-yNLOzTfzC3Q3xzJaUz',
|
u'url': u'http://tv.slashdot.org/video/?embed=JscHMzZDplD0p-yNLOzTfzC3Q3xzJaUz',
|
||||||
u'file': u'JscHMzZDplD0p-yNLOzTfzC3Q3xzJaUz.mp4',
|
u'file': u'JscHMzZDplD0p-yNLOzTfzC3Q3xzJaUz.mp4',
|
||||||
u'md5': u'd2222e7a4a4c1541b3e0cf732fb26735',
|
u'md5': u'd2222e7a4a4c1541b3e0cf732fb26735',
|
||||||
|
@ -29,7 +29,8 @@ class SoundcloudIE(InfoExtractor):
|
|||||||
)
|
)
|
||||||
'''
|
'''
|
||||||
IE_NAME = u'soundcloud'
|
IE_NAME = u'soundcloud'
|
||||||
_TEST = {
|
_TESTS = [
|
||||||
|
{
|
||||||
u'url': u'http://soundcloud.com/ethmusic/lostin-powers-she-so-heavy',
|
u'url': u'http://soundcloud.com/ethmusic/lostin-powers-she-so-heavy',
|
||||||
u'file': u'62986583.mp3',
|
u'file': u'62986583.mp3',
|
||||||
u'md5': u'ebef0a451b909710ed1d7787dddbf0d7',
|
u'md5': u'ebef0a451b909710ed1d7787dddbf0d7',
|
||||||
@ -39,7 +40,23 @@ class SoundcloudIE(InfoExtractor):
|
|||||||
u"uploader": u"E.T. ExTerrestrial Music",
|
u"uploader": u"E.T. ExTerrestrial Music",
|
||||||
u"title": u"Lostin Powers - She so Heavy (SneakPreview) Adrian Ackers Blueprint 1"
|
u"title": u"Lostin Powers - She so Heavy (SneakPreview) Adrian Ackers Blueprint 1"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
# not streamable song
|
||||||
|
{
|
||||||
|
u'url': u'https://soundcloud.com/the-concept-band/goldrushed-mastered?in=the-concept-band/sets/the-royal-concept-ep',
|
||||||
|
u'info_dict': {
|
||||||
|
u'id': u'47127627',
|
||||||
|
u'ext': u'mp3',
|
||||||
|
u'title': u'Goldrushed',
|
||||||
|
u'uploader': u'The Royal Concept',
|
||||||
|
u'upload_date': u'20120521',
|
||||||
|
},
|
||||||
|
u'params': {
|
||||||
|
# rtmp
|
||||||
|
u'skip_download': True,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
_CLIENT_ID = 'b45b1aa10f1ac2941910a7f0d10f8e28'
|
_CLIENT_ID = 'b45b1aa10f1ac2941910a7f0d10f8e28'
|
||||||
|
|
||||||
@ -56,16 +73,16 @@ class SoundcloudIE(InfoExtractor):
|
|||||||
return 'http://api.soundcloud.com/resolve.json?url=' + url + '&client_id=' + cls._CLIENT_ID
|
return 'http://api.soundcloud.com/resolve.json?url=' + url + '&client_id=' + cls._CLIENT_ID
|
||||||
|
|
||||||
def _extract_info_dict(self, info, full_title=None, quiet=False):
|
def _extract_info_dict(self, info, full_title=None, quiet=False):
|
||||||
video_id = info['id']
|
track_id = compat_str(info['id'])
|
||||||
name = full_title or video_id
|
name = full_title or track_id
|
||||||
if quiet == False:
|
if quiet == False:
|
||||||
self.report_extraction(name)
|
self.report_extraction(name)
|
||||||
|
|
||||||
thumbnail = info['artwork_url']
|
thumbnail = info['artwork_url']
|
||||||
if thumbnail is not None:
|
if thumbnail is not None:
|
||||||
thumbnail = thumbnail.replace('-large', '-t500x500')
|
thumbnail = thumbnail.replace('-large', '-t500x500')
|
||||||
return {
|
result = {
|
||||||
'id': info['id'],
|
'id': track_id,
|
||||||
'url': info['stream_url'] + '?client_id=' + self._CLIENT_ID,
|
'url': info['stream_url'] + '?client_id=' + self._CLIENT_ID,
|
||||||
'uploader': info['user']['username'],
|
'uploader': info['user']['username'],
|
||||||
'upload_date': unified_strdate(info['created_at']),
|
'upload_date': unified_strdate(info['created_at']),
|
||||||
@ -74,6 +91,21 @@ class SoundcloudIE(InfoExtractor):
|
|||||||
'description': info['description'],
|
'description': info['description'],
|
||||||
'thumbnail': thumbnail,
|
'thumbnail': thumbnail,
|
||||||
}
|
}
|
||||||
|
if info.get('downloadable', False):
|
||||||
|
result['url'] = 'https://api.soundcloud.com/tracks/{0}/download?client_id={1}'.format(track_id, self._CLIENT_ID)
|
||||||
|
if not info.get('streamable', False):
|
||||||
|
# We have to get the rtmp url
|
||||||
|
stream_json = self._download_webpage(
|
||||||
|
'http://api.soundcloud.com/i1/tracks/{0}/streams?client_id={1}'.format(track_id, self._CLIENT_ID),
|
||||||
|
track_id, u'Downloading track url')
|
||||||
|
rtmp_url = json.loads(stream_json)['rtmp_mp3_128_url']
|
||||||
|
# The url doesn't have an rtmp app, we have to extract the playpath
|
||||||
|
url, path = rtmp_url.split('mp3:', 1)
|
||||||
|
result.update({
|
||||||
|
'url': url,
|
||||||
|
'play_path': 'mp3:' + path,
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url, flags=re.VERBOSE)
|
mobj = re.match(self._VALID_URL, url, flags=re.VERBOSE)
|
||||||
@ -106,70 +138,8 @@ class SoundcloudIE(InfoExtractor):
|
|||||||
class SoundcloudSetIE(SoundcloudIE):
|
class SoundcloudSetIE(SoundcloudIE):
|
||||||
_VALID_URL = r'^(?:https?://)?(?:www\.)?soundcloud\.com/([\w\d-]+)/sets/([\w\d-]+)(?:[?].*)?$'
|
_VALID_URL = r'^(?:https?://)?(?:www\.)?soundcloud\.com/([\w\d-]+)/sets/([\w\d-]+)(?:[?].*)?$'
|
||||||
IE_NAME = u'soundcloud:set'
|
IE_NAME = u'soundcloud:set'
|
||||||
_TEST = {
|
# it's in tests/test_playlists.py
|
||||||
u"url":"https://soundcloud.com/the-concept-band/sets/the-royal-concept-ep",
|
_TESTS = []
|
||||||
u"playlist": [
|
|
||||||
{
|
|
||||||
u"file":"30510138.mp3",
|
|
||||||
u"md5":"f9136bf103901728f29e419d2c70f55d",
|
|
||||||
u"info_dict": {
|
|
||||||
u"upload_date": u"20111213",
|
|
||||||
u"description": u"The Royal Concept from Stockholm\r\nFilip / Povel / David / Magnus\r\nwww.royalconceptband.com",
|
|
||||||
u"uploader": u"The Royal Concept",
|
|
||||||
u"title": u"D-D-Dance"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
u"file":"47127625.mp3",
|
|
||||||
u"md5":"09b6758a018470570f8fd423c9453dd8",
|
|
||||||
u"info_dict": {
|
|
||||||
u"upload_date": u"20120521",
|
|
||||||
u"description": u"The Royal Concept from Stockholm\r\nFilip / Povel / David / Magnus\r\nwww.royalconceptband.com",
|
|
||||||
u"uploader": u"The Royal Concept",
|
|
||||||
u"title": u"The Royal Concept - Gimme Twice"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
u"file":"47127627.mp3",
|
|
||||||
u"md5":"154abd4e418cea19c3b901f1e1306d9c",
|
|
||||||
u"info_dict": {
|
|
||||||
u"upload_date": u"20120521",
|
|
||||||
u"uploader": u"The Royal Concept",
|
|
||||||
u"title": u"Goldrushed"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
u"file":"47127629.mp3",
|
|
||||||
u"md5":"2f5471edc79ad3f33a683153e96a79c1",
|
|
||||||
u"info_dict": {
|
|
||||||
u"upload_date": u"20120521",
|
|
||||||
u"description": u"The Royal Concept from Stockholm\r\nFilip / Povel / David / Magnus\r\nwww.royalconceptband.com",
|
|
||||||
u"uploader": u"The Royal Concept",
|
|
||||||
u"title": u"In the End"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
u"file":"47127631.mp3",
|
|
||||||
u"md5":"f9ba87aa940af7213f98949254f1c6e2",
|
|
||||||
u"info_dict": {
|
|
||||||
u"upload_date": u"20120521",
|
|
||||||
u"description": u"The Royal Concept from Stockholm\r\nFilip / David / Povel / Magnus\r\nwww.theroyalconceptband.com",
|
|
||||||
u"uploader": u"The Royal Concept",
|
|
||||||
u"title": u"Knocked Up"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
u"file":"75206121.mp3",
|
|
||||||
u"md5":"f9d1fe9406717e302980c30de4af9353",
|
|
||||||
u"info_dict": {
|
|
||||||
u"upload_date": u"20130116",
|
|
||||||
u"description": u"The unreleased track World on Fire premiered on the CW's hit show Arrow (8pm/7pm central). \r\nAs a gift to our fans we would like to offer you a free download of the track! ",
|
|
||||||
u"uploader": u"The Royal Concept",
|
|
||||||
u"title": u"World On Fire"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
@ -208,7 +178,7 @@ class SoundcloudUserIE(SoundcloudIE):
|
|||||||
IE_NAME = u'soundcloud:user'
|
IE_NAME = u'soundcloud:user'
|
||||||
|
|
||||||
# it's in tests/test_playlists.py
|
# it's in tests/test_playlists.py
|
||||||
_TEST = None
|
_TESTS = []
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
35
youtube_dl/extractor/space.py
Normal file
35
youtube_dl/extractor/space.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from .brightcove import BrightcoveIE
|
||||||
|
from ..utils import RegexNotFoundError, ExtractorError
|
||||||
|
|
||||||
|
|
||||||
|
class SpaceIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://www\.space\.com/\d+-(?P<title>[^/\.\?]*?)-video.html'
|
||||||
|
_TEST = {
|
||||||
|
u'add_ie': ['Brightcove'],
|
||||||
|
u'url': u'http://www.space.com/23373-huge-martian-landforms-detail-revealed-by-european-probe-video.html',
|
||||||
|
u'info_dict': {
|
||||||
|
u'id': u'2780937028001',
|
||||||
|
u'ext': u'mp4',
|
||||||
|
u'title': u'Huge Martian Landforms\' Detail Revealed By European Probe | Video',
|
||||||
|
u'description': u'md5:db81cf7f3122f95ed234b631a6ea1e61',
|
||||||
|
u'uploader': u'TechMedia Networks',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
title = mobj.group('title')
|
||||||
|
webpage = self._download_webpage(url, title)
|
||||||
|
try:
|
||||||
|
# Some videos require the playerKey field, which isn't define in
|
||||||
|
# the BrightcoveExperience object
|
||||||
|
brightcove_url = self._og_search_video_url(webpage)
|
||||||
|
except RegexNotFoundError:
|
||||||
|
# Other videos works fine with the info from the object
|
||||||
|
brightcove_url = BrightcoveIE._extract_brightcove_url(webpage)
|
||||||
|
if brightcove_url is None:
|
||||||
|
raise ExtractorError(u'The webpage does not contain a video', expected=True)
|
||||||
|
return self.url_result(brightcove_url, BrightcoveIE.ie_key())
|
@ -12,9 +12,9 @@ class SubtitlesInfoExtractor(InfoExtractor):
|
|||||||
return any([self._downloader.params.get('writesubtitles', False),
|
return any([self._downloader.params.get('writesubtitles', False),
|
||||||
self._downloader.params.get('writeautomaticsub')])
|
self._downloader.params.get('writeautomaticsub')])
|
||||||
|
|
||||||
def _list_available_subtitles(self, video_id, webpage=None):
|
def _list_available_subtitles(self, video_id, webpage):
|
||||||
""" outputs the available subtitles for the video """
|
""" outputs the available subtitles for the video """
|
||||||
sub_lang_list = self._get_available_subtitles(video_id)
|
sub_lang_list = self._get_available_subtitles(video_id, webpage)
|
||||||
auto_captions_list = self._get_available_automatic_caption(video_id, webpage)
|
auto_captions_list = self._get_available_automatic_caption(video_id, webpage)
|
||||||
sub_lang = ",".join(list(sub_lang_list.keys()))
|
sub_lang = ",".join(list(sub_lang_list.keys()))
|
||||||
self.to_screen(u'%s: Available subtitles for video: %s' %
|
self.to_screen(u'%s: Available subtitles for video: %s' %
|
||||||
@ -23,7 +23,7 @@ class SubtitlesInfoExtractor(InfoExtractor):
|
|||||||
self.to_screen(u'%s: Available automatic captions for video: %s' %
|
self.to_screen(u'%s: Available automatic captions for video: %s' %
|
||||||
(video_id, auto_lang))
|
(video_id, auto_lang))
|
||||||
|
|
||||||
def extract_subtitles(self, video_id, video_webpage=None):
|
def extract_subtitles(self, video_id, webpage):
|
||||||
"""
|
"""
|
||||||
returns {sub_lang: sub} ,{} if subtitles not found or None if the
|
returns {sub_lang: sub} ,{} if subtitles not found or None if the
|
||||||
subtitles aren't requested.
|
subtitles aren't requested.
|
||||||
@ -32,9 +32,9 @@ class SubtitlesInfoExtractor(InfoExtractor):
|
|||||||
return None
|
return None
|
||||||
available_subs_list = {}
|
available_subs_list = {}
|
||||||
if self._downloader.params.get('writeautomaticsub', False):
|
if self._downloader.params.get('writeautomaticsub', False):
|
||||||
available_subs_list.update(self._get_available_automatic_caption(video_id, video_webpage))
|
available_subs_list.update(self._get_available_automatic_caption(video_id, webpage))
|
||||||
if self._downloader.params.get('writesubtitles', False):
|
if self._downloader.params.get('writesubtitles', False):
|
||||||
available_subs_list.update(self._get_available_subtitles(video_id))
|
available_subs_list.update(self._get_available_subtitles(video_id, webpage))
|
||||||
|
|
||||||
if not available_subs_list: # error, it didn't get the available subtitles
|
if not available_subs_list: # error, it didn't get the available subtitles
|
||||||
return {}
|
return {}
|
||||||
@ -74,7 +74,7 @@ class SubtitlesInfoExtractor(InfoExtractor):
|
|||||||
return
|
return
|
||||||
return sub
|
return sub
|
||||||
|
|
||||||
def _get_available_subtitles(self, video_id):
|
def _get_available_subtitles(self, video_id, webpage):
|
||||||
"""
|
"""
|
||||||
returns {sub_lang: url} or {} if not available
|
returns {sub_lang: url} or {} if not available
|
||||||
Must be redefined by the subclasses
|
Must be redefined by the subclasses
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import re
|
import re
|
||||||
|
import xml.etree.ElementTree
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
@ -11,7 +12,7 @@ class TeamcocoIE(InfoExtractor):
|
|||||||
_TEST = {
|
_TEST = {
|
||||||
u'url': u'http://teamcoco.com/video/louis-ck-interview-george-w-bush',
|
u'url': u'http://teamcoco.com/video/louis-ck-interview-george-w-bush',
|
||||||
u'file': u'19705.mp4',
|
u'file': u'19705.mp4',
|
||||||
u'md5': u'27b6f7527da5acf534b15f21b032656e',
|
u'md5': u'cde9ba0fa3506f5f017ce11ead928f9a',
|
||||||
u'info_dict': {
|
u'info_dict': {
|
||||||
u"description": u"Louis C.K. got starstruck by George W. Bush, so what? Part one.",
|
u"description": u"Louis C.K. got starstruck by George W. Bush, so what? Part one.",
|
||||||
u"title": u"Louis C.K. Interview Pt. 1 11/3/11"
|
u"title": u"Louis C.K. Interview Pt. 1 11/3/11"
|
||||||
@ -31,16 +32,40 @@ class TeamcocoIE(InfoExtractor):
|
|||||||
self.report_extraction(video_id)
|
self.report_extraction(video_id)
|
||||||
|
|
||||||
data_url = 'http://teamcoco.com/cvp/2.0/%s.xml' % video_id
|
data_url = 'http://teamcoco.com/cvp/2.0/%s.xml' % video_id
|
||||||
data = self._download_webpage(data_url, video_id, 'Downloading data webpage')
|
data_xml = self._download_webpage(data_url, video_id, 'Downloading data webpage')
|
||||||
|
data = xml.etree.ElementTree.fromstring(data_xml.encode('utf-8'))
|
||||||
|
|
||||||
video_url = self._html_search_regex(r'<file [^>]*type="high".*?>(.*?)</file>',
|
|
||||||
data, u'video URL')
|
|
||||||
|
|
||||||
return [{
|
qualities = ['500k', '480p', '1000k', '720p', '1080p']
|
||||||
'id': video_id,
|
formats = []
|
||||||
'url': video_url,
|
for file in data.findall('files/file'):
|
||||||
|
if file.attrib.get('playmode') == 'all':
|
||||||
|
# it just duplicates one of the entries
|
||||||
|
break
|
||||||
|
file_url = file.text
|
||||||
|
m_format = re.search(r'(\d+(k|p))\.mp4', file_url)
|
||||||
|
if m_format is not None:
|
||||||
|
format_id = m_format.group(1)
|
||||||
|
else:
|
||||||
|
format_id = file.attrib['bitrate']
|
||||||
|
formats.append({
|
||||||
|
'url': file_url,
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
|
'format_id': format_id,
|
||||||
|
})
|
||||||
|
def sort_key(f):
|
||||||
|
try:
|
||||||
|
return qualities.index(f['format_id'])
|
||||||
|
except ValueError:
|
||||||
|
return -1
|
||||||
|
formats.sort(key=sort_key)
|
||||||
|
if not formats:
|
||||||
|
raise RegexNotFoundError(u'Unable to extract video URL')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'formats': formats,
|
||||||
'title': self._og_search_title(webpage),
|
'title': self._og_search_title(webpage),
|
||||||
'thumbnail': self._og_search_thumbnail(webpage),
|
'thumbnail': self._og_search_thumbnail(webpage),
|
||||||
'description': self._og_search_description(webpage),
|
'description': self._og_search_description(webpage),
|
||||||
}]
|
}
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .subtitles import SubtitlesInfoExtractor
|
||||||
|
|
||||||
|
from ..utils import (
|
||||||
|
compat_str,
|
||||||
|
RegexNotFoundError,
|
||||||
|
)
|
||||||
|
|
||||||
class TEDIE(InfoExtractor):
|
class TEDIE(SubtitlesInfoExtractor):
|
||||||
_VALID_URL=r'''http://www\.ted\.com/
|
_VALID_URL=r'''http://www\.ted\.com/
|
||||||
(
|
(
|
||||||
((?P<type_playlist>playlists)/(?P<playlist_id>\d+)) # We have a playlist
|
((?P<type_playlist>playlists)/(?P<playlist_id>\d+)) # We have a playlist
|
||||||
@ -32,7 +36,7 @@ class TEDIE(InfoExtractor):
|
|||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
m=re.match(self._VALID_URL, url, re.VERBOSE)
|
m=re.match(self._VALID_URL, url, re.VERBOSE)
|
||||||
if m.group('type_talk'):
|
if m.group('type_talk'):
|
||||||
return [self._talk_info(url)]
|
return self._talk_info(url)
|
||||||
else :
|
else :
|
||||||
playlist_id=m.group('playlist_id')
|
playlist_id=m.group('playlist_id')
|
||||||
name=m.group('name')
|
name=m.group('name')
|
||||||
@ -82,11 +86,21 @@ class TEDIE(InfoExtractor):
|
|||||||
'url': stream['file'],
|
'url': stream['file'],
|
||||||
'format': stream['id']
|
'format': stream['id']
|
||||||
} for stream in info['htmlStreams']]
|
} for stream in info['htmlStreams']]
|
||||||
|
|
||||||
|
video_id = info['id']
|
||||||
|
|
||||||
|
# subtitles
|
||||||
|
video_subtitles = self.extract_subtitles(video_id, webpage)
|
||||||
|
if self._downloader.params.get('listsubtitles', False):
|
||||||
|
self._list_available_subtitles(video_id, webpage)
|
||||||
|
return
|
||||||
|
|
||||||
info = {
|
info = {
|
||||||
'id': info['id'],
|
'id': video_id,
|
||||||
'title': title,
|
'title': title,
|
||||||
'thumbnail': thumbnail,
|
'thumbnail': thumbnail,
|
||||||
'description': desc,
|
'description': desc,
|
||||||
|
'subtitles': video_subtitles,
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,3 +108,17 @@ class TEDIE(InfoExtractor):
|
|||||||
info.update(info['formats'][-1])
|
info.update(info['formats'][-1])
|
||||||
|
|
||||||
return info
|
return info
|
||||||
|
|
||||||
|
def _get_available_subtitles(self, video_id, webpage):
|
||||||
|
try:
|
||||||
|
options = self._search_regex(r'(?:<select name="subtitles_language_select" id="subtitles_language_select">)(.*?)(?:</select>)', webpage, 'subtitles_language_select', flags=re.DOTALL)
|
||||||
|
languages = re.findall(r'(?:<option value=")(\S+)"', options)
|
||||||
|
if languages:
|
||||||
|
sub_lang_list = {}
|
||||||
|
for l in languages:
|
||||||
|
url = 'http://www.ted.com/talks/subtitles/id/%s/lang/%s/format/srt' % (video_id, l)
|
||||||
|
sub_lang_list[l] = url
|
||||||
|
return sub_lang_list
|
||||||
|
except RegexNotFoundError as err:
|
||||||
|
self._downloader.report_warning(u'video doesn\'t have subtitles')
|
||||||
|
return {}
|
||||||
|
@ -8,7 +8,7 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class ViddlerIE(InfoExtractor):
|
class ViddlerIE(InfoExtractor):
|
||||||
_VALID_URL = r'(?P<domain>https?://(?:www\.)?viddler.com)/(?:v|embed|player)/(?P<id>[0-9]+)'
|
_VALID_URL = r'(?P<domain>https?://(?:www\.)?viddler.com)/(?:v|embed|player)/(?P<id>[a-z0-9]+)'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
u"url": u"http://www.viddler.com/v/43903784",
|
u"url": u"http://www.viddler.com/v/43903784",
|
||||||
u'file': u'43903784.mp4',
|
u'file': u'43903784.mp4',
|
||||||
|
@ -20,7 +20,7 @@ class VimeoIE(InfoExtractor):
|
|||||||
"""Information extractor for vimeo.com."""
|
"""Information extractor for vimeo.com."""
|
||||||
|
|
||||||
# _VALID_URL matches Vimeo URLs
|
# _VALID_URL matches Vimeo URLs
|
||||||
_VALID_URL = r'(?P<proto>https?://)?(?:(?:www|player)\.)?vimeo(?P<pro>pro)?\.com/(?:(?:(?:groups|album)/[^/]+)|(?:.*?)/)?(?P<direct_link>play_redirect_hls\?clip_id=)?(?:videos?/)?(?P<id>[0-9]+)/?(?:[?].*)?(?:#.*)?$'
|
_VALID_URL = r'(?P<proto>https?://)?(?:(?:www|(?P<player>player))\.)?vimeo(?P<pro>pro)?\.com/(?:(?:(?:groups|album)/[^/]+)|(?:.*?)/)?(?P<direct_link>play_redirect_hls\?clip_id=)?(?:videos?/)?(?P<id>[0-9]+)/?(?:[?].*)?(?:#.*)?$'
|
||||||
_NETRC_MACHINE = 'vimeo'
|
_NETRC_MACHINE = 'vimeo'
|
||||||
IE_NAME = u'vimeo'
|
IE_NAME = u'vimeo'
|
||||||
_TESTS = [
|
_TESTS = [
|
||||||
@ -128,11 +128,9 @@ class VimeoIE(InfoExtractor):
|
|||||||
raise ExtractorError(u'Invalid URL: %s' % url)
|
raise ExtractorError(u'Invalid URL: %s' % url)
|
||||||
|
|
||||||
video_id = mobj.group('id')
|
video_id = mobj.group('id')
|
||||||
if not mobj.group('proto'):
|
if mobj.group('pro') or mobj.group('player'):
|
||||||
url = 'https://' + url
|
|
||||||
elif mobj.group('pro'):
|
|
||||||
url = 'http://player.vimeo.com/video/' + video_id
|
url = 'http://player.vimeo.com/video/' + video_id
|
||||||
elif mobj.group('direct_link'):
|
else:
|
||||||
url = 'https://vimeo.com/' + video_id
|
url = 'https://vimeo.com/' + video_id
|
||||||
|
|
||||||
# Retrieve video webpage to extract further information
|
# Retrieve video webpage to extract further information
|
||||||
@ -234,7 +232,7 @@ class VimeoIE(InfoExtractor):
|
|||||||
if len(formats) == 0:
|
if len(formats) == 0:
|
||||||
raise ExtractorError(u'No known codec found')
|
raise ExtractorError(u'No known codec found')
|
||||||
|
|
||||||
return [{
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'uploader': video_uploader,
|
'uploader': video_uploader,
|
||||||
'uploader_id': video_uploader_id,
|
'uploader_id': video_uploader_id,
|
||||||
@ -243,7 +241,8 @@ class VimeoIE(InfoExtractor):
|
|||||||
'thumbnail': video_thumbnail,
|
'thumbnail': video_thumbnail,
|
||||||
'description': video_description,
|
'description': video_description,
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
}]
|
'webpage_url': url,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class VimeoChannelIE(InfoExtractor):
|
class VimeoChannelIE(InfoExtractor):
|
||||||
|
@ -13,6 +13,7 @@ class WeiboIE(InfoExtractor):
|
|||||||
_VALID_URL = r'https?://video\.weibo\.com/v/weishipin/t_(?P<id>.+?)\.htm'
|
_VALID_URL = r'https?://video\.weibo\.com/v/weishipin/t_(?P<id>.+?)\.htm'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
|
u'add_ie': ['Sina'],
|
||||||
u'url': u'http://video.weibo.com/v/weishipin/t_zjUw2kZ.htm',
|
u'url': u'http://video.weibo.com/v/weishipin/t_zjUw2kZ.htm',
|
||||||
u'file': u'98322879.flv',
|
u'file': u'98322879.flv',
|
||||||
u'info_dict': {
|
u'info_dict': {
|
||||||
|
@ -9,7 +9,7 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class XNXXIE(InfoExtractor):
|
class XNXXIE(InfoExtractor):
|
||||||
_VALID_URL = r'^(?:https?://)?video\.xnxx\.com/video([0-9]+)/(.*)'
|
_VALID_URL = r'^(?:https?://)?(?:video|www)\.xnxx\.com/video([0-9]+)/(.*)'
|
||||||
VIDEO_URL_RE = r'flv_url=(.*?)&'
|
VIDEO_URL_RE = r'flv_url=(.*?)&'
|
||||||
VIDEO_TITLE_RE = r'<title>(.*?)\s+-\s+XNXX.COM'
|
VIDEO_TITLE_RE = r'<title>(.*?)\s+-\s+XNXX.COM'
|
||||||
VIDEO_THUMB_RE = r'url_bigthumb=(.*?)&'
|
VIDEO_THUMB_RE = r'url_bigthumb=(.*?)&'
|
||||||
|
55
youtube_dl/extractor/xtube.py
Normal file
55
youtube_dl/extractor/xtube.py
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
compat_urllib_parse_urlparse,
|
||||||
|
compat_urllib_request,
|
||||||
|
compat_urllib_parse,
|
||||||
|
)
|
||||||
|
|
||||||
|
class XTubeIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'^(?:https?://)?(?:www\.)?(?P<url>xtube\.com/watch\.php\?v=(?P<videoid>[^/?&]+))'
|
||||||
|
_TEST = {
|
||||||
|
u'url': u'http://www.xtube.com/watch.php?v=kVTUy_G222_',
|
||||||
|
u'file': u'kVTUy_G222_.mp4',
|
||||||
|
u'md5': u'092fbdd3cbe292c920ef6fc6a8a9cdab',
|
||||||
|
u'info_dict': {
|
||||||
|
u"title": u"strange erotica",
|
||||||
|
u"description": u"surreal gay themed erotica...almost an ET kind of thing",
|
||||||
|
u"uploader": u"greenshowers",
|
||||||
|
u"age_limit": 18,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('videoid')
|
||||||
|
url = 'http://www.' + mobj.group('url')
|
||||||
|
|
||||||
|
req = compat_urllib_request.Request(url)
|
||||||
|
req.add_header('Cookie', 'age_verified=1')
|
||||||
|
webpage = self._download_webpage(req, video_id)
|
||||||
|
|
||||||
|
video_title = self._html_search_regex(r'<div class="p_5px[^>]*>([^<]+)', webpage, u'title')
|
||||||
|
video_uploader = self._html_search_regex(r'so_s\.addVariable\("owner_u", "([^"]+)', webpage, u'uploader', fatal=False)
|
||||||
|
video_description = self._html_search_regex(r'<p class="video_description">([^<]+)', webpage, u'description', default=None)
|
||||||
|
video_url= self._html_search_regex(r'var videoMp4 = "([^"]+)', webpage, u'video_url').replace('\\/', '/')
|
||||||
|
path = compat_urllib_parse_urlparse(video_url).path
|
||||||
|
extension = os.path.splitext(path)[1][1:]
|
||||||
|
format = path.split('/')[5].split('_')[:2]
|
||||||
|
format[0] += 'p'
|
||||||
|
format[1] += 'k'
|
||||||
|
format = "-".join(format)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': video_title,
|
||||||
|
'uploader': video_uploader,
|
||||||
|
'description': video_description,
|
||||||
|
'url': video_url,
|
||||||
|
'ext': extension,
|
||||||
|
'format': format,
|
||||||
|
'format_id': format,
|
||||||
|
'age_limit': 18,
|
||||||
|
}
|
@ -1082,7 +1082,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):
|
|||||||
else:
|
else:
|
||||||
raise ExtractorError(u'Unable to decrypt signature, key length %d not supported; retrying might work' % (len(s)))
|
raise ExtractorError(u'Unable to decrypt signature, key length %d not supported; retrying might work' % (len(s)))
|
||||||
|
|
||||||
def _get_available_subtitles(self, video_id):
|
def _get_available_subtitles(self, video_id, webpage):
|
||||||
try:
|
try:
|
||||||
sub_list = self._download_webpage(
|
sub_list = self._download_webpage(
|
||||||
'http://video.google.com/timedtext?hl=en&type=list&v=%s' % video_id,
|
'http://video.google.com/timedtext?hl=en&type=list&v=%s' % video_id,
|
||||||
@ -1485,7 +1485,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):
|
|||||||
'subtitles': video_subtitles,
|
'subtitles': video_subtitles,
|
||||||
'duration': video_duration,
|
'duration': video_duration,
|
||||||
'age_limit': 18 if age_gate else 0,
|
'age_limit': 18 if age_gate else 0,
|
||||||
'annotations': video_annotations
|
'annotations': video_annotations,
|
||||||
|
'webpage_url': 'https://www.youtube.com/watch?v=%s' % video_id,
|
||||||
})
|
})
|
||||||
return results
|
return results
|
||||||
|
|
||||||
@ -1571,7 +1572,6 @@ class YoutubePlaylistIE(InfoExtractor):
|
|||||||
class YoutubeChannelIE(InfoExtractor):
|
class YoutubeChannelIE(InfoExtractor):
|
||||||
IE_DESC = u'YouTube.com channels'
|
IE_DESC = u'YouTube.com channels'
|
||||||
_VALID_URL = r"^(?:https?://)?(?:youtu\.be|(?:\w+\.)?youtube(?:-nocookie)?\.com)/channel/([0-9A-Za-z_-]+)"
|
_VALID_URL = r"^(?:https?://)?(?:youtu\.be|(?:\w+\.)?youtube(?:-nocookie)?\.com)/channel/([0-9A-Za-z_-]+)"
|
||||||
_TEMPLATE_URL = 'http://www.youtube.com/channel/%s/videos?sort=da&flow=list&view=0&page=%s&gl=US&hl=en'
|
|
||||||
_MORE_PAGES_INDICATOR = 'yt-uix-load-more'
|
_MORE_PAGES_INDICATOR = 'yt-uix-load-more'
|
||||||
_MORE_PAGES_URL = 'http://www.youtube.com/c4_browse_ajax?action_load_more_videos=1&flow=list&paging=%s&view=0&sort=da&channel_id=%s'
|
_MORE_PAGES_URL = 'http://www.youtube.com/c4_browse_ajax?action_load_more_videos=1&flow=list&paging=%s&view=0&sort=da&channel_id=%s'
|
||||||
IE_NAME = u'youtube:channel'
|
IE_NAME = u'youtube:channel'
|
||||||
@ -1592,18 +1592,8 @@ class YoutubeChannelIE(InfoExtractor):
|
|||||||
# Download channel page
|
# Download channel page
|
||||||
channel_id = mobj.group(1)
|
channel_id = mobj.group(1)
|
||||||
video_ids = []
|
video_ids = []
|
||||||
pagenum = 1
|
|
||||||
|
|
||||||
url = self._TEMPLATE_URL % (channel_id, pagenum)
|
# Download all channel pages using the json-based channel_ajax query
|
||||||
page = self._download_webpage(url, channel_id,
|
|
||||||
u'Downloading page #%s' % pagenum)
|
|
||||||
|
|
||||||
# Extract video identifiers
|
|
||||||
ids_in_page = self.extract_videos_from_page(page)
|
|
||||||
video_ids.extend(ids_in_page)
|
|
||||||
|
|
||||||
# Download any subsequent channel pages using the json-based channel_ajax query
|
|
||||||
if self._MORE_PAGES_INDICATOR in page:
|
|
||||||
for pagenum in itertools.count(1):
|
for pagenum in itertools.count(1):
|
||||||
url = self._MORE_PAGES_URL % (pagenum, channel_id)
|
url = self._MORE_PAGES_URL % (pagenum, channel_id)
|
||||||
page = self._download_webpage(url, channel_id,
|
page = self._download_webpage(url, channel_id,
|
||||||
@ -1731,6 +1721,10 @@ class YoutubeSearchIE(SearchInfoExtractor):
|
|||||||
videos = [self.url_result('http://www.youtube.com/watch?v=%s' % id, 'Youtube') for id in video_ids]
|
videos = [self.url_result('http://www.youtube.com/watch?v=%s' % id, 'Youtube') for id in video_ids]
|
||||||
return self.playlist_result(videos, query)
|
return self.playlist_result(videos, query)
|
||||||
|
|
||||||
|
class YoutubeSearchDateIE(YoutubeSearchIE):
|
||||||
|
_API_URL = 'https://gdata.youtube.com/feeds/api/videos?q=%s&start-index=%i&max-results=50&v=2&alt=jsonc&orderby=published'
|
||||||
|
_SEARCH_KEY = 'ytsearchdate'
|
||||||
|
IE_DESC = u'YouTube.com searches, newest videos first'
|
||||||
|
|
||||||
class YoutubeShowIE(InfoExtractor):
|
class YoutubeShowIE(InfoExtractor):
|
||||||
IE_DESC = u'YouTube.com (multi-season) shows'
|
IE_DESC = u'YouTube.com (multi-season) shows'
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
|
|
||||||
__version__ = '2013.11.02'
|
__version__ = '2013.11.11'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user