Compare commits
84 Commits
2013.11.17
...
2013.11.24
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
138df537ff | ||
|
|
0c7c19d6bc | ||
|
|
eaaafc59c2 | ||
|
|
382ed50e0e | ||
|
|
66ec019240 | ||
|
|
bd49928f7a | ||
|
|
23e6d50d73 | ||
|
|
2e767313e4 | ||
|
|
38b2db6a66 | ||
|
|
13ebea791f | ||
|
|
4c9c57428f | ||
|
|
8bf9319e9c | ||
|
|
4914120727 | ||
|
|
36de0a0e1a | ||
|
|
e5c146d586 | ||
|
|
52ad14aeb0 | ||
|
|
43afe28588 | ||
|
|
a87b0615aa | ||
|
|
d7386f6276 | ||
|
|
081640940e | ||
|
|
7012b23c94 | ||
|
|
d3b30148ed | ||
|
|
9f79463803 | ||
|
|
d35dc6d3b5 | ||
|
|
50123be421 | ||
|
|
3f8ced5144 | ||
|
|
00ea0f11eb | ||
|
|
0b63aed8df | ||
|
|
15c3adbb16 | ||
|
|
f143a42fe6 | ||
|
|
241650c7ff | ||
|
|
bfe7439a20 | ||
|
|
cffa6aa107 | ||
|
|
02e4ebbbad | ||
|
|
ab009f59ef | ||
|
|
0980426559 | ||
|
|
b1c9c66936 | ||
|
|
a6a173c2fd | ||
|
|
2bb683c201 | ||
|
|
64bb5187f5 | ||
|
|
9e4f50a8ae | ||
|
|
0190eecc00 | ||
|
|
ca872a4c0b | ||
|
|
f2e87ef4fa | ||
|
|
0ad97bbc05 | ||
|
|
c4864091a1 | ||
|
|
9a98a466b3 | ||
|
|
f99e0f1ed6 | ||
|
|
d323bcb152 | ||
|
|
da6a795fdb | ||
|
|
c5edcde21f | ||
|
|
15ff3c831e | ||
|
|
100959a6d9 | ||
|
|
0a120f74b2 | ||
|
|
8f05351984 | ||
|
|
4eb92208a3 | ||
|
|
71791f414c | ||
|
|
f3682997d7 | ||
|
|
cc13cc0251 | ||
|
|
86bd5f2ca9 | ||
|
|
8694c60000 | ||
|
|
9d1538182f | ||
|
|
5904088811 | ||
|
|
69545c2aff | ||
|
|
495da337ae | ||
|
|
34b3afc7be | ||
|
|
00373a4c5d | ||
|
|
cb7dfeeac4 | ||
|
|
efd6c574a2 | ||
|
|
4113e6ab56 | ||
|
|
9a942a4671 | ||
|
|
9906d397a0 | ||
|
|
ae8f787141 | ||
|
|
a81b4d5c8f | ||
|
|
887c6acdf2 | ||
|
|
83aa529330 | ||
|
|
96b31b6533 | ||
|
|
fccd377198 | ||
|
|
ba3881dffd | ||
|
|
08bc37cdd0 | ||
|
|
9771cceb2c | ||
|
|
880e1c529d | ||
|
|
dcbb45803f | ||
|
|
0bd59f3723 |
@@ -123,6 +123,7 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
--get-description simulate, quiet but print video description
|
--get-description simulate, quiet but print video description
|
||||||
--get-filename simulate, quiet but print output filename
|
--get-filename simulate, quiet but print output filename
|
||||||
--get-format simulate, quiet but print output format
|
--get-format simulate, quiet but print output format
|
||||||
|
-j, --dump-json simulate, quiet but print JSON information
|
||||||
--newline output progress bar as new lines
|
--newline output progress bar as new lines
|
||||||
--no-progress do not print progress bar
|
--no-progress do not print progress bar
|
||||||
--console-title display progress in console titlebar
|
--console-title display progress in console titlebar
|
||||||
|
|||||||
2
setup.py
2
setup.py
@@ -48,7 +48,7 @@ else:
|
|||||||
'data_files': [ # Installing system-wide would require sudo...
|
'data_files': [ # Installing system-wide would require sudo...
|
||||||
('etc/bash_completion.d', ['youtube-dl.bash-completion']),
|
('etc/bash_completion.d', ['youtube-dl.bash-completion']),
|
||||||
('share/doc/youtube_dl', ['README.txt']),
|
('share/doc/youtube_dl', ['README.txt']),
|
||||||
('share/man/man1/', ['youtube-dl.1'])
|
('share/man/man1', ['youtube-dl.1'])
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
if setuptools_available:
|
if setuptools_available:
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ def _download_restricted(url, filename, age):
|
|||||||
}
|
}
|
||||||
ydl = YoutubeDL(params)
|
ydl = YoutubeDL(params)
|
||||||
ydl.add_default_info_extractors()
|
ydl.add_default_info_extractors()
|
||||||
json_filename = filename + '.info.json'
|
json_filename = os.path.splitext(filename)[0] + '.info.json'
|
||||||
try_rm(json_filename)
|
try_rm(json_filename)
|
||||||
ydl.download([url])
|
ydl.download([url])
|
||||||
res = os.path.exists(json_filename)
|
res = os.path.exists(json_filename)
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ def generator(test_case):
|
|||||||
tc_filename = get_tc_filename(tc)
|
tc_filename = get_tc_filename(tc)
|
||||||
try_rm(tc_filename)
|
try_rm(tc_filename)
|
||||||
try_rm(tc_filename + '.part')
|
try_rm(tc_filename + '.part')
|
||||||
try_rm(tc_filename + '.info.json')
|
try_rm(os.path.splitext(tc_filename)[0] + '.info.json')
|
||||||
try_rm_tcs_files()
|
try_rm_tcs_files()
|
||||||
try:
|
try:
|
||||||
try_num = 1
|
try_num = 1
|
||||||
@@ -130,11 +130,12 @@ def generator(test_case):
|
|||||||
if not test_case.get('params', {}).get('skip_download', False):
|
if not test_case.get('params', {}).get('skip_download', False):
|
||||||
self.assertTrue(os.path.exists(tc_filename), msg='Missing file ' + tc_filename)
|
self.assertTrue(os.path.exists(tc_filename), msg='Missing file ' + tc_filename)
|
||||||
self.assertTrue(tc_filename in finished_hook_called)
|
self.assertTrue(tc_filename in finished_hook_called)
|
||||||
self.assertTrue(os.path.exists(tc_filename + '.info.json'))
|
info_json_fn = os.path.splitext(tc_filename)[0] + '.info.json'
|
||||||
|
self.assertTrue(os.path.exists(info_json_fn))
|
||||||
if 'md5' in tc:
|
if 'md5' in tc:
|
||||||
md5_for_file = _file_md5(tc_filename)
|
md5_for_file = _file_md5(tc_filename)
|
||||||
self.assertEqual(md5_for_file, tc['md5'])
|
self.assertEqual(md5_for_file, tc['md5'])
|
||||||
with io.open(tc_filename + '.info.json', encoding='utf-8') as infof:
|
with io.open(info_json_fn, encoding='utf-8') as infof:
|
||||||
info_dict = json.load(infof)
|
info_dict = json.load(infof)
|
||||||
for (info_field, expected) in tc.get('info_dict', {}).items():
|
for (info_field, expected) in tc.get('info_dict', {}).items():
|
||||||
if isinstance(expected, compat_str) and expected.startswith('md5:'):
|
if isinstance(expected, compat_str) and expected.startswith('md5:'):
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ from youtube_dl.extractor import (
|
|||||||
LivestreamIE,
|
LivestreamIE,
|
||||||
NHLVideocenterIE,
|
NHLVideocenterIE,
|
||||||
BambuserChannelIE,
|
BambuserChannelIE,
|
||||||
|
BandcampAlbumIE
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -101,7 +102,15 @@ class TestPlaylists(unittest.TestCase):
|
|||||||
result = ie.extract('http://bambuser.com/channel/pixelversity')
|
result = ie.extract('http://bambuser.com/channel/pixelversity')
|
||||||
self.assertIsPlaylist(result)
|
self.assertIsPlaylist(result)
|
||||||
self.assertEqual(result['title'], u'pixelversity')
|
self.assertEqual(result['title'], u'pixelversity')
|
||||||
self.assertTrue(len(result['entries']) >= 66)
|
self.assertTrue(len(result['entries']) >= 60)
|
||||||
|
|
||||||
|
def test_bandcamp_album(self):
|
||||||
|
dl = FakeYDL()
|
||||||
|
ie = BandcampAlbumIE(dl)
|
||||||
|
result = ie.extract('http://mpallante.bandcamp.com/album/nightmare-night-ep')
|
||||||
|
self.assertIsPlaylist(result)
|
||||||
|
self.assertEqual(result['title'], u'Nightmare Night EP')
|
||||||
|
self.assertTrue(len(result['entries']) >= 4)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ from youtube_dl.utils import (
|
|||||||
xpath_with_ns,
|
xpath_with_ns,
|
||||||
smuggle_url,
|
smuggle_url,
|
||||||
unsmuggle_url,
|
unsmuggle_url,
|
||||||
|
shell_quote,
|
||||||
|
encodeFilename,
|
||||||
)
|
)
|
||||||
|
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
@@ -170,6 +172,10 @@ class TestUtil(unittest.TestCase):
|
|||||||
self.assertEqual(res_url, url)
|
self.assertEqual(res_url, url)
|
||||||
self.assertEqual(res_data, None)
|
self.assertEqual(res_data, None)
|
||||||
|
|
||||||
|
def test_shell_quote(self):
|
||||||
|
args = ['ffmpeg', '-i', encodeFilename(u'ñ€ß\'.mp4')]
|
||||||
|
self.assertEqual(shell_quote(args), u"""ffmpeg -i 'ñ€ß'"'"'.mp4'""")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ params = get_params({
|
|||||||
|
|
||||||
|
|
||||||
TEST_ID = 'BaW_jenozKc'
|
TEST_ID = 'BaW_jenozKc'
|
||||||
INFO_JSON_FILE = TEST_ID + '.mp4.info.json'
|
INFO_JSON_FILE = TEST_ID + '.info.json'
|
||||||
DESCRIPTION_FILE = TEST_ID + '.mp4.description'
|
DESCRIPTION_FILE = TEST_ID + '.mp4.description'
|
||||||
EXPECTED_DESCRIPTION = u'''test chars: "'/\ä↭𝕐
|
EXPECTED_DESCRIPTION = u'''test chars: "'/\ä↭𝕐
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class TestYoutubeLists(unittest.TestCase):
|
|||||||
def test_youtube_playlist(self):
|
def test_youtube_playlist(self):
|
||||||
dl = FakeYDL()
|
dl = FakeYDL()
|
||||||
ie = YoutubePlaylistIE(dl)
|
ie = YoutubePlaylistIE(dl)
|
||||||
result = ie.extract('https://www.youtube.com/playlist?list=PLwiyx1dc3P2JR9N8gQaQN_BCvlSlap7re')[0]
|
result = ie.extract('https://www.youtube.com/playlist?list=PLwiyx1dc3P2JR9N8gQaQN_BCvlSlap7re')
|
||||||
self.assertIsPlaylist(result)
|
self.assertIsPlaylist(result)
|
||||||
self.assertEqual(result['title'], 'ytdl test PL')
|
self.assertEqual(result['title'], 'ytdl test PL')
|
||||||
ytie_results = [YoutubeIE()._extract_id(url['url']) for url in result['entries']]
|
ytie_results = [YoutubeIE()._extract_id(url['url']) for url in result['entries']]
|
||||||
@@ -44,13 +44,13 @@ class TestYoutubeLists(unittest.TestCase):
|
|||||||
def test_issue_673(self):
|
def test_issue_673(self):
|
||||||
dl = FakeYDL()
|
dl = FakeYDL()
|
||||||
ie = YoutubePlaylistIE(dl)
|
ie = YoutubePlaylistIE(dl)
|
||||||
result = ie.extract('PLBB231211A4F62143')[0]
|
result = ie.extract('PLBB231211A4F62143')
|
||||||
self.assertTrue(len(result['entries']) > 25)
|
self.assertTrue(len(result['entries']) > 25)
|
||||||
|
|
||||||
def test_youtube_playlist_long(self):
|
def test_youtube_playlist_long(self):
|
||||||
dl = FakeYDL()
|
dl = FakeYDL()
|
||||||
ie = YoutubePlaylistIE(dl)
|
ie = YoutubePlaylistIE(dl)
|
||||||
result = ie.extract('https://www.youtube.com/playlist?list=UUBABnxM4Ar9ten8Mdjj1j0Q')[0]
|
result = ie.extract('https://www.youtube.com/playlist?list=UUBABnxM4Ar9ten8Mdjj1j0Q')
|
||||||
self.assertIsPlaylist(result)
|
self.assertIsPlaylist(result)
|
||||||
self.assertTrue(len(result['entries']) >= 799)
|
self.assertTrue(len(result['entries']) >= 799)
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ class TestYoutubeLists(unittest.TestCase):
|
|||||||
#651
|
#651
|
||||||
dl = FakeYDL()
|
dl = FakeYDL()
|
||||||
ie = YoutubePlaylistIE(dl)
|
ie = YoutubePlaylistIE(dl)
|
||||||
result = ie.extract('https://www.youtube.com/playlist?list=PLwP_SiAcdui0KVebT0mU9Apz359a4ubsC')[0]
|
result = ie.extract('https://www.youtube.com/playlist?list=PLwP_SiAcdui0KVebT0mU9Apz359a4ubsC')
|
||||||
ytie_results = [YoutubeIE()._extract_id(url['url']) for url in result['entries']]
|
ytie_results = [YoutubeIE()._extract_id(url['url']) for url in result['entries']]
|
||||||
self.assertFalse('pElCt5oNDuI' in ytie_results)
|
self.assertFalse('pElCt5oNDuI' in ytie_results)
|
||||||
self.assertFalse('KdPEApIVdWM' in ytie_results)
|
self.assertFalse('KdPEApIVdWM' in ytie_results)
|
||||||
@@ -66,7 +66,7 @@ class TestYoutubeLists(unittest.TestCase):
|
|||||||
def test_youtube_playlist_empty(self):
|
def test_youtube_playlist_empty(self):
|
||||||
dl = FakeYDL()
|
dl = FakeYDL()
|
||||||
ie = YoutubePlaylistIE(dl)
|
ie = YoutubePlaylistIE(dl)
|
||||||
result = ie.extract('https://www.youtube.com/playlist?list=PLtPgu7CB4gbZDA7i_euNxn75ISqxwZPYx')[0]
|
result = ie.extract('https://www.youtube.com/playlist?list=PLtPgu7CB4gbZDA7i_euNxn75ISqxwZPYx')
|
||||||
self.assertIsPlaylist(result)
|
self.assertIsPlaylist(result)
|
||||||
self.assertEqual(len(result['entries']), 0)
|
self.assertEqual(len(result['entries']), 0)
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ class TestYoutubeLists(unittest.TestCase):
|
|||||||
dl = FakeYDL()
|
dl = FakeYDL()
|
||||||
ie = YoutubePlaylistIE(dl)
|
ie = YoutubePlaylistIE(dl)
|
||||||
# TODO find a > 100 (paginating?) videos course
|
# TODO find a > 100 (paginating?) videos course
|
||||||
result = ie.extract('https://www.youtube.com/course?list=ECUl4u3cNGP61MdtwGTqZA0MreSaDybji8')[0]
|
result = ie.extract('https://www.youtube.com/course?list=ECUl4u3cNGP61MdtwGTqZA0MreSaDybji8')
|
||||||
entries = result['entries']
|
entries = result['entries']
|
||||||
self.assertEqual(YoutubeIE()._extract_id(entries[0]['url']), 'j9WZyLZCBzs')
|
self.assertEqual(YoutubeIE()._extract_id(entries[0]['url']), 'j9WZyLZCBzs')
|
||||||
self.assertEqual(len(entries), 25)
|
self.assertEqual(len(entries), 25)
|
||||||
@@ -84,22 +84,22 @@ class TestYoutubeLists(unittest.TestCase):
|
|||||||
dl = FakeYDL()
|
dl = FakeYDL()
|
||||||
ie = YoutubeChannelIE(dl)
|
ie = YoutubeChannelIE(dl)
|
||||||
#test paginated channel
|
#test paginated channel
|
||||||
result = ie.extract('https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w')[0]
|
result = ie.extract('https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w')
|
||||||
self.assertTrue(len(result['entries']) > 90)
|
self.assertTrue(len(result['entries']) > 90)
|
||||||
#test autogenerated channel
|
#test autogenerated channel
|
||||||
result = ie.extract('https://www.youtube.com/channel/HCtnHdj3df7iM/videos')[0]
|
result = ie.extract('https://www.youtube.com/channel/HCtnHdj3df7iM/videos')
|
||||||
self.assertTrue(len(result['entries']) >= 18)
|
self.assertTrue(len(result['entries']) >= 18)
|
||||||
|
|
||||||
def test_youtube_user(self):
|
def test_youtube_user(self):
|
||||||
dl = FakeYDL()
|
dl = FakeYDL()
|
||||||
ie = YoutubeUserIE(dl)
|
ie = YoutubeUserIE(dl)
|
||||||
result = ie.extract('https://www.youtube.com/user/TheLinuxFoundation')[0]
|
result = ie.extract('https://www.youtube.com/user/TheLinuxFoundation')
|
||||||
self.assertTrue(len(result['entries']) >= 320)
|
self.assertTrue(len(result['entries']) >= 320)
|
||||||
|
|
||||||
def test_youtube_safe_search(self):
|
def test_youtube_safe_search(self):
|
||||||
dl = FakeYDL()
|
dl = FakeYDL()
|
||||||
ie = YoutubePlaylistIE(dl)
|
ie = YoutubePlaylistIE(dl)
|
||||||
result = ie.extract('PLtPgu7CB4gbY9oDN3drwC3cMbJggS7dKl')[0]
|
result = ie.extract('PLtPgu7CB4gbY9oDN3drwC3cMbJggS7dKl')
|
||||||
self.assertEqual(len(result['entries']), 2)
|
self.assertEqual(len(result['entries']), 2)
|
||||||
|
|
||||||
def test_youtube_show(self):
|
def test_youtube_show(self):
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from __future__ import absolute_import
|
|||||||
|
|
||||||
import errno
|
import errno
|
||||||
import io
|
import io
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
@@ -84,6 +85,7 @@ class YoutubeDL(object):
|
|||||||
forcethumbnail: Force printing thumbnail URL.
|
forcethumbnail: Force printing thumbnail URL.
|
||||||
forcedescription: Force printing description.
|
forcedescription: Force printing description.
|
||||||
forcefilename: Force printing final filename.
|
forcefilename: Force printing final filename.
|
||||||
|
forcejson: Force printing info_dict as JSON.
|
||||||
simulate: Do not download the video files.
|
simulate: Do not download the video files.
|
||||||
format: Video format code.
|
format: Video format code.
|
||||||
format_limit: Highest quality format to try.
|
format_limit: Highest quality format to try.
|
||||||
@@ -95,6 +97,7 @@ class YoutubeDL(object):
|
|||||||
playlistend: Playlist item to end at.
|
playlistend: Playlist item to end at.
|
||||||
matchtitle: Download only matching titles.
|
matchtitle: Download only matching titles.
|
||||||
rejecttitle: Reject downloads for matching titles.
|
rejecttitle: Reject downloads for matching titles.
|
||||||
|
logger: Log messages to a logging.Logger instance.
|
||||||
logtostderr: Log messages to stderr instead of stdout.
|
logtostderr: Log messages to stderr instead of stdout.
|
||||||
writedescription: Write the video description to a .description file
|
writedescription: Write the video description to a .description file
|
||||||
writeinfojson: Write the video description to a .info.json file
|
writeinfojson: Write the video description to a .info.json file
|
||||||
@@ -190,7 +193,9 @@ class YoutubeDL(object):
|
|||||||
|
|
||||||
def to_screen(self, message, skip_eol=False):
|
def to_screen(self, message, skip_eol=False):
|
||||||
"""Print message to stdout if not in quiet mode."""
|
"""Print message to stdout if not in quiet mode."""
|
||||||
if not self.params.get('quiet', False):
|
if self.params.get('logger'):
|
||||||
|
self.params['logger'].debug(message)
|
||||||
|
elif not self.params.get('quiet', False):
|
||||||
terminator = [u'\n', u''][skip_eol]
|
terminator = [u'\n', u''][skip_eol]
|
||||||
output = message + terminator
|
output = message + terminator
|
||||||
write_string(output, self._screen_file)
|
write_string(output, self._screen_file)
|
||||||
@@ -198,6 +203,9 @@ class YoutubeDL(object):
|
|||||||
def to_stderr(self, message):
|
def to_stderr(self, message):
|
||||||
"""Print message to stderr."""
|
"""Print message to stderr."""
|
||||||
assert type(message) == type(u'')
|
assert type(message) == type(u'')
|
||||||
|
if self.params.get('logger'):
|
||||||
|
self.params['logger'].error(message)
|
||||||
|
else:
|
||||||
output = message + u'\n'
|
output = message + u'\n'
|
||||||
if 'b' in getattr(self._screen_file, 'mode', '') or sys.version_info[0] < 3: # Python 2 lies about the mode of sys.stdout/sys.stderr
|
if 'b' in getattr(self._screen_file, 'mode', '') or sys.version_info[0] < 3: # Python 2 lies about the mode of sys.stdout/sys.stderr
|
||||||
output = output.encode(preferredencoding())
|
output = output.encode(preferredencoding())
|
||||||
@@ -217,13 +225,15 @@ class YoutubeDL(object):
|
|||||||
if not self.params.get('consoletitle', False):
|
if not self.params.get('consoletitle', False):
|
||||||
return
|
return
|
||||||
if 'TERM' in os.environ:
|
if 'TERM' in os.environ:
|
||||||
write_string(u'\033[22t', self._screen_file)
|
# Save the title on stack
|
||||||
|
write_string(u'\033[22;0t', self._screen_file)
|
||||||
|
|
||||||
def restore_console_title(self):
|
def restore_console_title(self):
|
||||||
if not self.params.get('consoletitle', False):
|
if not self.params.get('consoletitle', False):
|
||||||
return
|
return
|
||||||
if 'TERM' in os.environ:
|
if 'TERM' in os.environ:
|
||||||
write_string(u'\033[23t', self._screen_file)
|
# Restore the title from stack
|
||||||
|
write_string(u'\033[23;0t', self._screen_file)
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
self.save_console_title()
|
self.save_console_title()
|
||||||
@@ -351,6 +361,8 @@ class YoutubeDL(object):
|
|||||||
def _match_entry(self, info_dict):
|
def _match_entry(self, info_dict):
|
||||||
""" Returns None iff the file should be downloaded """
|
""" Returns None iff the file should be downloaded """
|
||||||
|
|
||||||
|
if 'title' in info_dict:
|
||||||
|
# This can happen when we're just evaluating the playlist
|
||||||
title = info_dict['title']
|
title = info_dict['title']
|
||||||
matchtitle = self.params.get('matchtitle', False)
|
matchtitle = self.params.get('matchtitle', False)
|
||||||
if matchtitle:
|
if matchtitle:
|
||||||
@@ -370,8 +382,8 @@ class YoutubeDL(object):
|
|||||||
if age_limit < info_dict.get('age_limit', 0):
|
if age_limit < info_dict.get('age_limit', 0):
|
||||||
return u'Skipping "' + title + '" because it is age restricted'
|
return u'Skipping "' + title + '" because it is age restricted'
|
||||||
if self.in_download_archive(info_dict):
|
if self.in_download_archive(info_dict):
|
||||||
return (u'%(title)s has already been recorded in archive'
|
return (u'%s has already been recorded in archive'
|
||||||
% info_dict)
|
% info_dict.get('title', info_dict.get('id', u'video')))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -450,7 +462,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)
|
||||||
@@ -480,6 +492,12 @@ class YoutubeDL(object):
|
|||||||
'webpage_url': ie_result['webpage_url'],
|
'webpage_url': ie_result['webpage_url'],
|
||||||
'extractor_key': ie_result['extractor_key'],
|
'extractor_key': ie_result['extractor_key'],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reason = self._match_entry(entry)
|
||||||
|
if reason is not None:
|
||||||
|
self.to_screen(u'[download] ' + reason)
|
||||||
|
continue
|
||||||
|
|
||||||
entry_result = self.process_ie_result(entry,
|
entry_result = self.process_ie_result(entry,
|
||||||
download=download,
|
download=download,
|
||||||
extra_info=extra)
|
extra_info=extra)
|
||||||
@@ -635,7 +653,7 @@ class YoutubeDL(object):
|
|||||||
|
|
||||||
# Forced printings
|
# Forced printings
|
||||||
if self.params.get('forcetitle', False):
|
if self.params.get('forcetitle', False):
|
||||||
compat_print(info_dict['title'])
|
compat_print(info_dict['fulltitle'])
|
||||||
if self.params.get('forceid', False):
|
if self.params.get('forceid', False):
|
||||||
compat_print(info_dict['id'])
|
compat_print(info_dict['id'])
|
||||||
if self.params.get('forceurl', False):
|
if self.params.get('forceurl', False):
|
||||||
@@ -649,6 +667,8 @@ class YoutubeDL(object):
|
|||||||
compat_print(filename)
|
compat_print(filename)
|
||||||
if self.params.get('forceformat', False):
|
if self.params.get('forceformat', False):
|
||||||
compat_print(info_dict['format'])
|
compat_print(info_dict['format'])
|
||||||
|
if self.params.get('forcejson', False):
|
||||||
|
compat_print(json.dumps(info_dict))
|
||||||
|
|
||||||
# Do nothing else if in simulate mode
|
# Do nothing else if in simulate mode
|
||||||
if self.params.get('simulate', False):
|
if self.params.get('simulate', False):
|
||||||
@@ -711,7 +731,7 @@ class YoutubeDL(object):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if self.params.get('writeinfojson', False):
|
if self.params.get('writeinfojson', False):
|
||||||
infofn = filename + u'.info.json'
|
infofn = os.path.splitext(filename)[0] + u'.info.json'
|
||||||
self.report_writeinfojson(infofn)
|
self.report_writeinfojson(infofn)
|
||||||
try:
|
try:
|
||||||
json_info_dict = dict((k, v) for k, v in info_dict.items() if not k in ['urlhandle'])
|
json_info_dict = dict((k, v) for k, v in info_dict.items() if not k in ['urlhandle'])
|
||||||
@@ -804,7 +824,16 @@ class YoutubeDL(object):
|
|||||||
fn = self.params.get('download_archive')
|
fn = self.params.get('download_archive')
|
||||||
if fn is None:
|
if fn is None:
|
||||||
return False
|
return False
|
||||||
vid_id = info_dict['extractor'] + u' ' + info_dict['id']
|
extractor = info_dict.get('extractor_id')
|
||||||
|
if extractor is None:
|
||||||
|
if 'id' in info_dict:
|
||||||
|
extractor = info_dict.get('ie_key') # key in a playlist
|
||||||
|
if extractor is None:
|
||||||
|
return False # Incomplete video information
|
||||||
|
# Future-proof against any change in case
|
||||||
|
# and backwards compatibility with prior versions
|
||||||
|
extractor = extractor.lower()
|
||||||
|
vid_id = extractor + u' ' + info_dict['id']
|
||||||
try:
|
try:
|
||||||
with locked_file(fn, 'r', encoding='utf-8') as archive_file:
|
with locked_file(fn, 'r', encoding='utf-8') as archive_file:
|
||||||
for line in archive_file:
|
for line in archive_file:
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ __authors__ = (
|
|||||||
'Andras Elso',
|
'Andras Elso',
|
||||||
'Jelle van der Waa',
|
'Jelle van der Waa',
|
||||||
'Marcin Cieślak',
|
'Marcin Cieślak',
|
||||||
|
'Anton Larionov',
|
||||||
|
'Takuya Tsuchida',
|
||||||
)
|
)
|
||||||
|
|
||||||
__license__ = 'Public Domain'
|
__license__ = 'Public Domain'
|
||||||
@@ -306,6 +308,9 @@ def parseOpts(overrideArguments=None):
|
|||||||
verbosity.add_option('--get-format',
|
verbosity.add_option('--get-format',
|
||||||
action='store_true', dest='getformat',
|
action='store_true', dest='getformat',
|
||||||
help='simulate, quiet but print output format', default=False)
|
help='simulate, quiet but print output format', default=False)
|
||||||
|
verbosity.add_option('-j', '--dump-json',
|
||||||
|
action='store_true', dest='dumpjson',
|
||||||
|
help='simulate, quiet but print JSON information', default=False)
|
||||||
verbosity.add_option('--newline',
|
verbosity.add_option('--newline',
|
||||||
action='store_true', dest='progress_with_newline', help='output progress bar as new lines', default=False)
|
action='store_true', dest='progress_with_newline', help='output progress bar as new lines', default=False)
|
||||||
verbosity.add_option('--no-progress',
|
verbosity.add_option('--no-progress',
|
||||||
@@ -608,7 +613,7 @@ def _real_main(argv=None):
|
|||||||
'username': opts.username,
|
'username': opts.username,
|
||||||
'password': opts.password,
|
'password': opts.password,
|
||||||
'videopassword': opts.videopassword,
|
'videopassword': opts.videopassword,
|
||||||
'quiet': (opts.quiet or opts.geturl or opts.gettitle or opts.getid or opts.getthumbnail or opts.getdescription or opts.getfilename or opts.getformat),
|
'quiet': (opts.quiet or opts.geturl or opts.gettitle or opts.getid or opts.getthumbnail or opts.getdescription or opts.getfilename or opts.getformat or opts.dumpjson),
|
||||||
'forceurl': opts.geturl,
|
'forceurl': opts.geturl,
|
||||||
'forcetitle': opts.gettitle,
|
'forcetitle': opts.gettitle,
|
||||||
'forceid': opts.getid,
|
'forceid': opts.getid,
|
||||||
@@ -616,8 +621,9 @@ def _real_main(argv=None):
|
|||||||
'forcedescription': opts.getdescription,
|
'forcedescription': opts.getdescription,
|
||||||
'forcefilename': opts.getfilename,
|
'forcefilename': opts.getfilename,
|
||||||
'forceformat': opts.getformat,
|
'forceformat': opts.getformat,
|
||||||
|
'forcejson': opts.dumpjson,
|
||||||
'simulate': opts.simulate,
|
'simulate': opts.simulate,
|
||||||
'skip_download': (opts.skip_download or opts.simulate or opts.geturl or opts.gettitle or opts.getid or opts.getthumbnail or opts.getdescription or opts.getfilename or opts.getformat),
|
'skip_download': (opts.skip_download or opts.simulate or opts.geturl or opts.gettitle or opts.getid or opts.getthumbnail or opts.getdescription or opts.getfilename or opts.getformat or opts.dumpjson),
|
||||||
'format': opts.format,
|
'format': opts.format,
|
||||||
'format_limit': opts.format_limit,
|
'format_limit': opts.format_limit,
|
||||||
'listformats': opts.listformats,
|
'listformats': opts.listformats,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from .appletrailers import AppleTrailersIE
|
from .appletrailers import AppleTrailersIE
|
||||||
from .addanime import AddAnimeIE
|
from .addanime import AddAnimeIE
|
||||||
|
from .anitube import AnitubeIE
|
||||||
from .archiveorg import ArchiveOrgIE
|
from .archiveorg import ArchiveOrgIE
|
||||||
from .ard import ARDIE
|
from .ard import ARDIE
|
||||||
from .arte import (
|
from .arte import (
|
||||||
@@ -10,7 +11,7 @@ from .arte import (
|
|||||||
)
|
)
|
||||||
from .auengine import AUEngineIE
|
from .auengine import AUEngineIE
|
||||||
from .bambuser import BambuserIE, BambuserChannelIE
|
from .bambuser import BambuserIE, BambuserChannelIE
|
||||||
from .bandcamp import BandcampIE
|
from .bandcamp import BandcampIE, BandcampAlbumIE
|
||||||
from .bliptv import BlipTVIE, BlipTVUserIE
|
from .bliptv import BlipTVIE, BlipTVUserIE
|
||||||
from .bloomberg import BloombergIE
|
from .bloomberg import BloombergIE
|
||||||
from .breakcom import BreakIE
|
from .breakcom import BreakIE
|
||||||
@@ -19,12 +20,14 @@ from .c56 import C56IE
|
|||||||
from .canalplus import CanalplusIE
|
from .canalplus import CanalplusIE
|
||||||
from .canalc2 import Canalc2IE
|
from .canalc2 import Canalc2IE
|
||||||
from .cinemassacre import CinemassacreIE
|
from .cinemassacre import CinemassacreIE
|
||||||
|
from .clipfish import ClipfishIE
|
||||||
from .cnn import CNNIE
|
from .cnn import CNNIE
|
||||||
from .collegehumor import CollegeHumorIE
|
from .collegehumor import CollegeHumorIE
|
||||||
from .comedycentral import ComedyCentralIE
|
from .comedycentral import ComedyCentralIE
|
||||||
from .condenast import CondeNastIE
|
from .condenast import CondeNastIE
|
||||||
from .criterion import CriterionIE
|
from .criterion import CriterionIE
|
||||||
from .cspan import CSpanIE
|
from .cspan import CSpanIE
|
||||||
|
from .d8 import D8IE
|
||||||
from .dailymotion import (
|
from .dailymotion import (
|
||||||
DailymotionIE,
|
DailymotionIE,
|
||||||
DailymotionPlaylistIE,
|
DailymotionPlaylistIE,
|
||||||
@@ -96,6 +99,7 @@ from .nba import NBAIE
|
|||||||
from .nbc import NBCNewsIE
|
from .nbc import NBCNewsIE
|
||||||
from .newgrounds import NewgroundsIE
|
from .newgrounds import NewgroundsIE
|
||||||
from .nhl import NHLIE, NHLVideocenterIE
|
from .nhl import NHLIE, NHLVideocenterIE
|
||||||
|
from .niconico import NiconicoIE
|
||||||
from .nowvideo import NowVideoIE
|
from .nowvideo import NowVideoIE
|
||||||
from .ooyala import OoyalaIE
|
from .ooyala import OoyalaIE
|
||||||
from .orf import ORFIE
|
from .orf import ORFIE
|
||||||
@@ -126,12 +130,14 @@ from .spiegel import SpiegelIE
|
|||||||
from .stanfordoc import StanfordOpenClassroomIE
|
from .stanfordoc import StanfordOpenClassroomIE
|
||||||
from .statigram import StatigramIE
|
from .statigram import StatigramIE
|
||||||
from .steam import SteamIE
|
from .steam import SteamIE
|
||||||
|
from .streamcloud import StreamcloudIE
|
||||||
from .sztvhu import SztvHuIE
|
from .sztvhu import SztvHuIE
|
||||||
from .teamcoco import TeamcocoIE
|
from .teamcoco import TeamcocoIE
|
||||||
from .techtalks import TechTalksIE
|
from .techtalks import TechTalksIE
|
||||||
from .ted import TEDIE
|
from .ted import TEDIE
|
||||||
from .tf1 import TF1IE
|
from .tf1 import TF1IE
|
||||||
from .thisav import ThisAVIE
|
from .thisav import ThisAVIE
|
||||||
|
from .toutv import TouTvIE
|
||||||
from .traileraddict import TrailerAddictIE
|
from .traileraddict import TrailerAddictIE
|
||||||
from .trilulilu import TriluliluIE
|
from .trilulilu import TriluliluIE
|
||||||
from .tube8 import Tube8IE
|
from .tube8 import Tube8IE
|
||||||
@@ -152,6 +158,7 @@ from .videofyme import VideofyMeIE
|
|||||||
from .videopremium import VideoPremiumIE
|
from .videopremium import VideoPremiumIE
|
||||||
from .vimeo import VimeoIE, VimeoChannelIE
|
from .vimeo import VimeoIE, VimeoChannelIE
|
||||||
from .vine import VineIE
|
from .vine import VineIE
|
||||||
|
from .viki import VikiIE
|
||||||
from .vk import VKIE
|
from .vk import VKIE
|
||||||
from .wat import WatIE
|
from .wat import WatIE
|
||||||
from .websurg import WeBSurgIE
|
from .websurg import WeBSurgIE
|
||||||
|
|||||||
55
youtube_dl/extractor/anitube.py
Normal file
55
youtube_dl/extractor/anitube.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import re
|
||||||
|
import xml.etree.ElementTree
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
|
class AnitubeIE(InfoExtractor):
|
||||||
|
IE_NAME = u'anitube.se'
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?anitube\.se/video/(?P<id>\d+)'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
u'url': u'http://www.anitube.se/video/36621',
|
||||||
|
u'md5': u'59d0eeae28ea0bc8c05e7af429998d43',
|
||||||
|
u'file': u'36621.mp4',
|
||||||
|
u'info_dict': {
|
||||||
|
u'id': u'36621',
|
||||||
|
u'ext': u'mp4',
|
||||||
|
u'title': u'Recorder to Randoseru 01',
|
||||||
|
},
|
||||||
|
u'skip': u'Blocked in the US',
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
key = self._html_search_regex(r'http://www\.anitube\.se/embed/([A-Za-z0-9_-]*)',
|
||||||
|
webpage, u'key')
|
||||||
|
|
||||||
|
webpage_config = self._download_webpage('http://www.anitube.se/nuevo/econfig.php?key=%s' % key,
|
||||||
|
key)
|
||||||
|
config_xml = xml.etree.ElementTree.fromstring(webpage_config.encode('utf-8'))
|
||||||
|
|
||||||
|
video_title = config_xml.find('title').text
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
video_url = config_xml.find('file')
|
||||||
|
if video_url is not None:
|
||||||
|
formats.append({
|
||||||
|
'format_id': 'sd',
|
||||||
|
'url': video_url.text,
|
||||||
|
})
|
||||||
|
video_url = config_xml.find('filehd')
|
||||||
|
if video_url is not None:
|
||||||
|
formats.append({
|
||||||
|
'format_id': 'hd',
|
||||||
|
'url': video_url.text,
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': video_title,
|
||||||
|
'formats': formats
|
||||||
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import os.path
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
compat_urllib_parse_urlparse,
|
determine_ext,
|
||||||
|
ExtractorError,
|
||||||
)
|
)
|
||||||
|
|
||||||
class AUEngineIE(InfoExtractor):
|
class AUEngineIE(InfoExtractor):
|
||||||
@@ -25,22 +25,25 @@ class AUEngineIE(InfoExtractor):
|
|||||||
title = self._html_search_regex(r'<title>(?P<title>.+?)</title>',
|
title = self._html_search_regex(r'<title>(?P<title>.+?)</title>',
|
||||||
webpage, u'title')
|
webpage, u'title')
|
||||||
title = title.strip()
|
title = title.strip()
|
||||||
links = re.findall(r'[^A-Za-z0-9]?(?:file|url):\s*["\'](http[^\'"&]*)', webpage)
|
links = re.findall(r'\s(?:file|url):\s*["\']([^\'"]+)["\']', webpage)
|
||||||
links = [compat_urllib_parse.unquote(l) for l in links]
|
links = map(compat_urllib_parse.unquote, links)
|
||||||
|
|
||||||
|
thumbnail = None
|
||||||
|
video_url = None
|
||||||
for link in links:
|
for link in links:
|
||||||
root, pathext = os.path.splitext(compat_urllib_parse_urlparse(link).path)
|
if link.endswith('.png'):
|
||||||
if pathext == '.png':
|
|
||||||
thumbnail = link
|
thumbnail = link
|
||||||
elif pathext == '.mp4':
|
elif '/videos/' in link:
|
||||||
url = link
|
video_url = link
|
||||||
ext = pathext
|
if not video_url:
|
||||||
|
raise ExtractorError(u'Could not find video URL')
|
||||||
|
ext = u'.' + determine_ext(video_url)
|
||||||
if ext == title[-len(ext):]:
|
if ext == title[-len(ext):]:
|
||||||
title = title[:-len(ext)]
|
title = title[:-len(ext)]
|
||||||
ext = ext[1:]
|
|
||||||
return [{
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'url': url,
|
'url': video_url,
|
||||||
'ext': ext,
|
|
||||||
'title': title,
|
'title': title,
|
||||||
'thumbnail': thumbnail,
|
'thumbnail': thumbnail,
|
||||||
}]
|
}
|
||||||
|
|||||||
@@ -3,13 +3,16 @@ import re
|
|||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
|
compat_str,
|
||||||
|
compat_urlparse,
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class BandcampIE(InfoExtractor):
|
class BandcampIE(InfoExtractor):
|
||||||
|
IE_NAME = u'Bandcamp'
|
||||||
_VALID_URL = r'http://.*?\.bandcamp\.com/track/(?P<title>.*)'
|
_VALID_URL = r'http://.*?\.bandcamp\.com/track/(?P<title>.*)'
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
u'url': u'http://youtube-dl.bandcamp.com/track/youtube-dl-test-song',
|
u'url': u'http://youtube-dl.bandcamp.com/track/youtube-dl-test-song',
|
||||||
u'file': u'1812978515.mp3',
|
u'file': u'1812978515.mp3',
|
||||||
u'md5': u'cdeb30cdae1921719a3cbcab696ef53c',
|
u'md5': u'cdeb30cdae1921719a3cbcab696ef53c',
|
||||||
@@ -17,7 +20,7 @@ class BandcampIE(InfoExtractor):
|
|||||||
u"title": u"youtube-dl test song \"'/\\\u00e4\u21ad"
|
u"title": u"youtube-dl test song \"'/\\\u00e4\u21ad"
|
||||||
},
|
},
|
||||||
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'
|
||||||
}
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
@@ -26,6 +29,23 @@ class BandcampIE(InfoExtractor):
|
|||||||
# We get the link to the free download page
|
# We get the link to the free download page
|
||||||
m_download = re.search(r'freeDownloadPage: "(.*?)"', webpage)
|
m_download = re.search(r'freeDownloadPage: "(.*?)"', webpage)
|
||||||
if m_download is None:
|
if m_download is None:
|
||||||
|
m_trackinfo = re.search(r'trackinfo: (.+),\s*?\n', webpage)
|
||||||
|
if m_trackinfo:
|
||||||
|
json_code = m_trackinfo.group(1)
|
||||||
|
data = json.loads(json_code)
|
||||||
|
|
||||||
|
for d in data:
|
||||||
|
formats = [{
|
||||||
|
'format_id': 'format_id',
|
||||||
|
'url': format_url,
|
||||||
|
'ext': format_id.partition('-')[0]
|
||||||
|
} for format_id, format_url in sorted(d['file'].items())]
|
||||||
|
return {
|
||||||
|
'id': compat_str(d['id']),
|
||||||
|
'title': d['title'],
|
||||||
|
'formats': formats,
|
||||||
|
}
|
||||||
|
else:
|
||||||
raise ExtractorError(u'No free songs found')
|
raise ExtractorError(u'No free songs found')
|
||||||
|
|
||||||
download_link = m_download.group(1)
|
download_link = m_download.group(1)
|
||||||
@@ -61,3 +81,49 @@ class BandcampIE(InfoExtractor):
|
|||||||
}
|
}
|
||||||
|
|
||||||
return [track_info]
|
return [track_info]
|
||||||
|
|
||||||
|
|
||||||
|
class BandcampAlbumIE(InfoExtractor):
|
||||||
|
IE_NAME = u'Bandcamp:album'
|
||||||
|
_VALID_URL = r'http://.*?\.bandcamp\.com/album/(?P<title>.*)'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
u'url': u'http://blazo.bandcamp.com/album/jazz-format-mixtape-vol-1',
|
||||||
|
u'playlist': [
|
||||||
|
{
|
||||||
|
u'file': u'1353101989.mp3',
|
||||||
|
u'md5': u'39bc1eded3476e927c724321ddf116cf',
|
||||||
|
u'info_dict': {
|
||||||
|
u'title': u'Intro',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
u'file': u'38097443.mp3',
|
||||||
|
u'md5': u'1a2c32e2691474643e912cc6cd4bffaa',
|
||||||
|
u'info_dict': {
|
||||||
|
u'title': u'Kero One - Keep It Alive (Blazo remix)',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
u'params': {
|
||||||
|
u'playlistend': 2
|
||||||
|
},
|
||||||
|
u'skip': u'Bancamp imposes download limits. See test_playlists:test_bandcamp_album for the playlist test'
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
title = mobj.group('title')
|
||||||
|
webpage = self._download_webpage(url, title)
|
||||||
|
tracks_paths = re.findall(r'<a href="(.*?)" itemprop="url">', webpage)
|
||||||
|
if not tracks_paths:
|
||||||
|
raise ExtractorError(u'The page doesn\'t contain any track')
|
||||||
|
entries = [
|
||||||
|
self.url_result(compat_urlparse.urljoin(url, t_path), ie=BandcampIE.ie_key())
|
||||||
|
for t_path in tracks_paths]
|
||||||
|
title = self._search_regex(r'album_title : "(.*?)"', webpage, u'title')
|
||||||
|
return {
|
||||||
|
'_type': 'playlist',
|
||||||
|
'title': title,
|
||||||
|
'entries': entries,
|
||||||
|
}
|
||||||
|
|||||||
@@ -75,14 +75,17 @@ class BrightcoveIE(InfoExtractor):
|
|||||||
params = {'flashID': object_doc.attrib['id'],
|
params = {'flashID': object_doc.attrib['id'],
|
||||||
'playerID': find_xpath_attr(object_doc, './param', 'name', 'playerID').attrib['value'],
|
'playerID': find_xpath_attr(object_doc, './param', 'name', 'playerID').attrib['value'],
|
||||||
}
|
}
|
||||||
playerKey = find_xpath_attr(object_doc, './param', 'name', 'playerKey')
|
def find_param(name):
|
||||||
|
return find_xpath_attr(object_doc, './param', 'name', name)
|
||||||
|
playerKey = find_param('playerKey')
|
||||||
# Not all pages define this value
|
# Not all pages define this value
|
||||||
if playerKey is not None:
|
if playerKey is not None:
|
||||||
params['playerKey'] = playerKey.attrib['value']
|
params['playerKey'] = playerKey.attrib['value']
|
||||||
videoPlayer = find_xpath_attr(object_doc, './param', 'name', '@videoPlayer')
|
# The three fields hold the id of the video
|
||||||
|
videoPlayer = find_param('@videoPlayer') or find_param('videoId') or find_param('videoID')
|
||||||
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')
|
linkBase = find_param('linkBaseURL')
|
||||||
if linkBase is not None:
|
if linkBase is not None:
|
||||||
params['linkBaseURL'] = linkBase.attrib['value']
|
params['linkBaseURL'] = linkBase.attrib['value']
|
||||||
data = compat_urllib_parse.urlencode(params)
|
data = compat_urllib_parse.urlencode(params)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import xml.etree.ElementTree
|
|||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import unified_strdate
|
from ..utils import unified_strdate
|
||||||
|
|
||||||
|
|
||||||
class CanalplusIE(InfoExtractor):
|
class CanalplusIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(www\.canalplus\.fr/.*?/(?P<path>.*)|player\.canalplus\.fr/#/(?P<id>\d+))'
|
_VALID_URL = r'https?://(www\.canalplus\.fr/.*?/(?P<path>.*)|player\.canalplus\.fr/#/(?P<id>\d+))'
|
||||||
_VIDEO_INFO_TEMPLATE = 'http://service.canal-plus.com/video/rest/getVideosLiees/cplus/%s'
|
_VIDEO_INFO_TEMPLATE = 'http://service.canal-plus.com/video/rest/getVideosLiees/cplus/%s'
|
||||||
@@ -25,7 +26,7 @@ class CanalplusIE(InfoExtractor):
|
|||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
video_id = mobj.group('id')
|
video_id = mobj.groupdict().get('id')
|
||||||
if video_id is None:
|
if video_id is None:
|
||||||
webpage = self._download_webpage(url, mobj.group('path'))
|
webpage = self._download_webpage(url, mobj.group('path'))
|
||||||
video_id = self._search_regex(r'videoId = "(\d+)";', webpage, u'video id')
|
video_id = self._search_regex(r'videoId = "(\d+)";', webpage, u'video id')
|
||||||
|
|||||||
53
youtube_dl/extractor/clipfish.py
Normal file
53
youtube_dl/extractor/clipfish.py
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import re
|
||||||
|
import time
|
||||||
|
import xml.etree.ElementTree
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
|
class ClipfishIE(InfoExtractor):
|
||||||
|
IE_NAME = u'clipfish'
|
||||||
|
|
||||||
|
_VALID_URL = r'^https?://(?:www\.)?clipfish\.de/.*?/video/(?P<id>[0-9]+)/'
|
||||||
|
_TEST = {
|
||||||
|
u'url': u'http://www.clipfish.de/special/supertalent/video/4028320/supertalent-2013-ivana-opacak-singt-nobodys-perfect/',
|
||||||
|
u'file': u'4028320.f4v',
|
||||||
|
u'md5': u'5e38bda8c329fbfb42be0386a3f5a382',
|
||||||
|
u'info_dict': {
|
||||||
|
u'title': u'Supertalent 2013: Ivana Opacak singt Nobody\'s Perfect',
|
||||||
|
u'duration': 399,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group(1)
|
||||||
|
|
||||||
|
info_url = ('http://www.clipfish.de/devxml/videoinfo/%s?ts=%d' %
|
||||||
|
(video_id, int(time.time())))
|
||||||
|
info_xml = self._download_webpage(
|
||||||
|
info_url, video_id, note=u'Downloading info page')
|
||||||
|
doc = xml.etree.ElementTree.fromstring(info_xml)
|
||||||
|
title = doc.find('title').text
|
||||||
|
video_url = doc.find('filename').text
|
||||||
|
thumbnail = doc.find('imageurl').text
|
||||||
|
duration_str = doc.find('duration').text
|
||||||
|
m = re.match(
|
||||||
|
r'^(?P<hours>[0-9]+):(?P<minutes>[0-9]{2}):(?P<seconds>[0-9]{2}):(?P<ms>[0-9]*)$',
|
||||||
|
duration_str)
|
||||||
|
if m:
|
||||||
|
duration = (
|
||||||
|
(int(m.group('hours')) * 60 * 60) +
|
||||||
|
(int(m.group('minutes')) * 60) +
|
||||||
|
(int(m.group('seconds')))
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
duration = None
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'url': video_url,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'duration': duration,
|
||||||
|
}
|
||||||
@@ -71,10 +71,8 @@ class CollegeHumorIE(InfoExtractor):
|
|||||||
|
|
||||||
adoc = xml.etree.ElementTree.fromstring(manifestXml)
|
adoc = xml.etree.ElementTree.fromstring(manifestXml)
|
||||||
try:
|
try:
|
||||||
media_node = adoc.findall('./{http://ns.adobe.com/f4m/1.0}media')[0]
|
|
||||||
node_id = media_node.attrib['url']
|
|
||||||
video_id = adoc.findall('./{http://ns.adobe.com/f4m/1.0}id')[0].text
|
video_id = adoc.findall('./{http://ns.adobe.com/f4m/1.0}id')[0].text
|
||||||
except IndexError as err:
|
except IndexError:
|
||||||
raise ExtractorError(u'Invalid manifest file')
|
raise ExtractorError(u'Invalid manifest file')
|
||||||
url_pr = compat_urllib_parse_urlparse(info['thumbnail'])
|
url_pr = compat_urllib_parse_urlparse(info['thumbnail'])
|
||||||
info['url'] = url_pr.scheme + '://' + url_pr.netloc + video_id[:-2].replace('.csmil','').replace(',','')
|
info['url'] = url_pr.scheme + '://' + url_pr.netloc + video_id[:-2].replace('.csmil','').replace(',','')
|
||||||
|
|||||||
@@ -229,12 +229,14 @@ class InfoExtractor(object):
|
|||||||
self.to_screen(u'Logging in')
|
self.to_screen(u'Logging in')
|
||||||
|
|
||||||
#Methods for following #608
|
#Methods for following #608
|
||||||
def url_result(self, url, ie=None):
|
def url_result(self, url, ie=None, video_id=None):
|
||||||
"""Returns a url that points to a page that should be processed"""
|
"""Returns a url that points to a page that should be processed"""
|
||||||
#TODO: ie should be the class used for getting the info
|
#TODO: ie should be the class used for getting the info
|
||||||
video_info = {'_type': 'url',
|
video_info = {'_type': 'url',
|
||||||
'url': url,
|
'url': url,
|
||||||
'ie_key': ie}
|
'ie_key': ie}
|
||||||
|
if video_id is not None:
|
||||||
|
video_info['id'] = video_id
|
||||||
return video_info
|
return video_info
|
||||||
def playlist_result(self, entries, playlist_id=None, playlist_title=None):
|
def playlist_result(self, entries, playlist_id=None, playlist_title=None):
|
||||||
"""Returns a playlist"""
|
"""Returns a playlist"""
|
||||||
@@ -350,6 +352,17 @@ class InfoExtractor(object):
|
|||||||
if secure: regexes = self._og_regexes('video:secure_url') + regexes
|
if secure: regexes = self._og_regexes('video:secure_url') + regexes
|
||||||
return self._html_search_regex(regexes, html, name, **kargs)
|
return self._html_search_regex(regexes, html, name, **kargs)
|
||||||
|
|
||||||
|
def _html_search_meta(self, name, html, display_name=None):
|
||||||
|
if display_name is None:
|
||||||
|
display_name = name
|
||||||
|
return self._html_search_regex(
|
||||||
|
r'''(?ix)<meta(?=[^>]+(?:name|property)=["\']%s["\'])
|
||||||
|
[^>]+content=["\']([^"\']+)["\']''' % re.escape(name),
|
||||||
|
html, display_name, fatal=False)
|
||||||
|
|
||||||
|
def _dc_search_uploader(self, html):
|
||||||
|
return self._html_search_meta('dc.creator', html, 'uploader')
|
||||||
|
|
||||||
def _rta_search(self, html):
|
def _rta_search(self, html):
|
||||||
# See http://www.rtalabel.org/index.php?content=howtofaq#single
|
# See http://www.rtalabel.org/index.php?content=howtofaq#single
|
||||||
if re.search(r'(?ix)<meta\s+name="rating"\s+'
|
if re.search(r'(?ix)<meta\s+name="rating"\s+'
|
||||||
@@ -358,6 +371,23 @@ class InfoExtractor(object):
|
|||||||
return 18
|
return 18
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
def _media_rating_search(self, html):
|
||||||
|
# See http://www.tjg-designs.com/WP/metadata-code-examples-adding-metadata-to-your-web-pages/
|
||||||
|
rating = self._html_search_meta('rating', html)
|
||||||
|
|
||||||
|
if not rating:
|
||||||
|
return None
|
||||||
|
|
||||||
|
RATING_TABLE = {
|
||||||
|
'safe for kids': 0,
|
||||||
|
'general': 8,
|
||||||
|
'14 years': 14,
|
||||||
|
'mature': 17,
|
||||||
|
'restricted': 19,
|
||||||
|
}
|
||||||
|
return RATING_TABLE.get(rating.lower(), None)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class SearchInfoExtractor(InfoExtractor):
|
class SearchInfoExtractor(InfoExtractor):
|
||||||
"""
|
"""
|
||||||
|
|||||||
22
youtube_dl/extractor/d8.py
Normal file
22
youtube_dl/extractor/d8.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# encoding: utf-8
|
||||||
|
from .canalplus import CanalplusIE
|
||||||
|
|
||||||
|
|
||||||
|
class D8IE(CanalplusIE):
|
||||||
|
_VALID_URL = r'https?://www\.d8\.tv/.*?/(?P<path>.*)'
|
||||||
|
_VIDEO_INFO_TEMPLATE = 'http://service.canal-plus.com/video/rest/getVideosLiees/d8/%s'
|
||||||
|
IE_NAME = u'd8.tv'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
u'url': u'http://www.d8.tv/d8-docs-mags/pid6589-d8-campagne-intime.html',
|
||||||
|
u'file': u'966289.flv',
|
||||||
|
u'info_dict': {
|
||||||
|
u'title': u'Campagne intime - Documentaire exceptionnel',
|
||||||
|
u'description': u'md5:d2643b799fb190846ae09c61e59a859f',
|
||||||
|
u'upload_date': u'20131108',
|
||||||
|
},
|
||||||
|
u'params': {
|
||||||
|
# rtmp
|
||||||
|
u'skip_download': True,
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import itertools
|
|
||||||
import json
|
import json
|
||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class EscapistIE(InfoExtractor):
|
class EscapistIE(InfoExtractor):
|
||||||
_VALID_URL = r'^(https?://)?(www\.)?escapistmagazine\.com/videos/view/(?P<showname>[^/]+)/(?P<episode>[^/?]+)[/?]?.*$'
|
_VALID_URL = r'^https?://?(www\.)?escapistmagazine\.com/videos/view/(?P<showname>[^/]+)/(?P<episode>[^/?]+)[/?]?.*$'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
u'url': u'http://www.escapistmagazine.com/videos/view/the-escapist-presents/6618-Breaking-Down-Baldurs-Gate',
|
u'url': u'http://www.escapistmagazine.com/videos/view/the-escapist-presents/6618-Breaking-Down-Baldurs-Gate',
|
||||||
u'file': u'6618-Breaking-Down-Baldurs-Gate.mp4',
|
u'file': u'6618-Breaking-Down-Baldurs-Gate.mp4',
|
||||||
u'md5': u'c6793dbda81388f4264c1ba18684a74d',
|
u'md5': u'ab3a706c681efca53f0a35f1415cf0d1',
|
||||||
u'info_dict': {
|
u'info_dict': {
|
||||||
u"description": u"Baldur's Gate: Original, Modded or Enhanced Edition? I'll break down what you can expect from the new Baldur's Gate: Enhanced Edition.",
|
u"description": u"Baldur's Gate: Original, Modded or Enhanced Edition? I'll break down what you can expect from the new Baldur's Gate: Enhanced Edition.",
|
||||||
u"uploader": u"the-escapist-presents",
|
u"uploader": u"the-escapist-presents",
|
||||||
@@ -25,28 +25,32 @@ class EscapistIE(InfoExtractor):
|
|||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
if mobj is None:
|
|
||||||
raise ExtractorError(u'Invalid URL: %s' % url)
|
|
||||||
showName = mobj.group('showname')
|
showName = mobj.group('showname')
|
||||||
videoId = mobj.group('episode')
|
videoId = mobj.group('episode')
|
||||||
|
|
||||||
self.report_extraction(videoId)
|
self.report_extraction(videoId)
|
||||||
webpage = self._download_webpage(url, videoId)
|
webpage = self._download_webpage(url, videoId)
|
||||||
|
|
||||||
videoDesc = self._html_search_regex('<meta name="description" content="([^"]*)"',
|
videoDesc = self._html_search_regex(
|
||||||
|
r'<meta name="description" content="([^"]*)"',
|
||||||
webpage, u'description', fatal=False)
|
webpage, u'description', fatal=False)
|
||||||
|
|
||||||
playerUrl = self._og_search_video_url(webpage, name='player url')
|
playerUrl = self._og_search_video_url(webpage, name=u'player URL')
|
||||||
|
|
||||||
title = self._html_search_regex('<meta name="title" content="([^"]*)"',
|
title = self._html_search_regex(
|
||||||
webpage, u'player url').split(' : ')[-1]
|
r'<meta name="title" content="([^"]*)"',
|
||||||
|
webpage, u'title').split(' : ')[-1]
|
||||||
|
|
||||||
configUrl = self._search_regex('config=(.*)$', playerUrl, u'config url')
|
configUrl = self._search_regex('config=(.*)$', playerUrl, u'config URL')
|
||||||
configUrl = compat_urllib_parse.unquote(configUrl)
|
configUrl = compat_urllib_parse.unquote(configUrl)
|
||||||
|
|
||||||
configJSON = self._download_webpage(configUrl, videoId,
|
formats = []
|
||||||
u'Downloading configuration',
|
|
||||||
u'unable to download configuration')
|
def _add_format(name, cfgurl):
|
||||||
|
configJSON = self._download_webpage(
|
||||||
|
cfgurl, videoId,
|
||||||
|
u'Downloading ' + name + ' configuration',
|
||||||
|
u'Unable to download ' + name + ' configuration')
|
||||||
|
|
||||||
# Technically, it's JavaScript, not JSON
|
# Technically, it's JavaScript, not JSON
|
||||||
configJSON = configJSON.replace("'", '"')
|
configJSON = configJSON.replace("'", '"')
|
||||||
@@ -55,20 +59,26 @@ class EscapistIE(InfoExtractor):
|
|||||||
config = json.loads(configJSON)
|
config = json.loads(configJSON)
|
||||||
except (ValueError,) as err:
|
except (ValueError,) as err:
|
||||||
raise ExtractorError(u'Invalid JSON in configuration file: ' + compat_str(err))
|
raise ExtractorError(u'Invalid JSON in configuration file: ' + compat_str(err))
|
||||||
|
|
||||||
playlist = config['playlist']
|
playlist = config['playlist']
|
||||||
videoUrl = playlist[1]['url']
|
formats.append({
|
||||||
|
'url': playlist[1]['url'],
|
||||||
|
'format_id': name,
|
||||||
|
})
|
||||||
|
|
||||||
info = {
|
_add_format(u'normal', configUrl)
|
||||||
|
hq_url = (configUrl +
|
||||||
|
('&hq=1' if '?' in configUrl else configUrl + '?hq=1'))
|
||||||
|
try:
|
||||||
|
_add_format(u'hq', hq_url)
|
||||||
|
except ExtractorError:
|
||||||
|
pass # That's fine, we'll just use normal quality
|
||||||
|
|
||||||
|
return {
|
||||||
'id': videoId,
|
'id': videoId,
|
||||||
'url': videoUrl,
|
'formats': formats,
|
||||||
'uploader': showName,
|
'uploader': showName,
|
||||||
'upload_date': None,
|
|
||||||
'title': title,
|
'title': title,
|
||||||
'ext': 'mp4',
|
|
||||||
'thumbnail': self._og_search_thumbnail(webpage),
|
'thumbnail': self._og_search_thumbnail(webpage),
|
||||||
'description': videoDesc,
|
'description': videoDesc,
|
||||||
'player_url': playerUrl,
|
'player_url': playerUrl,
|
||||||
}
|
}
|
||||||
|
|
||||||
return [info]
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import json
|
import json
|
||||||
import netrc
|
|
||||||
import re
|
import re
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ class FKTVIE(InfoExtractor):
|
|||||||
for i, _ in enumerate(files, 1):
|
for i, _ in enumerate(files, 1):
|
||||||
video_id = '%04d%d' % (episode, i)
|
video_id = '%04d%d' % (episode, i)
|
||||||
video_url = 'http://dl%d.fernsehkritik.tv/fernsehkritik%d%s.flv' % (server, episode, '' if i == 1 else '-%d' % i)
|
video_url = 'http://dl%d.fernsehkritik.tv/fernsehkritik%d%s.flv' % (server, episode, '' if i == 1 else '-%d' % i)
|
||||||
video_title = 'Fernsehkritik %d.%d' % (episode, i)
|
|
||||||
videos.append({
|
videos.append({
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'url': video_url,
|
'url': video_url,
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class GameSpotIE(InfoExtractor):
|
|||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
page_id = video_id = mobj.group('page_id')
|
page_id = mobj.group('page_id')
|
||||||
webpage = self._download_webpage(url, page_id)
|
webpage = self._download_webpage(url, page_id)
|
||||||
data_video_json = self._search_regex(r'data-video=\'(.*?)\'', webpage, u'data video')
|
data_video_json = self._search_regex(r'data-video=\'(.*?)\'', webpage, u'data video')
|
||||||
data_video = json.loads(unescapeHTML(data_video_json))
|
data_video = json.loads(unescapeHTML(data_video_json))
|
||||||
|
|||||||
@@ -162,6 +162,16 @@ class GenericIE(InfoExtractor):
|
|||||||
raise ExtractorError(u'Failed to download URL: %s' % url)
|
raise ExtractorError(u'Failed to download URL: %s' % url)
|
||||||
|
|
||||||
self.report_extraction(video_id)
|
self.report_extraction(video_id)
|
||||||
|
|
||||||
|
# it's tempting to parse this further, but you would
|
||||||
|
# have to take into account all the variations like
|
||||||
|
# Video Title - Site Name
|
||||||
|
# Site Name | Video Title
|
||||||
|
# Video Title - Tagline | Site Name
|
||||||
|
# and so on and so forth; it's just not practical
|
||||||
|
video_title = self._html_search_regex(r'<title>(.*)</title>',
|
||||||
|
webpage, u'video title', default=u'video', flags=re.DOTALL)
|
||||||
|
|
||||||
# Look for BrightCove:
|
# Look for BrightCove:
|
||||||
bc_url = BrightcoveIE._extract_brightcove_url(webpage)
|
bc_url = BrightcoveIE._extract_brightcove_url(webpage)
|
||||||
if bc_url is not None:
|
if bc_url is not None:
|
||||||
@@ -177,17 +187,20 @@ class GenericIE(InfoExtractor):
|
|||||||
return self.url_result(surl, 'Vimeo')
|
return self.url_result(surl, 'Vimeo')
|
||||||
|
|
||||||
# Look for embedded YouTube player
|
# Look for embedded YouTube player
|
||||||
mobj = re.search(
|
matches = re.findall(
|
||||||
r'<iframe[^>]+?src=(["\'])(?P<url>https?://(?:www\.)?youtube.com/embed/.+?)\1', webpage)
|
r'<iframe[^>]+?src=(["\'])(?P<url>(?:https?:)?//(?:www\.)?youtube.com/embed/.+?)\1', webpage)
|
||||||
if mobj:
|
if matches:
|
||||||
surl = unescapeHTML(mobj.group(u'url'))
|
urlrs = [self.url_result(unescapeHTML(tuppl[1]), 'Youtube')
|
||||||
return self.url_result(surl, 'Youtube')
|
for tuppl in matches]
|
||||||
|
return self.playlist_result(
|
||||||
|
urlrs, playlist_id=video_id, playlist_title=video_title)
|
||||||
|
|
||||||
# Look for Bandcamp pages with custom domain
|
# Look for Bandcamp pages with custom domain
|
||||||
mobj = re.search(r'<meta property="og:url"[^>]*?content="(.*?bandcamp\.com.*?)"', webpage)
|
mobj = re.search(r'<meta property="og:url"[^>]*?content="(.*?bandcamp\.com.*?)"', webpage)
|
||||||
if mobj is not None:
|
if mobj is not None:
|
||||||
burl = unescapeHTML(mobj.group(1))
|
burl = unescapeHTML(mobj.group(1))
|
||||||
return self.url_result(burl, 'Bandcamp')
|
# Don't set the extractor because it can be a track url or an album
|
||||||
|
return self.url_result(burl)
|
||||||
|
|
||||||
# Start with something easy: JW Player in SWFObject
|
# Start with something easy: JW Player in SWFObject
|
||||||
mobj = re.search(r'flashvars: [\'"](?:.*&)?file=(http[^\'"&]*)', webpage)
|
mobj = re.search(r'flashvars: [\'"](?:.*&)?file=(http[^\'"&]*)', webpage)
|
||||||
@@ -226,15 +239,6 @@ class GenericIE(InfoExtractor):
|
|||||||
video_extension = os.path.splitext(video_id)[1][1:]
|
video_extension = os.path.splitext(video_id)[1][1:]
|
||||||
video_id = os.path.splitext(video_id)[0]
|
video_id = os.path.splitext(video_id)[0]
|
||||||
|
|
||||||
# it's tempting to parse this further, but you would
|
|
||||||
# have to take into account all the variations like
|
|
||||||
# Video Title - Site Name
|
|
||||||
# Site Name | Video Title
|
|
||||||
# Video Title - Tagline | Site Name
|
|
||||||
# and so on and so forth; it's just not practical
|
|
||||||
video_title = self._html_search_regex(r'<title>(.*)</title>',
|
|
||||||
webpage, u'video title', default=u'video', flags=re.DOTALL)
|
|
||||||
|
|
||||||
# video uploader is domain name
|
# video uploader is domain name
|
||||||
video_uploader = self._search_regex(r'(?:https?://)?([^/]*)/.*',
|
video_uploader = self._search_regex(r'(?:https?://)?([^/]*)/.*',
|
||||||
url, u'video uploader')
|
url, u'video uploader')
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ class HowcastIE(InfoExtractor):
|
|||||||
_TEST = {
|
_TEST = {
|
||||||
u'url': u'http://www.howcast.com/videos/390161-How-to-Tie-a-Square-Knot-Properly',
|
u'url': u'http://www.howcast.com/videos/390161-How-to-Tie-a-Square-Knot-Properly',
|
||||||
u'file': u'390161.mp4',
|
u'file': u'390161.mp4',
|
||||||
u'md5': u'1d7ba54e2c9d7dc6935ef39e00529138',
|
u'md5': u'8b743df908c42f60cf6496586c7f12c3',
|
||||||
u'info_dict': {
|
u'info_dict': {
|
||||||
u"description": u"The square knot, also known as the reef knot, is one of the oldest, most basic knots to tie, and can be used in many different ways. Here's the proper way to tie a square knot.",
|
u"description": u"The square knot, also known as the reef knot, is one of the oldest, most basic knots to tie, and can be used in many different ways. Here's the proper way to tie a square knot.",
|
||||||
u"title": u"How to Tie a Square Knot Properly"
|
u"title": u"How to Tie a Square Knot Properly"
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class JeuxVideoIE(InfoExtractor):
|
|||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
title = re.match(self._VALID_URL, url).group(1)
|
title = mobj.group(1)
|
||||||
webpage = self._download_webpage(url, title)
|
webpage = self._download_webpage(url, title)
|
||||||
xml_link = self._html_search_regex(
|
xml_link = self._html_search_regex(
|
||||||
r'<param name="flashvars" value="config=(.*?)" />',
|
r'<param name="flashvars" value="config=(.*?)" />',
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ from .common import InfoExtractor
|
|||||||
from ..utils import (
|
from ..utils import (
|
||||||
compat_urllib_parse_urlparse,
|
compat_urllib_parse_urlparse,
|
||||||
compat_urlparse,
|
compat_urlparse,
|
||||||
get_meta_content,
|
|
||||||
xpath_with_ns,
|
xpath_with_ns,
|
||||||
ExtractorError,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ class MTVIE(InfoExtractor):
|
|||||||
if '/error_country_block.swf' in metadataXml:
|
if '/error_country_block.swf' in metadataXml:
|
||||||
raise ExtractorError(u'This video is not available from your country.', expected=True)
|
raise ExtractorError(u'This video is not available from your country.', expected=True)
|
||||||
mdoc = xml.etree.ElementTree.fromstring(metadataXml.encode('utf-8'))
|
mdoc = xml.etree.ElementTree.fromstring(metadataXml.encode('utf-8'))
|
||||||
renditions = mdoc.findall('.//rendition')
|
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
for rendition in mdoc.findall('.//rendition'):
|
for rendition in mdoc.findall('.//rendition'):
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ class NHLIE(NHLBaseInfoExtractor):
|
|||||||
|
|
||||||
class NHLVideocenterIE(NHLBaseInfoExtractor):
|
class NHLVideocenterIE(NHLBaseInfoExtractor):
|
||||||
IE_NAME = u'nhl.com:videocenter'
|
IE_NAME = u'nhl.com:videocenter'
|
||||||
IE_DESC = u'Download the first 12 videos from a videocenter category'
|
IE_DESC = u'NHL videocenter category'
|
||||||
_VALID_URL = r'https?://video\.(?P<team>[^.]*)\.nhl\.com/videocenter/(console\?.*?catid=(?P<catid>[^&]+))?'
|
_VALID_URL = r'https?://video\.(?P<team>[^.]*)\.nhl\.com/videocenter/(console\?.*?catid=(?P<catid>[^&]+))?'
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
131
youtube_dl/extractor/niconico.py
Normal file
131
youtube_dl/extractor/niconico.py
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
# encoding: utf-8
|
||||||
|
|
||||||
|
import re
|
||||||
|
import socket
|
||||||
|
import xml.etree.ElementTree
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
compat_http_client,
|
||||||
|
compat_urllib_error,
|
||||||
|
compat_urllib_parse,
|
||||||
|
compat_urllib_request,
|
||||||
|
compat_urlparse,
|
||||||
|
compat_str,
|
||||||
|
|
||||||
|
ExtractorError,
|
||||||
|
unified_strdate,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class NiconicoIE(InfoExtractor):
|
||||||
|
IE_NAME = u'niconico'
|
||||||
|
IE_DESC = u'ニコニコ動画'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
u'url': u'http://www.nicovideo.jp/watch/sm22312215',
|
||||||
|
u'file': u'sm22312215.mp4',
|
||||||
|
u'md5': u'd1a75c0823e2f629128c43e1212760f9',
|
||||||
|
u'info_dict': {
|
||||||
|
u'title': u'Big Buck Bunny',
|
||||||
|
u'uploader': u'takuya0301',
|
||||||
|
u'uploader_id': u'2698420',
|
||||||
|
u'upload_date': u'20131123',
|
||||||
|
u'description': u'(c) copyright 2008, Blender Foundation / www.bigbuckbunny.org',
|
||||||
|
},
|
||||||
|
u'params': {
|
||||||
|
u'username': u'ydl.niconico@gmail.com',
|
||||||
|
u'password': u'youtube-dl',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_VALID_URL = r'^https?://(?:www\.|secure\.)?nicovideo\.jp/watch/([a-z][a-z][0-9]+)(?:.*)$'
|
||||||
|
_NETRC_MACHINE = 'niconico'
|
||||||
|
# If True it will raise an error if no login info is provided
|
||||||
|
_LOGIN_REQUIRED = True
|
||||||
|
|
||||||
|
def _real_initialize(self):
|
||||||
|
self._login()
|
||||||
|
|
||||||
|
def _login(self):
|
||||||
|
(username, password) = self._get_login_info()
|
||||||
|
# No authentication to be performed
|
||||||
|
if username is None:
|
||||||
|
if self._LOGIN_REQUIRED:
|
||||||
|
raise ExtractorError(u'No login info available, needed for using %s.' % self.IE_NAME, expected=True)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Log in
|
||||||
|
login_form_strs = {
|
||||||
|
u'mail': username,
|
||||||
|
u'password': password,
|
||||||
|
}
|
||||||
|
# Convert to UTF-8 *before* urlencode because Python 2.x's urlencode
|
||||||
|
# chokes on unicode
|
||||||
|
login_form = dict((k.encode('utf-8'), v.encode('utf-8')) for k,v in login_form_strs.items())
|
||||||
|
login_data = compat_urllib_parse.urlencode(login_form).encode('utf-8')
|
||||||
|
request = compat_urllib_request.Request(
|
||||||
|
u'https://secure.nicovideo.jp/secure/login', login_data)
|
||||||
|
login_results = self._download_webpage(
|
||||||
|
request, u'', note=u'Logging in', errnote=u'Unable to log in')
|
||||||
|
if re.search(r'(?i)<h1 class="mb8p4">Log in error</h1>', login_results) is not None:
|
||||||
|
self._downloader.report_warning(u'unable to log in: bad username or password')
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group(1)
|
||||||
|
|
||||||
|
# Get video webpage. We are not actually interested in it, but need
|
||||||
|
# the cookies in order to be able to download the info webpage
|
||||||
|
self._download_webpage('http://www.nicovideo.jp/watch/' + video_id, video_id)
|
||||||
|
|
||||||
|
video_info_webpage = self._download_webpage(
|
||||||
|
'http://ext.nicovideo.jp/api/getthumbinfo/' + video_id, video_id,
|
||||||
|
note=u'Downloading video info page')
|
||||||
|
|
||||||
|
# Get flv info
|
||||||
|
flv_info_webpage = self._download_webpage(
|
||||||
|
u'http://flapi.nicovideo.jp/api/getflv?v=' + video_id,
|
||||||
|
video_id, u'Downloading flv info')
|
||||||
|
video_real_url = compat_urlparse.parse_qs(flv_info_webpage)['url'][0]
|
||||||
|
|
||||||
|
# Start extracting information
|
||||||
|
video_info = xml.etree.ElementTree.fromstring(video_info_webpage)
|
||||||
|
video_title = video_info.find('.//title').text
|
||||||
|
video_extension = video_info.find('.//movie_type').text
|
||||||
|
video_format = video_extension.upper()
|
||||||
|
video_thumbnail = video_info.find('.//thumbnail_url').text
|
||||||
|
video_description = video_info.find('.//description').text
|
||||||
|
video_uploader_id = video_info.find('.//user_id').text
|
||||||
|
video_upload_date = unified_strdate(video_info.find('.//first_retrieve').text.split('+')[0])
|
||||||
|
video_view_count = video_info.find('.//view_counter').text
|
||||||
|
video_webpage_url = video_info.find('.//watch_url').text
|
||||||
|
|
||||||
|
# uploader
|
||||||
|
video_uploader = video_uploader_id
|
||||||
|
url = 'http://seiga.nicovideo.jp/api/user/info?id=' + video_uploader_id
|
||||||
|
try:
|
||||||
|
user_info_webpage = self._download_webpage(
|
||||||
|
url, video_id, note=u'Downloading user information')
|
||||||
|
except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
|
||||||
|
self._downloader.report_warning(u'Unable to download user info webpage: %s' % compat_str(err))
|
||||||
|
else:
|
||||||
|
user_info = xml.etree.ElementTree.fromstring(user_info_webpage)
|
||||||
|
video_uploader = user_info.find('.//nickname').text
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'url': video_real_url,
|
||||||
|
'title': video_title,
|
||||||
|
'ext': video_extension,
|
||||||
|
'format': video_format,
|
||||||
|
'thumbnail': video_thumbnail,
|
||||||
|
'description': video_description,
|
||||||
|
'uploader': video_uploader,
|
||||||
|
'upload_date': video_upload_date,
|
||||||
|
'uploader_id': video_uploader_id,
|
||||||
|
'view_count': video_view_count,
|
||||||
|
'webpage_url': video_webpage_url,
|
||||||
|
}
|
||||||
@@ -6,7 +6,6 @@ from ..utils import (
|
|||||||
compat_urllib_parse_urlparse,
|
compat_urllib_parse_urlparse,
|
||||||
compat_urllib_request,
|
compat_urllib_request,
|
||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
unescapeHTML,
|
|
||||||
)
|
)
|
||||||
from ..aes import (
|
from ..aes import (
|
||||||
aes_decrypt_text
|
aes_decrypt_text
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ class SoundcloudIE(InfoExtractor):
|
|||||||
]
|
]
|
||||||
|
|
||||||
_CLIENT_ID = 'b45b1aa10f1ac2941910a7f0d10f8e28'
|
_CLIENT_ID = 'b45b1aa10f1ac2941910a7f0d10f8e28'
|
||||||
|
_IPHONE_CLIENT_ID = '376f225bf427445fc4bfb6b99b72e0bf'
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def suitable(cls, url):
|
def suitable(cls, url):
|
||||||
@@ -83,7 +84,6 @@ class SoundcloudIE(InfoExtractor):
|
|||||||
thumbnail = thumbnail.replace('-large', '-t500x500')
|
thumbnail = thumbnail.replace('-large', '-t500x500')
|
||||||
result = {
|
result = {
|
||||||
'id': track_id,
|
'id': track_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']),
|
||||||
'title': info['title'],
|
'title': info['title'],
|
||||||
@@ -92,19 +92,29 @@ class SoundcloudIE(InfoExtractor):
|
|||||||
'thumbnail': thumbnail,
|
'thumbnail': thumbnail,
|
||||||
}
|
}
|
||||||
if info.get('downloadable', False):
|
if info.get('downloadable', False):
|
||||||
|
# We can build a direct link to the song
|
||||||
result['url'] = 'https://api.soundcloud.com/tracks/{0}/download?client_id={1}'.format(track_id, self._CLIENT_ID)
|
result['url'] = 'https://api.soundcloud.com/tracks/{0}/download?client_id={1}'.format(track_id, self._CLIENT_ID)
|
||||||
if not info.get('streamable', False):
|
else:
|
||||||
# We have to get the rtmp url
|
# We have to retrieve the url
|
||||||
stream_json = self._download_webpage(
|
stream_json = self._download_webpage(
|
||||||
'http://api.soundcloud.com/i1/tracks/{0}/streams?client_id={1}'.format(track_id, self._CLIENT_ID),
|
'http://api.soundcloud.com/i1/tracks/{0}/streams?client_id={1}'.format(track_id, self._IPHONE_CLIENT_ID),
|
||||||
track_id, u'Downloading track url')
|
track_id, u'Downloading track url')
|
||||||
rtmp_url = json.loads(stream_json)['rtmp_mp3_128_url']
|
# There should be only one entry in the dictionary
|
||||||
|
key, stream_url = list(json.loads(stream_json).items())[0]
|
||||||
|
if key.startswith(u'http'):
|
||||||
|
result['url'] = stream_url
|
||||||
|
elif key.startswith(u'rtmp'):
|
||||||
# The url doesn't have an rtmp app, we have to extract the playpath
|
# The url doesn't have an rtmp app, we have to extract the playpath
|
||||||
url, path = rtmp_url.split('mp3:', 1)
|
url, path = stream_url.split('mp3:', 1)
|
||||||
result.update({
|
result.update({
|
||||||
'url': url,
|
'url': url,
|
||||||
'play_path': 'mp3:' + path,
|
'play_path': 'mp3:' + path,
|
||||||
})
|
})
|
||||||
|
else:
|
||||||
|
# We fallback to the stream_url in the original info, this
|
||||||
|
# cannot be always used, sometimes it can give an HTTP 404 error
|
||||||
|
result['url'] = info['stream_url'] + '?client_id=' + self._CLIENT_ID,
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
@@ -158,7 +168,6 @@ class SoundcloudSetIE(SoundcloudIE):
|
|||||||
resolv_url = self._resolv_url(url)
|
resolv_url = self._resolv_url(url)
|
||||||
info_json = self._download_webpage(resolv_url, full_title)
|
info_json = self._download_webpage(resolv_url, full_title)
|
||||||
|
|
||||||
videos = []
|
|
||||||
info = json.loads(info_json)
|
info = json.loads(info_json)
|
||||||
if 'errors' in info:
|
if 'errors' in info:
|
||||||
for err in info['errors']:
|
for err in info['errors']:
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ from ..utils import (
|
|||||||
compat_urllib_parse_urlparse,
|
compat_urllib_parse_urlparse,
|
||||||
compat_urllib_request,
|
compat_urllib_request,
|
||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
unescapeHTML,
|
|
||||||
)
|
)
|
||||||
from ..aes import (
|
from ..aes import (
|
||||||
aes_decrypt_text
|
aes_decrypt_text
|
||||||
@@ -36,11 +35,12 @@ class SpankwireIE(InfoExtractor):
|
|||||||
webpage = self._download_webpage(req, video_id)
|
webpage = self._download_webpage(req, video_id)
|
||||||
|
|
||||||
video_title = self._html_search_regex(r'<h1>([^<]+)', webpage, u'title')
|
video_title = self._html_search_regex(r'<h1>([^<]+)', webpage, u'title')
|
||||||
video_uploader = self._html_search_regex(r'by:\s*<a [^>]*>(.+?)</a>', webpage, u'uploader', fatal=False)
|
video_uploader = self._html_search_regex(
|
||||||
thumbnail = self._html_search_regex(r'flashvars\.image_url = "([^"]+)', webpage, u'thumbnail', fatal=False)
|
r'by:\s*<a [^>]*>(.+?)</a>', webpage, u'uploader', fatal=False)
|
||||||
description = self._html_search_regex(r'>\s*Description:</div>\s*<[^>]*>([^<]+)', webpage, u'description', fatal=False)
|
thumbnail = self._html_search_regex(
|
||||||
if len(description) == 0:
|
r'flashvars\.image_url = "([^"]+)', webpage, u'thumbnail', fatal=False)
|
||||||
description = None
|
description = self._html_search_regex(
|
||||||
|
r'<div\s+id="descriptionContent">([^<]+)<', webpage, u'description', fatal=False)
|
||||||
|
|
||||||
video_urls = list(map(compat_urllib_parse.unquote , re.findall(r'flashvars\.quality_[0-9]{3}p = "([^"]+)', webpage)))
|
video_urls = list(map(compat_urllib_parse.unquote , re.findall(r'flashvars\.quality_[0-9]{3}p = "([^"]+)', webpage)))
|
||||||
if webpage.find('flashvars\.encrypted = "true"') != -1:
|
if webpage.find('flashvars\.encrypted = "true"') != -1:
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import re
|
|||||||
import xml.etree.ElementTree
|
import xml.etree.ElementTree
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import determine_ext
|
|
||||||
|
|
||||||
|
|
||||||
class SpiegelIE(InfoExtractor):
|
class SpiegelIE(InfoExtractor):
|
||||||
|
|||||||
66
youtube_dl/extractor/streamcloud.py
Normal file
66
youtube_dl/extractor/streamcloud.py
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
compat_urllib_parse,
|
||||||
|
compat_urllib_request,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class StreamcloudIE(InfoExtractor):
|
||||||
|
IE_NAME = u'streamcloud.eu'
|
||||||
|
_VALID_URL = r'https?://streamcloud\.eu/(?P<id>[a-zA-Z0-9_-]+)/(?P<fname>[^#?]*)\.html'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
u'url': u'http://streamcloud.eu/skp9j99s4bpz/youtube-dl_test_video_____________-BaW_jenozKc.mp4.html',
|
||||||
|
u'file': u'skp9j99s4bpz.mp4',
|
||||||
|
u'md5': u'6bea4c7fa5daaacc2a946b7146286686',
|
||||||
|
u'info_dict': {
|
||||||
|
u'title': u'youtube-dl test video \'/\\ ä ↭',
|
||||||
|
u'duration': 9,
|
||||||
|
},
|
||||||
|
u'skip': u'Only available from the EU'
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
orig_webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
fields = re.findall(r'''(?x)<input\s+
|
||||||
|
type="(?:hidden|submit)"\s+
|
||||||
|
name="([^"]+)"\s+
|
||||||
|
(?:id="[^"]+"\s+)?
|
||||||
|
value="([^"]*)"
|
||||||
|
''', orig_webpage)
|
||||||
|
post = compat_urllib_parse.urlencode(fields)
|
||||||
|
|
||||||
|
self.to_screen('%s: Waiting for timeout' % video_id)
|
||||||
|
time.sleep(12)
|
||||||
|
headers = {
|
||||||
|
b'Content-Type': b'application/x-www-form-urlencoded',
|
||||||
|
}
|
||||||
|
req = compat_urllib_request.Request(url, post, headers)
|
||||||
|
|
||||||
|
webpage = self._download_webpage(
|
||||||
|
req, video_id, note=u'Downloading video page ...')
|
||||||
|
title = self._html_search_regex(
|
||||||
|
r'<h1[^>]*>([^<]+)<', webpage, u'title')
|
||||||
|
video_url = self._search_regex(
|
||||||
|
r'file:\s*"([^"]+)"', webpage, u'video URL')
|
||||||
|
duration_str = self._search_regex(
|
||||||
|
r'duration:\s*"?([0-9]+)"?', webpage, u'duration', fatal=False)
|
||||||
|
duration = None if duration_str is None else int(duration_str)
|
||||||
|
thumbnail = self._search_regex(
|
||||||
|
r'image:\s*"([^"]+)"', webpage, u'thumbnail URL', fatal=False)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'url': video_url,
|
||||||
|
'duration': duration,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
}
|
||||||
@@ -15,7 +15,8 @@ class SztvHuIE(InfoExtractor):
|
|||||||
u'info_dict': {
|
u'info_dict': {
|
||||||
u"title": u"Cserkészek népszerűsítették a környezettudatos életmódot a Savaria téren",
|
u"title": u"Cserkészek népszerűsítették a környezettudatos életmódot a Savaria téren",
|
||||||
u"description": u'A zöld nap játékos ismeretterjesztő programjait a Magyar Cserkész Szövetség szervezte, akik az ország nyolc városában adják át tudásukat az érdeklődőknek. A PET...',
|
u"description": u'A zöld nap játékos ismeretterjesztő programjait a Magyar Cserkész Szövetség szervezte, akik az ország nyolc városában adják át tudásukat az érdeklődőknek. A PET...',
|
||||||
}
|
},
|
||||||
|
u'skip': u'Service temporarily disabled as of 2013-11-20'
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ class TeamcocoIE(InfoExtractor):
|
|||||||
return -1
|
return -1
|
||||||
formats.sort(key=sort_key)
|
formats.sort(key=sort_key)
|
||||||
if not formats:
|
if not formats:
|
||||||
raise RegexNotFoundError(u'Unable to extract video URL')
|
raise ExtractorError(u'Unable to extract video URL')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import re
|
|||||||
from .subtitles import SubtitlesInfoExtractor
|
from .subtitles import SubtitlesInfoExtractor
|
||||||
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
compat_str,
|
|
||||||
RegexNotFoundError,
|
RegexNotFoundError,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -113,6 +112,6 @@ class TEDIE(SubtitlesInfoExtractor):
|
|||||||
url = 'http://www.ted.com/talks/subtitles/id/%s/lang/%s/format/srt' % (video_id, l)
|
url = 'http://www.ted.com/talks/subtitles/id/%s/lang/%s/format/srt' % (video_id, l)
|
||||||
sub_lang_list[l] = url
|
sub_lang_list[l] = url
|
||||||
return sub_lang_list
|
return sub_lang_list
|
||||||
except RegexNotFoundError as err:
|
except RegexNotFoundError:
|
||||||
self._downloader.report_warning(u'video doesn\'t have subtitles')
|
self._downloader.report_warning(u'video doesn\'t have subtitles')
|
||||||
return {}
|
return {}
|
||||||
|
|||||||
74
youtube_dl/extractor/toutv.py
Normal file
74
youtube_dl/extractor/toutv.py
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
import re
|
||||||
|
import xml.etree.ElementTree
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
ExtractorError,
|
||||||
|
unified_strdate,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TouTvIE(InfoExtractor):
|
||||||
|
IE_NAME = u'tou.tv'
|
||||||
|
_VALID_URL = r'https?://www\.tou\.tv/(?P<id>[a-zA-Z0-9_-]+(?:/(?P<episode>S[0-9]+E[0-9]+)))'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
u'url': u'http://www.tou.tv/30-vies/S04E41',
|
||||||
|
u'file': u'30-vies_S04E41.mp4',
|
||||||
|
u'info_dict': {
|
||||||
|
u'title': u'30 vies Saison 4 / Épisode 41',
|
||||||
|
u'description': u'md5:da363002db82ccbe4dafeb9cab039b09',
|
||||||
|
u'age_limit': 8,
|
||||||
|
u'uploader': u'Groupe des Nouveaux Médias',
|
||||||
|
u'duration': 1296,
|
||||||
|
u'upload_date': u'20131118',
|
||||||
|
u'thumbnail': u'http://static.tou.tv/medias/images/2013-11-18_19_00_00_30VIES_0341_01_L.jpeg',
|
||||||
|
},
|
||||||
|
u'params': {
|
||||||
|
u'skip_download': True, # Requires rtmpdump
|
||||||
|
},
|
||||||
|
u'skip': 'Only available in Canada'
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
mediaId = self._search_regex(
|
||||||
|
r'"idMedia":\s*"([^"]+)"', webpage, u'media ID')
|
||||||
|
|
||||||
|
streams_url = u'http://release.theplatform.com/content.select?pid=' + mediaId
|
||||||
|
streams_webpage = self._download_webpage(
|
||||||
|
streams_url, video_id, note=u'Downloading stream list')
|
||||||
|
|
||||||
|
streams_doc = xml.etree.ElementTree.fromstring(
|
||||||
|
streams_webpage.encode('utf-8'))
|
||||||
|
video_url = next(n.text
|
||||||
|
for n in streams_doc.findall('.//choice/url')
|
||||||
|
if u'//ad.doubleclick' not in n.text)
|
||||||
|
if video_url.endswith('/Unavailable.flv'):
|
||||||
|
raise ExtractorError(
|
||||||
|
u'Access to this video is blocked from outside of Canada',
|
||||||
|
expected=True)
|
||||||
|
|
||||||
|
duration_str = self._html_search_meta(
|
||||||
|
'video:duration', webpage, u'duration')
|
||||||
|
duration = int(duration_str) if duration_str else None
|
||||||
|
upload_date_str = self._html_search_meta(
|
||||||
|
'video:release_date', webpage, u'upload date')
|
||||||
|
upload_date = unified_strdate(upload_date_str) if upload_date_str else None
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': self._og_search_title(webpage),
|
||||||
|
'url': video_url,
|
||||||
|
'description': self._og_search_description(webpage),
|
||||||
|
'uploader': self._dc_search_uploader(webpage),
|
||||||
|
'thumbnail': self._og_search_thumbnail(webpage),
|
||||||
|
'age_limit': self._media_rating_search(webpage),
|
||||||
|
'duration': duration,
|
||||||
|
'upload_date': upload_date,
|
||||||
|
'ext': 'mp4',
|
||||||
|
}
|
||||||
@@ -5,8 +5,6 @@ from .common import InfoExtractor
|
|||||||
from ..utils import (
|
from ..utils import (
|
||||||
compat_urllib_parse_urlparse,
|
compat_urllib_parse_urlparse,
|
||||||
compat_urllib_request,
|
compat_urllib_request,
|
||||||
compat_urllib_parse,
|
|
||||||
unescapeHTML,
|
|
||||||
)
|
)
|
||||||
from ..aes import (
|
from ..aes import (
|
||||||
aes_decrypt_text
|
aes_decrypt_text
|
||||||
|
|||||||
@@ -24,12 +24,16 @@ class VideoPremiumIE(InfoExtractor):
|
|||||||
webpage_url = 'http://videopremium.tv/' + video_id
|
webpage_url = 'http://videopremium.tv/' + video_id
|
||||||
webpage = self._download_webpage(webpage_url, video_id)
|
webpage = self._download_webpage(webpage_url, video_id)
|
||||||
|
|
||||||
self.report_extraction(video_id)
|
if re.match(r"^<html><head><script[^>]*>window.location\s*=", webpage):
|
||||||
|
# Download again, we need a cookie
|
||||||
|
webpage = self._download_webpage(
|
||||||
|
webpage_url, video_id,
|
||||||
|
note=u'Downloading webpage again (with cookie)')
|
||||||
|
|
||||||
video_title = self._html_search_regex(r'<h2(?:.*?)>\s*(.+?)\s*<',
|
video_title = self._html_search_regex(
|
||||||
webpage, u'video title')
|
r'<h2(?:.*?)>\s*(.+?)\s*<', webpage, u'video title')
|
||||||
|
|
||||||
return [{
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'url': "rtmp://e%d.md.iplay.md/play" % random.randint(1, 16),
|
'url': "rtmp://e%d.md.iplay.md/play" % random.randint(1, 16),
|
||||||
'play_path': "mp4:%s.f4v" % video_id,
|
'play_path': "mp4:%s.f4v" % video_id,
|
||||||
@@ -37,4 +41,4 @@ class VideoPremiumIE(InfoExtractor):
|
|||||||
'player_url': "http://videopremium.tv/uplayer/uppod.swf",
|
'player_url': "http://videopremium.tv/uplayer/uppod.swf",
|
||||||
'ext': 'f4v',
|
'ext': 'f4v',
|
||||||
'title': video_title,
|
'title': video_title,
|
||||||
}]
|
}
|
||||||
91
youtube_dl/extractor/viki.py
Normal file
91
youtube_dl/extractor/viki.py
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
|
from ..utils import (
|
||||||
|
unified_strdate,
|
||||||
|
)
|
||||||
|
from .subtitles import SubtitlesInfoExtractor
|
||||||
|
|
||||||
|
|
||||||
|
class VikiIE(SubtitlesInfoExtractor):
|
||||||
|
IE_NAME = u'viki'
|
||||||
|
|
||||||
|
_VALID_URL = r'^https?://(?:www\.)?viki\.com/videos/(?P<id>[0-9]+v)'
|
||||||
|
_TEST = {
|
||||||
|
u'url': u'http://www.viki.com/videos/1023585v-heirs-episode-14',
|
||||||
|
u'file': u'1023585v.mp4',
|
||||||
|
u'md5': u'a21454021c2646f5433514177e2caa5f',
|
||||||
|
u'info_dict': {
|
||||||
|
u'title': u'Heirs Episode 14',
|
||||||
|
u'uploader': u'SBS',
|
||||||
|
u'description': u'md5:c4b17b9626dd4b143dcc4d855ba3474e',
|
||||||
|
u'upload_date': u'20131121',
|
||||||
|
u'age_limit': 13,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group(1)
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
title = self._og_search_title(webpage)
|
||||||
|
description = self._og_search_description(webpage)
|
||||||
|
thumbnail = self._og_search_thumbnail(webpage)
|
||||||
|
|
||||||
|
uploader = self._html_search_regex(
|
||||||
|
r'<strong>Broadcast Network: </strong>\s*([^<]*)<', webpage,
|
||||||
|
u'uploader')
|
||||||
|
if uploader is not None:
|
||||||
|
uploader = uploader.strip()
|
||||||
|
|
||||||
|
rating_str = self._html_search_regex(
|
||||||
|
r'<strong>Rating: </strong>\s*([^<]*)<', webpage,
|
||||||
|
u'rating information', default='').strip()
|
||||||
|
RATINGS = {
|
||||||
|
'G': 0,
|
||||||
|
'PG': 10,
|
||||||
|
'PG-13': 13,
|
||||||
|
'R': 16,
|
||||||
|
'NC': 18,
|
||||||
|
}
|
||||||
|
age_limit = RATINGS.get(rating_str)
|
||||||
|
|
||||||
|
info_url = 'http://www.viki.com/player5_fragment/%s?action=show&controller=videos' % video_id
|
||||||
|
info_webpage = self._download_webpage(info_url, video_id)
|
||||||
|
video_url = self._html_search_regex(
|
||||||
|
r'<source[^>]+src="([^"]+)"', info_webpage, u'video URL')
|
||||||
|
|
||||||
|
upload_date_str = self._html_search_regex(
|
||||||
|
r'"created_at":"([^"]+)"', info_webpage, u'upload date')
|
||||||
|
upload_date = (
|
||||||
|
unified_strdate(upload_date_str)
|
||||||
|
if upload_date_str is not None
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
|
# subtitles
|
||||||
|
video_subtitles = self.extract_subtitles(video_id, info_webpage)
|
||||||
|
if self._downloader.params.get('listsubtitles', False):
|
||||||
|
self._list_available_subtitles(video_id, info_webpage)
|
||||||
|
return
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'url': video_url,
|
||||||
|
'description': description,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'age_limit': age_limit,
|
||||||
|
'uploader': uploader,
|
||||||
|
'subtitles': video_subtitles,
|
||||||
|
'upload_date': upload_date,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_available_subtitles(self, video_id, info_webpage):
|
||||||
|
res = {}
|
||||||
|
for sturl in re.findall(r'<track src="([^"]+)"/>'):
|
||||||
|
m = re.search(r'/(?P<lang>[a-z]+)\.vtt', sturl)
|
||||||
|
if not m:
|
||||||
|
continue
|
||||||
|
res[m.group('lang')] = sturl
|
||||||
|
return res
|
||||||
@@ -151,7 +151,7 @@ class VimeoIE(InfoExtractor):
|
|||||||
config = json.loads(config_json)
|
config = json.loads(config_json)
|
||||||
except RegexNotFoundError:
|
except RegexNotFoundError:
|
||||||
# For pro videos or player.vimeo.com urls
|
# For pro videos or player.vimeo.com urls
|
||||||
config = self._search_regex([r' = {config:({.+?}),assets:', r'c=({.+?);'],
|
config = self._search_regex([r' = {config:({.+?}),assets:', r'(?:c|b)=({.+?});'],
|
||||||
webpage, u'info section', flags=re.DOTALL)
|
webpage, u'info section', flags=re.DOTALL)
|
||||||
config = json.loads(config)
|
config = json.loads(config)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ from .common import InfoExtractor
|
|||||||
from ..utils import (
|
from ..utils import (
|
||||||
compat_urllib_parse_urlparse,
|
compat_urllib_parse_urlparse,
|
||||||
compat_urllib_request,
|
compat_urllib_request,
|
||||||
compat_urllib_parse,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class XTubeIE(InfoExtractor):
|
class XTubeIE(InfoExtractor):
|
||||||
|
|||||||
@@ -139,10 +139,10 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
|
|||||||
|
|
||||||
class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):
|
class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):
|
||||||
IE_DESC = u'YouTube.com'
|
IE_DESC = u'YouTube.com'
|
||||||
_VALID_URL = r"""^
|
_VALID_URL = r"""(?x)^
|
||||||
(
|
(
|
||||||
(?:https?://)? # http(s):// (optional)
|
(?:https?://|//)? # http(s):// or protocol-independent URL (optional)
|
||||||
(?:(?:(?:(?:\w+\.)?youtube(?:-nocookie)?\.com/|
|
(?:(?:(?:(?:\w+\.)?[yY][oO][uU][tT][uU][bB][eE](?:-nocookie)?\.com/|
|
||||||
tube\.majestyc\.net/|
|
tube\.majestyc\.net/|
|
||||||
youtube\.googleapis\.com/) # the various hostnames, with wildcard subdomains
|
youtube\.googleapis\.com/) # the various hostnames, with wildcard subdomains
|
||||||
(?:.*?\#/)? # handle anchor (#/) redirect urls
|
(?:.*?\#/)? # handle anchor (#/) redirect urls
|
||||||
@@ -363,6 +363,18 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):
|
|||||||
u"uploader_id": u"justintimberlakeVEVO"
|
u"uploader_id": u"justintimberlakeVEVO"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
u"url": u"//www.YouTube.com/watch?v=yZIXLfi8CZQ",
|
||||||
|
u"file": u"yZIXLfi8CZQ.mp4",
|
||||||
|
u"note": u"Embed-only video (#1746)",
|
||||||
|
u"info_dict": {
|
||||||
|
u"upload_date": u"20120608",
|
||||||
|
u"title": u"Principal Sexually Assaults A Teacher - Episode 117 - 8th June 2012",
|
||||||
|
u"description": u"md5:09b78bd971f1e3e289601dfba15ca4f7",
|
||||||
|
u"uploader": u"SET India",
|
||||||
|
u"uploader_id": u"setindia"
|
||||||
|
}
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -370,7 +382,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):
|
|||||||
def suitable(cls, url):
|
def suitable(cls, url):
|
||||||
"""Receives a URL and returns True if suitable for this IE."""
|
"""Receives a URL and returns True if suitable for this IE."""
|
||||||
if YoutubePlaylistIE.suitable(url): return False
|
if YoutubePlaylistIE.suitable(url): return False
|
||||||
return re.match(cls._VALID_URL, url, re.VERBOSE) is not None
|
return re.match(cls._VALID_URL, url) is not None
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(YoutubeIE, self).__init__(*args, **kwargs)
|
super(YoutubeIE, self).__init__(*args, **kwargs)
|
||||||
@@ -1272,7 +1284,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):
|
|||||||
# We simulate the access to the video from www.youtube.com/v/{video_id}
|
# We simulate the access to the video from www.youtube.com/v/{video_id}
|
||||||
# this can be viewed without login into Youtube
|
# this can be viewed without login into Youtube
|
||||||
data = compat_urllib_parse.urlencode({'video_id': video_id,
|
data = compat_urllib_parse.urlencode({'video_id': video_id,
|
||||||
'el': 'embedded',
|
'el': 'player_embedded',
|
||||||
'gl': 'US',
|
'gl': 'US',
|
||||||
'hl': 'en',
|
'hl': 'en',
|
||||||
'eurl': 'https://youtube.googleapis.com/v/' + video_id,
|
'eurl': 'https://youtube.googleapis.com/v/' + video_id,
|
||||||
@@ -1498,7 +1510,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):
|
|||||||
})
|
})
|
||||||
return results
|
return results
|
||||||
|
|
||||||
class YoutubePlaylistIE(InfoExtractor):
|
class YoutubePlaylistIE(YoutubeBaseInfoExtractor):
|
||||||
IE_DESC = u'YouTube.com playlists'
|
IE_DESC = u'YouTube.com playlists'
|
||||||
_VALID_URL = r"""(?:
|
_VALID_URL = r"""(?:
|
||||||
(?:https?://)?
|
(?:https?://)?
|
||||||
@@ -1514,8 +1526,9 @@ class YoutubePlaylistIE(InfoExtractor):
|
|||||||
|
|
|
|
||||||
((?:PL|EC|UU|FL)[0-9A-Za-z-_]{10,})
|
((?:PL|EC|UU|FL)[0-9A-Za-z-_]{10,})
|
||||||
)"""
|
)"""
|
||||||
_TEMPLATE_URL = 'https://gdata.youtube.com/feeds/api/playlists/%s?max-results=%i&start-index=%i&v=2&alt=json&safeSearch=none'
|
_TEMPLATE_URL = 'https://www.youtube.com/playlist?list=%s&page=%s'
|
||||||
_MAX_RESULTS = 50
|
_MORE_PAGES_INDICATOR = r'data-link-type="next"'
|
||||||
|
_VIDEO_RE = r'href="/watch\?v=([0-9A-Za-z_-]{11})&'
|
||||||
IE_NAME = u'youtube:playlist'
|
IE_NAME = u'youtube:playlist'
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -1523,6 +1536,9 @@ class YoutubePlaylistIE(InfoExtractor):
|
|||||||
"""Receives a URL and returns True if suitable for this IE."""
|
"""Receives a URL and returns True if suitable for this IE."""
|
||||||
return re.match(cls._VALID_URL, url, re.VERBOSE) is not None
|
return re.match(cls._VALID_URL, url, re.VERBOSE) is not None
|
||||||
|
|
||||||
|
def _real_initialize(self):
|
||||||
|
self._login()
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
# Extract playlist id
|
# Extract playlist id
|
||||||
mobj = re.match(self._VALID_URL, url, re.VERBOSE)
|
mobj = re.match(self._VALID_URL, url, re.VERBOSE)
|
||||||
@@ -1536,45 +1552,28 @@ class YoutubePlaylistIE(InfoExtractor):
|
|||||||
video_id = query_dict['v'][0]
|
video_id = query_dict['v'][0]
|
||||||
if self._downloader.params.get('noplaylist'):
|
if self._downloader.params.get('noplaylist'):
|
||||||
self.to_screen(u'Downloading just video %s because of --no-playlist' % video_id)
|
self.to_screen(u'Downloading just video %s because of --no-playlist' % video_id)
|
||||||
return self.url_result('https://www.youtube.com/watch?v=' + video_id, 'Youtube')
|
return self.url_result(video_id, 'Youtube', video_id=video_id)
|
||||||
else:
|
else:
|
||||||
self.to_screen(u'Downloading playlist PL%s - add --no-playlist to just download video %s' % (playlist_id, video_id))
|
self.to_screen(u'Downloading playlist PL%s - add --no-playlist to just download video %s' % (playlist_id, video_id))
|
||||||
|
|
||||||
# Download playlist videos from API
|
# Extract the video ids from the playlist pages
|
||||||
videos = []
|
ids = []
|
||||||
|
|
||||||
for page_num in itertools.count(1):
|
for page_num in itertools.count(1):
|
||||||
start_index = self._MAX_RESULTS * (page_num - 1) + 1
|
url = self._TEMPLATE_URL % (playlist_id, page_num)
|
||||||
if start_index >= 1000:
|
|
||||||
self._downloader.report_warning(u'Max number of results reached')
|
|
||||||
break
|
|
||||||
url = self._TEMPLATE_URL % (playlist_id, self._MAX_RESULTS, start_index)
|
|
||||||
page = self._download_webpage(url, playlist_id, u'Downloading page #%s' % page_num)
|
page = self._download_webpage(url, playlist_id, u'Downloading page #%s' % page_num)
|
||||||
|
# The ids are duplicated
|
||||||
|
new_ids = orderedSet(re.findall(self._VIDEO_RE, page))
|
||||||
|
ids.extend(new_ids)
|
||||||
|
|
||||||
try:
|
if re.search(self._MORE_PAGES_INDICATOR, page) is None:
|
||||||
response = json.loads(page)
|
|
||||||
except ValueError as err:
|
|
||||||
raise ExtractorError(u'Invalid JSON in API response: ' + compat_str(err))
|
|
||||||
|
|
||||||
if 'feed' not in response:
|
|
||||||
raise ExtractorError(u'Got a malformed response from YouTube API')
|
|
||||||
playlist_title = response['feed']['title']['$t']
|
|
||||||
if 'entry' not in response['feed']:
|
|
||||||
# Number of videos is a multiple of self._MAX_RESULTS
|
|
||||||
break
|
break
|
||||||
|
|
||||||
for entry in response['feed']['entry']:
|
playlist_title = self._og_search_title(page)
|
||||||
index = entry['yt$position']['$t']
|
|
||||||
if 'media$group' in entry and 'yt$videoid' in entry['media$group']:
|
|
||||||
videos.append((
|
|
||||||
index,
|
|
||||||
'https://www.youtube.com/watch?v=' + entry['media$group']['yt$videoid']['$t']
|
|
||||||
))
|
|
||||||
|
|
||||||
videos = [v[1] for v in sorted(videos)]
|
url_results = [self.url_result(vid_id, 'Youtube', video_id=vid_id)
|
||||||
|
for vid_id in ids]
|
||||||
url_results = [self.url_result(vurl, 'Youtube') for vurl in videos]
|
return self.playlist_result(url_results, playlist_id, playlist_title)
|
||||||
return [self.playlist_result(url_results, playlist_id, playlist_title)]
|
|
||||||
|
|
||||||
|
|
||||||
class YoutubeChannelIE(InfoExtractor):
|
class YoutubeChannelIE(InfoExtractor):
|
||||||
@@ -1628,9 +1627,9 @@ class YoutubeChannelIE(InfoExtractor):
|
|||||||
|
|
||||||
self._downloader.to_screen(u'[youtube] Channel %s: Found %i videos' % (channel_id, len(video_ids)))
|
self._downloader.to_screen(u'[youtube] Channel %s: Found %i videos' % (channel_id, len(video_ids)))
|
||||||
|
|
||||||
urls = ['http://www.youtube.com/watch?v=%s' % id for id in video_ids]
|
url_entries = [self.url_result(video_id, 'Youtube', video_id=video_id)
|
||||||
url_entries = [self.url_result(eurl, 'Youtube') for eurl in urls]
|
for video_id in video_ids]
|
||||||
return [self.playlist_result(url_entries, channel_id)]
|
return self.playlist_result(url_entries, channel_id)
|
||||||
|
|
||||||
|
|
||||||
class YoutubeUserIE(InfoExtractor):
|
class YoutubeUserIE(InfoExtractor):
|
||||||
@@ -1694,9 +1693,11 @@ class YoutubeUserIE(InfoExtractor):
|
|||||||
if len(ids_in_page) < self._GDATA_PAGE_SIZE:
|
if len(ids_in_page) < self._GDATA_PAGE_SIZE:
|
||||||
break
|
break
|
||||||
|
|
||||||
urls = ['http://www.youtube.com/watch?v=%s' % video_id for video_id in video_ids]
|
url_results = [
|
||||||
url_results = [self.url_result(rurl, 'Youtube') for rurl in urls]
|
self.url_result(video_id, 'Youtube', video_id=video_id)
|
||||||
return [self.playlist_result(url_results, playlist_title = username)]
|
for video_id in video_ids]
|
||||||
|
return self.playlist_result(url_results, playlist_title=username)
|
||||||
|
|
||||||
|
|
||||||
class YoutubeSearchIE(SearchInfoExtractor):
|
class YoutubeSearchIE(SearchInfoExtractor):
|
||||||
IE_DESC = u'YouTube.com searches'
|
IE_DESC = u'YouTube.com searches'
|
||||||
@@ -1737,7 +1738,8 @@ class YoutubeSearchIE(SearchInfoExtractor):
|
|||||||
|
|
||||||
if len(video_ids) > n:
|
if len(video_ids) > n:
|
||||||
video_ids = video_ids[:n]
|
video_ids = video_ids[:n]
|
||||||
videos = [self.url_result('http://www.youtube.com/watch?v=%s' % id, 'Youtube') for id in video_ids]
|
videos = [self.url_result(video_id, 'Youtube', video_id=video_id)
|
||||||
|
for video_id in video_ids]
|
||||||
return self.playlist_result(videos, query)
|
return self.playlist_result(videos, query)
|
||||||
|
|
||||||
class YoutubeSearchDateIE(YoutubeSearchIE):
|
class YoutubeSearchDateIE(YoutubeSearchIE):
|
||||||
@@ -1797,7 +1799,9 @@ class YoutubeFeedsInfoExtractor(YoutubeBaseInfoExtractor):
|
|||||||
feed_html = info['feed_html']
|
feed_html = info['feed_html']
|
||||||
m_ids = re.finditer(r'"/watch\?v=(.*?)["&]', feed_html)
|
m_ids = re.finditer(r'"/watch\?v=(.*?)["&]', feed_html)
|
||||||
ids = orderedSet(m.group(1) for m in m_ids)
|
ids = orderedSet(m.group(1) for m in m_ids)
|
||||||
feed_entries.extend(self.url_result(id, 'Youtube') for id in ids)
|
feed_entries.extend(
|
||||||
|
self.url_result(video_id, 'Youtube', video_id=video_id)
|
||||||
|
for video_id in ids)
|
||||||
if info['paging'] is None:
|
if info['paging'] is None:
|
||||||
break
|
break
|
||||||
return self.playlist_result(feed_entries, playlist_title=self._PLAYLIST_TITLE)
|
return self.playlist_result(feed_entries, playlist_title=self._PLAYLIST_TITLE)
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ class ZDFIE(InfoExtractor):
|
|||||||
video_id,
|
video_id,
|
||||||
u'Get stream URL')
|
u'Get stream URL')
|
||||||
|
|
||||||
MMS_STREAM = r'href="(?P<video_url>mms://[^"]*)"'
|
#MMS_STREAM = r'href="(?P<video_url>mms://[^"]*)"'
|
||||||
RTSP_STREAM = r'(?P<video_url>rtsp://[^"]*.mp4)'
|
RTSP_STREAM = r'(?P<video_url>rtsp://[^"]*.mp4)'
|
||||||
|
|
||||||
mobj = re.search(self._MEDIA_STREAM, media_link)
|
mobj = re.search(self._MEDIA_STREAM, media_link)
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ def rsa_verify(message, signature, key):
|
|||||||
if signature != sha256(message).digest(): return False
|
if signature != sha256(message).digest(): return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def update_self(to_screen, verbose):
|
def update_self(to_screen, verbose):
|
||||||
"""Update the program file with the latest version from the repository"""
|
"""Update the program file with the latest version from the repository"""
|
||||||
|
|
||||||
@@ -82,6 +83,13 @@ def update_self(to_screen, verbose):
|
|||||||
return
|
return
|
||||||
|
|
||||||
version_id = versions_info['latest']
|
version_id = versions_info['latest']
|
||||||
|
|
||||||
|
def version_tuple(version_str):
|
||||||
|
return tuple(map(int, version_str.split('.')))
|
||||||
|
if version_tuple(__version__) >= version_tuple(version_id):
|
||||||
|
to_screen(u'youtube-dl is up to date (%s)' % __version__)
|
||||||
|
return
|
||||||
|
|
||||||
to_screen(u'Updating to version ' + version_id + '...')
|
to_screen(u'Updating to version ' + version_id + '...')
|
||||||
version = versions_info['versions'][version_id]
|
version = versions_info['versions'][version_id]
|
||||||
|
|
||||||
@@ -109,7 +117,7 @@ def update_self(to_screen, verbose):
|
|||||||
urlh = compat_urllib_request.urlopen(version['exe'][0])
|
urlh = compat_urllib_request.urlopen(version['exe'][0])
|
||||||
newcontent = urlh.read()
|
newcontent = urlh.read()
|
||||||
urlh.close()
|
urlh.close()
|
||||||
except (IOError, OSError) as err:
|
except (IOError, OSError):
|
||||||
if verbose: to_screen(compat_str(traceback.format_exc()))
|
if verbose: to_screen(compat_str(traceback.format_exc()))
|
||||||
to_screen(u'ERROR: unable to download latest version')
|
to_screen(u'ERROR: unable to download latest version')
|
||||||
return
|
return
|
||||||
@@ -122,7 +130,7 @@ def update_self(to_screen, verbose):
|
|||||||
try:
|
try:
|
||||||
with open(exe + '.new', 'wb') as outf:
|
with open(exe + '.new', 'wb') as outf:
|
||||||
outf.write(newcontent)
|
outf.write(newcontent)
|
||||||
except (IOError, OSError) as err:
|
except (IOError, OSError):
|
||||||
if verbose: to_screen(compat_str(traceback.format_exc()))
|
if verbose: to_screen(compat_str(traceback.format_exc()))
|
||||||
to_screen(u'ERROR: unable to write the new version')
|
to_screen(u'ERROR: unable to write the new version')
|
||||||
return
|
return
|
||||||
@@ -141,7 +149,7 @@ start /b "" cmd /c del "%%~f0"&exit /b"
|
|||||||
|
|
||||||
subprocess.Popen([bat]) # Continues to run in the background
|
subprocess.Popen([bat]) # Continues to run in the background
|
||||||
return # Do not show premature success messages
|
return # Do not show premature success messages
|
||||||
except (IOError, OSError) as err:
|
except (IOError, OSError):
|
||||||
if verbose: to_screen(compat_str(traceback.format_exc()))
|
if verbose: to_screen(compat_str(traceback.format_exc()))
|
||||||
to_screen(u'ERROR: unable to overwrite current version')
|
to_screen(u'ERROR: unable to overwrite current version')
|
||||||
return
|
return
|
||||||
@@ -152,7 +160,7 @@ start /b "" cmd /c del "%%~f0"&exit /b"
|
|||||||
urlh = compat_urllib_request.urlopen(version['bin'][0])
|
urlh = compat_urllib_request.urlopen(version['bin'][0])
|
||||||
newcontent = urlh.read()
|
newcontent = urlh.read()
|
||||||
urlh.close()
|
urlh.close()
|
||||||
except (IOError, OSError) as err:
|
except (IOError, OSError):
|
||||||
if verbose: to_screen(compat_str(traceback.format_exc()))
|
if verbose: to_screen(compat_str(traceback.format_exc()))
|
||||||
to_screen(u'ERROR: unable to download latest version')
|
to_screen(u'ERROR: unable to download latest version')
|
||||||
return
|
return
|
||||||
@@ -165,7 +173,7 @@ start /b "" cmd /c del "%%~f0"&exit /b"
|
|||||||
try:
|
try:
|
||||||
with open(filename, 'wb') as outf:
|
with open(filename, 'wb') as outf:
|
||||||
outf.write(newcontent)
|
outf.write(newcontent)
|
||||||
except (IOError, OSError) as err:
|
except (IOError, OSError):
|
||||||
if verbose: to_screen(compat_str(traceback.format_exc()))
|
if verbose: to_screen(compat_str(traceback.format_exc()))
|
||||||
to_screen(u'ERROR: unable to overwrite current version')
|
to_screen(u'ERROR: unable to overwrite current version')
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import os
|
|||||||
import pipes
|
import pipes
|
||||||
import platform
|
import platform
|
||||||
import re
|
import re
|
||||||
|
import ssl
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
@@ -535,13 +536,31 @@ def formatSeconds(secs):
|
|||||||
else:
|
else:
|
||||||
return '%d' % secs
|
return '%d' % secs
|
||||||
|
|
||||||
|
|
||||||
def make_HTTPS_handler(opts):
|
def make_HTTPS_handler(opts):
|
||||||
if sys.version_info < (3,2):
|
if sys.version_info < (3, 2):
|
||||||
# Python's 2.x handler is very simplistic
|
import httplib
|
||||||
return compat_urllib_request.HTTPSHandler()
|
|
||||||
|
class HTTPSConnectionV3(httplib.HTTPSConnection):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
httplib.HTTPSConnection.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
sock = socket.create_connection((self.host, self.port), self.timeout)
|
||||||
|
if self._tunnel_host:
|
||||||
|
self.sock = sock
|
||||||
|
self._tunnel()
|
||||||
|
try:
|
||||||
|
self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, ssl_version=ssl.PROTOCOL_SSLv3)
|
||||||
|
except ssl.SSLError as e:
|
||||||
|
self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, ssl_version=ssl.PROTOCOL_SSLv23)
|
||||||
|
|
||||||
|
class HTTPSHandlerV3(compat_urllib_request.HTTPSHandler):
|
||||||
|
def https_open(self, req):
|
||||||
|
return self.do_open(HTTPSConnectionV3, req)
|
||||||
|
return HTTPSHandlerV3()
|
||||||
else:
|
else:
|
||||||
import ssl
|
context = ssl.SSLContext(ssl.PROTOCOL_SSLv3)
|
||||||
context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
|
|
||||||
context.set_default_verify_paths()
|
context.set_default_verify_paths()
|
||||||
|
|
||||||
context.verify_mode = (ssl.CERT_NONE
|
context.verify_mode = (ssl.CERT_NONE
|
||||||
@@ -734,6 +753,8 @@ def unified_strdate(date_str):
|
|||||||
'%Y/%m/%d %H:%M:%S',
|
'%Y/%m/%d %H:%M:%S',
|
||||||
'%d.%m.%Y %H:%M',
|
'%d.%m.%Y %H:%M',
|
||||||
'%Y-%m-%dT%H:%M:%SZ',
|
'%Y-%m-%dT%H:%M:%SZ',
|
||||||
|
'%Y-%m-%dT%H:%M:%S.%fZ',
|
||||||
|
'%Y-%m-%dT%H:%M:%S.%f0Z',
|
||||||
'%Y-%m-%dT%H:%M:%S',
|
'%Y-%m-%dT%H:%M:%S',
|
||||||
]
|
]
|
||||||
for expression in format_expressions:
|
for expression in format_expressions:
|
||||||
@@ -949,7 +970,16 @@ class locked_file(object):
|
|||||||
|
|
||||||
|
|
||||||
def shell_quote(args):
|
def shell_quote(args):
|
||||||
return ' '.join(map(pipes.quote, args))
|
quoted_args = []
|
||||||
|
encoding = sys.getfilesystemencoding()
|
||||||
|
if encoding is None:
|
||||||
|
encoding = 'utf-8'
|
||||||
|
for a in args:
|
||||||
|
if isinstance(a, bytes):
|
||||||
|
# We may get a filename encoded with 'encodeFilename'
|
||||||
|
a = a.decode(encoding)
|
||||||
|
quoted_args.append(pipes.quote(a))
|
||||||
|
return u' '.join(quoted_args)
|
||||||
|
|
||||||
|
|
||||||
def takewhile_inclusive(pred, seq):
|
def takewhile_inclusive(pred, seq):
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
|
|
||||||
__version__ = '2013.11.17'
|
__version__ = '2013.11.24.1'
|
||||||
|
|||||||
Reference in New Issue
Block a user