Compare commits
97 Commits
2013.02.19
...
2013.04.03
Author | SHA1 | Date | |
---|---|---|---|
|
c2b293ba30 | ||
|
37cd9f522f | ||
|
f33154cd39 | ||
|
bafeed9f5d | ||
|
ef767f9fd5 | ||
|
bc97f6d60c | ||
|
90a99c1b5e | ||
|
f375d4b7de | ||
|
fa41fbd318 | ||
|
6a205c8876 | ||
|
0fb3756409 | ||
|
fbbdf475b1 | ||
|
c238be3e3a | ||
|
1bf2801e6a | ||
|
c9c8402093 | ||
|
6060788083 | ||
|
e3700fc9e4 | ||
|
b693216d8d | ||
|
46b9d8295d | ||
|
7decf8951c | ||
|
1f46c15262 | ||
|
0cd358676c | ||
|
43113d92cc | ||
|
44e939514e | ||
|
95506f1235 | ||
|
a91556fd74 | ||
|
1447f728b5 | ||
|
898280a056 | ||
|
59b4a2f0e4 | ||
|
1ee9778405 | ||
|
db74c11d2b | ||
|
5011cded16 | ||
|
f10b2a9c14 | ||
|
5cb3c0b319 | ||
|
b9fc428494 | ||
|
c0ba104674 | ||
|
2a4093eaf3 | ||
|
9e62bc4439 | ||
|
553d097442 | ||
|
ae608b8076 | ||
|
c397187061 | ||
|
e32b06e977 | ||
|
8c42c506cd | ||
|
8cc83b8dbe | ||
|
51af426d89 | ||
|
08ec0af7c6 | ||
|
3b221c5406 | ||
|
3d3423574d | ||
|
e5edd51de4 | ||
|
64c78d50cc | ||
|
b3bcca0844 | ||
|
61e40c88a9 | ||
|
40634747f7 | ||
|
c2e21f2f0d | ||
|
47dcd621c0 | ||
|
c9fa1cbab6 | ||
|
e5f30ade10 | ||
|
6622d22c79 | ||
|
4e1582f372 | ||
|
967897fd22 | ||
|
f918ec7ea2 | ||
|
a2ae43a55f | ||
|
7ae153ee9c | ||
|
f7b567ff84 | ||
|
f2e237adc8 | ||
|
2e5457be1d | ||
|
7f9d41a55e | ||
|
8207626bbe | ||
|
691db5ba02 | ||
|
acb8752f80 | ||
|
679790eee1 | ||
|
6bf48bd866 | ||
|
790d4fcbe1 | ||
|
89de9eb125 | ||
|
6324fd1d74 | ||
|
9e07cf2955 | ||
|
f03b88b3fb | ||
|
97d0365f49 | ||
|
12887875a2 | ||
|
450e709972 | ||
|
9befce2b8c | ||
|
cb99797798 | ||
|
f82b28146a | ||
|
4dc72b830c | ||
|
ea05129ebd | ||
|
35d217133f | ||
|
d1b7a24354 | ||
|
c85538dba1 | ||
|
60bd48b175 | ||
|
4be0aa3539 | ||
|
f636c34481 | ||
|
3bf79c752e | ||
|
cdb130b09a | ||
|
2e5d60b7db | ||
|
8271226a55 | ||
|
1013186a17 | ||
|
7c038b3c32 |
14
README.md
14
README.md
@@ -18,7 +18,7 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
--version print program version and exit
|
--version print program version and exit
|
||||||
-U, --update update this program to latest version
|
-U, --update update this program to latest version
|
||||||
-i, --ignore-errors continue on download errors
|
-i, --ignore-errors continue on download errors
|
||||||
-r, --rate-limit LIMIT download rate limit (e.g. 50k or 44.6m)
|
-r, --rate-limit LIMIT maximum download rate (e.g. 50k or 44.6m)
|
||||||
-R, --retries RETRIES number of retries (default is 10)
|
-R, --retries RETRIES number of retries (default is 10)
|
||||||
--buffer-size SIZE size of download buffer (e.g. 1024 or 16k) (default
|
--buffer-size SIZE size of download buffer (e.g. 1024 or 16k) (default
|
||||||
is 1024)
|
is 1024)
|
||||||
@@ -97,10 +97,16 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
requested
|
requested
|
||||||
--max-quality FORMAT highest quality format to download
|
--max-quality FORMAT highest quality format to download
|
||||||
-F, --list-formats list all available formats (currently youtube only)
|
-F, --list-formats list all available formats (currently youtube only)
|
||||||
--write-srt write video closed captions to a .srt file
|
--write-sub write subtitle file (currently youtube only)
|
||||||
|
--only-sub downloads only the subtitles (no video)
|
||||||
|
--all-subs downloads all the available subtitles of the video
|
||||||
(currently youtube only)
|
(currently youtube only)
|
||||||
--srt-lang LANG language of the closed captions to download
|
--list-subs lists all available subtitles for the video
|
||||||
(optional) use IETF language tags like 'en'
|
(currently youtube only)
|
||||||
|
--sub-format LANG subtitle format [srt/sbv] (default=srt) (currently
|
||||||
|
youtube only)
|
||||||
|
--sub-lang LANG language of the subtitles to download (optional)
|
||||||
|
use IETF language tags like 'en'
|
||||||
|
|
||||||
## Authentication Options:
|
## Authentication Options:
|
||||||
-u, --username USERNAME account username
|
-u, --username USERNAME account username
|
||||||
|
57
devscripts/gh-pages/update-feed.py
Executable file
57
devscripts/gh-pages/update-feed.py
Executable file
@@ -0,0 +1,57 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
import textwrap
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
atom_template=textwrap.dedent("""\
|
||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<atom:feed xmlns:atom="http://www.w3.org/2005/Atom">
|
||||||
|
<atom:title>youtube-dl releases</atom:title>
|
||||||
|
<atom:id>youtube-dl-updates-feed</atom:id>
|
||||||
|
<atom:updated>@TIMESTAMP@</atom:updated>
|
||||||
|
@ENTRIES@
|
||||||
|
</atom:feed>""")
|
||||||
|
|
||||||
|
entry_template=textwrap.dedent("""
|
||||||
|
<atom:entry>
|
||||||
|
<atom:id>youtube-dl-@VERSION@</atom:id>
|
||||||
|
<atom:title>New version @VERSION@</atom:title>
|
||||||
|
<atom:link href="http://rg3.github.com/youtube-dl" />
|
||||||
|
<atom:content type="xhtml">
|
||||||
|
<div xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
Downloads available at <a href="http://youtube-dl.org/downloads/@VERSION@/">http://youtube-dl.org/downloads/@VERSION@/</a>
|
||||||
|
</div>
|
||||||
|
</atom:content>
|
||||||
|
<atom:author>
|
||||||
|
<atom:name>The youtube-dl maintainers</atom:name>
|
||||||
|
</atom:author>
|
||||||
|
<atom:updated>@TIMESTAMP@</atom:updated>
|
||||||
|
</atom:entry>
|
||||||
|
""")
|
||||||
|
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
now_iso = now.isoformat()
|
||||||
|
|
||||||
|
atom_template = atom_template.replace('@TIMESTAMP@',now_iso)
|
||||||
|
|
||||||
|
entries=[]
|
||||||
|
|
||||||
|
versions_info = json.load(open('update/versions.json'))
|
||||||
|
versions = list(versions_info['versions'].keys())
|
||||||
|
versions.sort()
|
||||||
|
|
||||||
|
for v in versions:
|
||||||
|
entry = entry_template.replace('@TIMESTAMP@',v.replace('.','-'))
|
||||||
|
entry = entry.replace('@VERSION@',v)
|
||||||
|
entries.append(entry)
|
||||||
|
|
||||||
|
entries_str = textwrap.indent(''.join(entries), '\t')
|
||||||
|
atom_template = atom_template.replace('@ENTRIES@', entries_str)
|
||||||
|
|
||||||
|
with open('update/releases.atom','w',encoding='utf-8') as atom_file:
|
||||||
|
atom_file.write(atom_template)
|
||||||
|
|
||||||
|
|
@@ -22,7 +22,7 @@ if [ ! -f "updates_key.pem" ]; then echo 'ERROR: updates_key.pem missing'; exit
|
|||||||
|
|
||||||
/bin/echo -e "\n### First of all, testing..."
|
/bin/echo -e "\n### First of all, testing..."
|
||||||
make cleanall
|
make cleanall
|
||||||
nosetests --with-coverage --cover-package=youtube_dl --cover-html test || exit 1
|
nosetests --with-coverage --cover-package=youtube_dl --cover-html test --stop || exit 1
|
||||||
|
|
||||||
/bin/echo -e "\n### Changing version in version.py..."
|
/bin/echo -e "\n### Changing version in version.py..."
|
||||||
sed -i "s/__version__ = '.*'/__version__ = '$version'/" youtube_dl/version.py
|
sed -i "s/__version__ = '.*'/__version__ = '$version'/" youtube_dl/version.py
|
||||||
@@ -69,6 +69,7 @@ ROOT=$(pwd)
|
|||||||
ORIGIN_URL=$(git config --get remote.origin.url)
|
ORIGIN_URL=$(git config --get remote.origin.url)
|
||||||
cd build/gh-pages
|
cd build/gh-pages
|
||||||
"$ROOT/devscripts/gh-pages/add-version.py" $version
|
"$ROOT/devscripts/gh-pages/add-version.py" $version
|
||||||
|
"$ROOT/devscripts/gh-pages/update-feed.py"
|
||||||
"$ROOT/devscripts/gh-pages/sign-versions.py" < "$ROOT/updates_key.pem"
|
"$ROOT/devscripts/gh-pages/sign-versions.py" < "$ROOT/updates_key.pem"
|
||||||
"$ROOT/devscripts/gh-pages/generate-download.py"
|
"$ROOT/devscripts/gh-pages/generate-download.py"
|
||||||
"$ROOT/devscripts/gh-pages/update-copyright.py"
|
"$ROOT/devscripts/gh-pages/update-copyright.py"
|
||||||
|
@@ -29,6 +29,7 @@
|
|||||||
"simulate": false,
|
"simulate": false,
|
||||||
"skip_download": false,
|
"skip_download": false,
|
||||||
"subtitleslang": null,
|
"subtitleslang": null,
|
||||||
|
"subtitlesformat": "srt",
|
||||||
"test": true,
|
"test": true,
|
||||||
"updatetime": true,
|
"updatetime": true,
|
||||||
"usenetrc": false,
|
"usenetrc": false,
|
||||||
@@ -36,5 +37,8 @@
|
|||||||
"verbose": true,
|
"verbose": true,
|
||||||
"writedescription": false,
|
"writedescription": false,
|
||||||
"writeinfojson": true,
|
"writeinfojson": true,
|
||||||
"writesubtitles": false
|
"writesubtitles": false,
|
||||||
|
"onlysubtitles": false,
|
||||||
|
"allsubtitles": false,
|
||||||
|
"listssubtitles": false
|
||||||
}
|
}
|
@@ -11,12 +11,18 @@ from youtube_dl.InfoExtractors import YoutubeIE, YoutubePlaylistIE
|
|||||||
|
|
||||||
class TestAllURLsMatching(unittest.TestCase):
|
class TestAllURLsMatching(unittest.TestCase):
|
||||||
def test_youtube_playlist_matching(self):
|
def test_youtube_playlist_matching(self):
|
||||||
self.assertTrue(YoutubePlaylistIE().suitable(u'ECUl4u3cNGP61MdtwGTqZA0MreSaDybji8'))
|
self.assertTrue(YoutubePlaylistIE.suitable(u'ECUl4u3cNGP61MdtwGTqZA0MreSaDybji8'))
|
||||||
self.assertTrue(YoutubePlaylistIE().suitable(u'PL63F0C78739B09958'))
|
self.assertTrue(YoutubePlaylistIE.suitable(u'UUBABnxM4Ar9ten8Mdjj1j0Q')) #585
|
||||||
self.assertFalse(YoutubePlaylistIE().suitable(u'PLtS2H6bU1M'))
|
self.assertTrue(YoutubePlaylistIE.suitable(u'PL63F0C78739B09958'))
|
||||||
|
self.assertTrue(YoutubePlaylistIE.suitable(u'https://www.youtube.com/playlist?list=UUBABnxM4Ar9ten8Mdjj1j0Q'))
|
||||||
|
self.assertTrue(YoutubePlaylistIE.suitable(u'https://www.youtube.com/course?list=ECUl4u3cNGP61MdtwGTqZA0MreSaDybji8'))
|
||||||
|
self.assertTrue(YoutubePlaylistIE.suitable(u'https://www.youtube.com/playlist?list=PLwP_SiAcdui0KVebT0mU9Apz359a4ubsC'))
|
||||||
|
self.assertTrue(YoutubePlaylistIE.suitable(u'https://www.youtube.com/watch?v=AV6J6_AeFEQ&playnext=1&list=PL4023E734DA416012')) #668
|
||||||
|
self.assertFalse(YoutubePlaylistIE.suitable(u'PLtS2H6bU1M'))
|
||||||
|
|
||||||
def test_youtube_matching(self):
|
def test_youtube_matching(self):
|
||||||
self.assertTrue(YoutubeIE().suitable(u'PLtS2H6bU1M'))
|
self.assertTrue(YoutubeIE.suitable(u'PLtS2H6bU1M'))
|
||||||
|
self.assertFalse(YoutubeIE.suitable(u'https://www.youtube.com/watch?v=AV6J6_AeFEQ&playnext=1&list=PL4023E734DA416012')) #668
|
||||||
|
|
||||||
def test_youtube_extract(self):
|
def test_youtube_extract(self):
|
||||||
self.assertEqual(YoutubeIE()._extract_id('http://www.youtube.com/watch?&v=BaW_jenozKc'), 'BaW_jenozKc')
|
self.assertEqual(YoutubeIE()._extract_id('http://www.youtube.com/watch?&v=BaW_jenozKc'), 'BaW_jenozKc')
|
||||||
|
@@ -20,6 +20,8 @@ from youtube_dl.utils import *
|
|||||||
DEF_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'tests.json')
|
DEF_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'tests.json')
|
||||||
PARAMETERS_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "parameters.json")
|
PARAMETERS_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "parameters.json")
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
# General configuration (from __init__, not very elegant...)
|
# General configuration (from __init__, not very elegant...)
|
||||||
jar = compat_cookiejar.CookieJar()
|
jar = compat_cookiejar.CookieJar()
|
||||||
cookie_processor = compat_urllib_request.HTTPCookieProcessor(jar)
|
cookie_processor = compat_urllib_request.HTTPCookieProcessor(jar)
|
||||||
@@ -79,9 +81,8 @@ def generator(test_case):
|
|||||||
params.update(test_case.get('params', {}))
|
params.update(test_case.get('params', {}))
|
||||||
|
|
||||||
fd = FileDownloader(params)
|
fd = FileDownloader(params)
|
||||||
fd.add_info_extractor(ie())
|
for ie in youtube_dl.InfoExtractors.gen_extractors():
|
||||||
for ien in test_case.get('add_ie', []):
|
fd.add_info_extractor(ie)
|
||||||
fd.add_info_extractor(getattr(youtube_dl.InfoExtractors, ien + 'IE')())
|
|
||||||
finished_hook_called = set()
|
finished_hook_called = set()
|
||||||
def _hook(status):
|
def _hook(status):
|
||||||
if status['status'] == 'finished':
|
if status['status'] == 'finished':
|
||||||
@@ -93,8 +94,20 @@ def generator(test_case):
|
|||||||
_try_rm(tc['file'])
|
_try_rm(tc['file'])
|
||||||
_try_rm(tc['file'] + '.part')
|
_try_rm(tc['file'] + '.part')
|
||||||
_try_rm(tc['file'] + '.info.json')
|
_try_rm(tc['file'] + '.info.json')
|
||||||
|
try:
|
||||||
|
for retry in range(1, RETRIES + 1):
|
||||||
try:
|
try:
|
||||||
fd.download([test_case['url']])
|
fd.download([test_case['url']])
|
||||||
|
except (DownloadError, ExtractorError) as err:
|
||||||
|
if retry == RETRIES: raise
|
||||||
|
|
||||||
|
# Check if the exception is not a network related one
|
||||||
|
if not err.exc_info[0] in (compat_urllib_error.URLError, socket.timeout, UnavailableVideoError):
|
||||||
|
raise
|
||||||
|
|
||||||
|
print('Retrying: {0} failed tries\n\n##########\n\n'.format(retry))
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
for tc in test_cases:
|
for tc in test_cases:
|
||||||
if not test_case.get('params', {}).get('skip_download', False):
|
if not test_case.get('params', {}).get('skip_download', False):
|
||||||
|
@@ -8,7 +8,7 @@ import json
|
|||||||
import os
|
import os
|
||||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
from youtube_dl.InfoExtractors import YoutubeUserIE,YoutubePlaylistIE
|
from youtube_dl.InfoExtractors import YoutubeUserIE, YoutubePlaylistIE, YoutubeIE
|
||||||
from youtube_dl.utils import *
|
from youtube_dl.utils import *
|
||||||
|
|
||||||
PARAMETERS_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "parameters.json")
|
PARAMETERS_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "parameters.json")
|
||||||
@@ -35,39 +35,51 @@ class FakeDownloader(object):
|
|||||||
|
|
||||||
class TestYoutubeLists(unittest.TestCase):
|
class TestYoutubeLists(unittest.TestCase):
|
||||||
def test_youtube_playlist(self):
|
def test_youtube_playlist(self):
|
||||||
DL = FakeDownloader()
|
dl = FakeDownloader()
|
||||||
IE = YoutubePlaylistIE(DL)
|
ie = YoutubePlaylistIE(dl)
|
||||||
IE.extract('https://www.youtube.com/playlist?list=PLwiyx1dc3P2JR9N8gQaQN_BCvlSlap7re')
|
ie.extract('https://www.youtube.com/playlist?list=PLwiyx1dc3P2JR9N8gQaQN_BCvlSlap7re')
|
||||||
self.assertEqual(DL.result, [
|
ytie_results = [YoutubeIE()._extract_id(r[0]) for r in dl.result]
|
||||||
['http://www.youtube.com/watch?v=bV9L5Ht9LgY'],
|
self.assertEqual(ytie_results, [ 'bV9L5Ht9LgY', 'FXxLjLQi3Fg', 'tU3Bgo5qJZE'])
|
||||||
['http://www.youtube.com/watch?v=FXxLjLQi3Fg'],
|
|
||||||
['http://www.youtube.com/watch?v=tU3Bgo5qJZE']
|
def test_issue_673(self):
|
||||||
])
|
dl = FakeDownloader()
|
||||||
|
ie = YoutubePlaylistIE(dl)
|
||||||
|
ie.extract('PLBB231211A4F62143')
|
||||||
|
self.assertTrue(len(dl.result) > 40)
|
||||||
|
|
||||||
def test_youtube_playlist_long(self):
|
def test_youtube_playlist_long(self):
|
||||||
DL = FakeDownloader()
|
dl = FakeDownloader()
|
||||||
IE = YoutubePlaylistIE(DL)
|
ie = YoutubePlaylistIE(dl)
|
||||||
IE.extract('https://www.youtube.com/playlist?list=UUBABnxM4Ar9ten8Mdjj1j0Q')
|
ie.extract('https://www.youtube.com/playlist?list=UUBABnxM4Ar9ten8Mdjj1j0Q')
|
||||||
self.assertTrue(len(DL.result) >= 799)
|
self.assertTrue(len(dl.result) >= 799)
|
||||||
|
|
||||||
|
def test_youtube_playlist_with_deleted(self):
|
||||||
|
#651
|
||||||
|
dl = FakeDownloader()
|
||||||
|
ie = YoutubePlaylistIE(dl)
|
||||||
|
ie.extract('https://www.youtube.com/playlist?list=PLwP_SiAcdui0KVebT0mU9Apz359a4ubsC')
|
||||||
|
ytie_results = [YoutubeIE()._extract_id(r[0]) for r in dl.result]
|
||||||
|
self.assertFalse('pElCt5oNDuI' in ytie_results)
|
||||||
|
self.assertFalse('KdPEApIVdWM' in ytie_results)
|
||||||
|
|
||||||
def test_youtube_course(self):
|
def test_youtube_course(self):
|
||||||
DL = FakeDownloader()
|
dl = FakeDownloader()
|
||||||
IE = YoutubePlaylistIE(DL)
|
ie = YoutubePlaylistIE(dl)
|
||||||
# TODO find a > 100 (paginating?) videos course
|
# TODO find a > 100 (paginating?) videos course
|
||||||
IE.extract('https://www.youtube.com/course?list=ECUl4u3cNGP61MdtwGTqZA0MreSaDybji8')
|
ie.extract('https://www.youtube.com/course?list=ECUl4u3cNGP61MdtwGTqZA0MreSaDybji8')
|
||||||
self.assertEqual(DL.result[0], ['http://www.youtube.com/watch?v=j9WZyLZCBzs'])
|
self.assertEqual(YoutubeIE()._extract_id(dl.result[0][0]), 'j9WZyLZCBzs')
|
||||||
self.assertEqual(len(DL.result), 25)
|
self.assertEqual(len(dl.result), 25)
|
||||||
self.assertEqual(DL.result[-1], ['http://www.youtube.com/watch?v=rYefUsYuEp0'])
|
self.assertEqual(YoutubeIE()._extract_id(dl.result[-1][0]), 'rYefUsYuEp0')
|
||||||
|
|
||||||
def test_youtube_channel(self):
|
def test_youtube_channel(self):
|
||||||
# I give up, please find a channel that does paginate and test this like test_youtube_playlist_long
|
# I give up, please find a channel that does paginate and test this like test_youtube_playlist_long
|
||||||
pass # TODO
|
pass # TODO
|
||||||
|
|
||||||
def test_youtube_user(self):
|
def test_youtube_user(self):
|
||||||
DL = FakeDownloader()
|
dl = FakeDownloader()
|
||||||
IE = YoutubeUserIE(DL)
|
ie = YoutubeUserIE(dl)
|
||||||
IE.extract('https://www.youtube.com/user/TheLinuxFoundation')
|
ie.extract('https://www.youtube.com/user/TheLinuxFoundation')
|
||||||
self.assertTrue(len(DL.result) >= 320)
|
self.assertTrue(len(dl.result) >= 320)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@@ -38,20 +38,63 @@ class FakeDownloader(object):
|
|||||||
md5 = lambda s: hashlib.md5(s.encode('utf-8')).hexdigest()
|
md5 = lambda s: hashlib.md5(s.encode('utf-8')).hexdigest()
|
||||||
|
|
||||||
class TestYoutubeSubtitles(unittest.TestCase):
|
class TestYoutubeSubtitles(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
DL = FakeDownloader()
|
||||||
|
DL.params['allsubtitles'] = False
|
||||||
|
DL.params['writesubtitles'] = False
|
||||||
|
DL.params['subtitlesformat'] = 'srt'
|
||||||
|
DL.params['listsubtitles'] = False
|
||||||
|
def test_youtube_no_subtitles(self):
|
||||||
|
DL = FakeDownloader()
|
||||||
|
DL.params['writesubtitles'] = False
|
||||||
|
IE = YoutubeIE(DL)
|
||||||
|
info_dict = IE.extract('QRS8MkLhQmM')
|
||||||
|
subtitles = info_dict[0]['subtitles']
|
||||||
|
self.assertEqual(subtitles, None)
|
||||||
def test_youtube_subtitles(self):
|
def test_youtube_subtitles(self):
|
||||||
DL = FakeDownloader()
|
DL = FakeDownloader()
|
||||||
DL.params['writesubtitles'] = True
|
DL.params['writesubtitles'] = True
|
||||||
IE = YoutubeIE(DL)
|
IE = YoutubeIE(DL)
|
||||||
info_dict = IE.extract('QRS8MkLhQmM')
|
info_dict = IE.extract('QRS8MkLhQmM')
|
||||||
self.assertEqual(md5(info_dict[0]['subtitles']), 'c3228550d59116f3c29fba370b55d033')
|
sub = info_dict[0]['subtitles'][0]
|
||||||
|
self.assertEqual(md5(sub[2]), '4cd9278a35ba2305f47354ee13472260')
|
||||||
def test_youtube_subtitles_it(self):
|
def test_youtube_subtitles_it(self):
|
||||||
DL = FakeDownloader()
|
DL = FakeDownloader()
|
||||||
DL.params['writesubtitles'] = True
|
DL.params['writesubtitles'] = True
|
||||||
DL.params['subtitleslang'] = 'it'
|
DL.params['subtitleslang'] = 'it'
|
||||||
IE = YoutubeIE(DL)
|
IE = YoutubeIE(DL)
|
||||||
info_dict = IE.extract('QRS8MkLhQmM')
|
info_dict = IE.extract('QRS8MkLhQmM')
|
||||||
self.assertEqual(md5(info_dict[0]['subtitles']), '132a88a0daf8e1520f393eb58f1f646a')
|
sub = info_dict[0]['subtitles'][0]
|
||||||
|
self.assertEqual(md5(sub[2]), '164a51f16f260476a05b50fe4c2f161d')
|
||||||
|
def test_youtube_onlysubtitles(self):
|
||||||
|
DL = FakeDownloader()
|
||||||
|
DL.params['writesubtitles'] = True
|
||||||
|
DL.params['onlysubtitles'] = True
|
||||||
|
IE = YoutubeIE(DL)
|
||||||
|
info_dict = IE.extract('QRS8MkLhQmM')
|
||||||
|
sub = info_dict[0]['subtitles'][0]
|
||||||
|
self.assertEqual(md5(sub[2]), '4cd9278a35ba2305f47354ee13472260')
|
||||||
|
def test_youtube_allsubtitles(self):
|
||||||
|
DL = FakeDownloader()
|
||||||
|
DL.params['allsubtitles'] = True
|
||||||
|
IE = YoutubeIE(DL)
|
||||||
|
info_dict = IE.extract('QRS8MkLhQmM')
|
||||||
|
subtitles = info_dict[0]['subtitles']
|
||||||
|
self.assertEqual(len(subtitles), 12)
|
||||||
|
def test_youtube_subtitles_format(self):
|
||||||
|
DL = FakeDownloader()
|
||||||
|
DL.params['writesubtitles'] = True
|
||||||
|
DL.params['subtitlesformat'] = 'sbv'
|
||||||
|
IE = YoutubeIE(DL)
|
||||||
|
info_dict = IE.extract('QRS8MkLhQmM')
|
||||||
|
sub = info_dict[0]['subtitles'][0]
|
||||||
|
self.assertEqual(md5(sub[2]), '13aeaa0c245a8bed9a451cb643e3ad8b')
|
||||||
|
def test_youtube_list_subtitles(self):
|
||||||
|
DL = FakeDownloader()
|
||||||
|
DL.params['listsubtitles'] = True
|
||||||
|
IE = YoutubeIE(DL)
|
||||||
|
info_dict = IE.extract('QRS8MkLhQmM')
|
||||||
|
self.assertEqual(info_dict, None)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@@ -128,18 +128,6 @@
|
|||||||
"file": "0732f586d7.mp4",
|
"file": "0732f586d7.mp4",
|
||||||
"md5": "f647e9e90064b53b6e046e75d0241fbd"
|
"md5": "f647e9e90064b53b6e046e75d0241fbd"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "TweetReel",
|
|
||||||
"url": "http://tweetreel.com/?77smq",
|
|
||||||
"file": "77smq.mov",
|
|
||||||
"md5": "56b4d9ca9de467920f3f99a6d91255d6",
|
|
||||||
"info_dict": {
|
|
||||||
"uploader": "itszero",
|
|
||||||
"uploader_id": "itszero",
|
|
||||||
"upload_date": "20091225",
|
|
||||||
"description": "Installing Gentoo Linux on Powerbook G4, it turns out the sleep indicator becomes HDD activity indicator :D"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "Steam",
|
"name": "Steam",
|
||||||
"url": "http://store.steampowered.com/video/105600/",
|
"url": "http://store.steampowered.com/video/105600/",
|
||||||
@@ -293,7 +281,8 @@
|
|||||||
"file": "102.mp4",
|
"file": "102.mp4",
|
||||||
"md5": "7bc087e71d16f18f9b8ab9fa62a8a031",
|
"md5": "7bc087e71d16f18f9b8ab9fa62a8a031",
|
||||||
"info_dict": {
|
"info_dict": {
|
||||||
"title": "Dan Dennett: The illusion of consciousness"
|
"title": "Dan Dennett: The illusion of consciousness",
|
||||||
|
"thumbnail": "http://images.ted.com/images/ted/488_389x292.jpg"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -304,5 +293,40 @@
|
|||||||
"info_dict": {
|
"info_dict": {
|
||||||
"title": "Absolute Mehrheit vom 17.02.2013 - Die Highlights, Teil 2"
|
"title": "Absolute Mehrheit vom 17.02.2013 - Die Highlights, Teil 2"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Generic",
|
||||||
|
"url": "http://www.hodiho.fr/2013/02/regis-plante-sa-jeep.html",
|
||||||
|
"file": "13601338388002.mp4",
|
||||||
|
"md5": "85b90ccc9d73b4acd9138d3af4c27f89"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Spiegel",
|
||||||
|
"url": "http://www.spiegel.de/video/vulkan-tungurahua-in-ecuador-ist-wieder-aktiv-video-1259285.html",
|
||||||
|
"file": "1259285.mp4",
|
||||||
|
"md5": "2c2754212136f35fb4b19767d242f66e",
|
||||||
|
"info_dict": {
|
||||||
|
"title": "Vulkanausbruch in Ecuador: Der \"Feuerschlund\" ist wieder aktiv"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LiveLeak",
|
||||||
|
"md5": "0813c2430bea7a46bf13acf3406992f4",
|
||||||
|
"url": "http://www.liveleak.com/view?i=757_1364311680",
|
||||||
|
"file": "757_1364311680.mp4",
|
||||||
|
"info_dict": {
|
||||||
|
"title": "Most unlucky car accident",
|
||||||
|
"description": "extremely bad day for this guy..!",
|
||||||
|
"uploader": "ljfriel2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "WorldStarHipHop",
|
||||||
|
"url": "http://www.worldstarhiphop.com/videos/video.php?v=wshh6a7q1ny0G34ZwuIO",
|
||||||
|
"file": "wshh6a7q1ny0G34ZwuIO.mp4",
|
||||||
|
"md5": "9d04de741161603bf7071bbf4e883186",
|
||||||
|
"info_dict": {
|
||||||
|
"title": "Video: KO Of The Week: MMA Fighter Gets Knocked Out By Swift Head Kick! "
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
BIN
youtube-dl
BIN
youtube-dl
Binary file not shown.
@@ -78,7 +78,11 @@ class FileDownloader(object):
|
|||||||
updatetime: Use the Last-modified header to set output file timestamps.
|
updatetime: Use the Last-modified header to set output file timestamps.
|
||||||
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
|
||||||
writesubtitles: Write the video subtitles to a .srt file
|
writesubtitles: Write the video subtitles to a file
|
||||||
|
onlysubtitles: Downloads only the subtitles of the video
|
||||||
|
allsubtitles: Downloads all the subtitles of the video
|
||||||
|
listsubtitles: Lists all available subtitles for the video
|
||||||
|
subtitlesformat: Subtitle format [sbv/srt] (default=srt)
|
||||||
subtitleslang: Language of the subtitles to download
|
subtitleslang: Language of the subtitles to download
|
||||||
test: Download only first bytes to test the downloader.
|
test: Download only first bytes to test the downloader.
|
||||||
keepvideo: Keep the video file after post-processing
|
keepvideo: Keep the video file after post-processing
|
||||||
@@ -104,7 +108,7 @@ class FileDownloader(object):
|
|||||||
self.params = params
|
self.params = params
|
||||||
|
|
||||||
if '%(stitle)s' in self.params['outtmpl']:
|
if '%(stitle)s' in self.params['outtmpl']:
|
||||||
self.to_stderr(u'WARNING: %(stitle)s is deprecated. Use the %(title)s and the --restrict-filenames flag(which also secures %(uploader)s et al) instead.')
|
self.report_warning(u'%(stitle)s is deprecated. Use the %(title)s and the --restrict-filenames flag(which also secures %(uploader)s et al) instead.')
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def format_bytes(bytes):
|
def format_bytes(bytes):
|
||||||
@@ -227,13 +231,47 @@ class FileDownloader(object):
|
|||||||
self.to_stderr(message)
|
self.to_stderr(message)
|
||||||
if self.params.get('verbose'):
|
if self.params.get('verbose'):
|
||||||
if tb is None:
|
if tb is None:
|
||||||
|
if sys.exc_info()[0]: # if .trouble has been called from an except block
|
||||||
|
tb = u''
|
||||||
|
if hasattr(sys.exc_info()[1], 'exc_info') and sys.exc_info()[1].exc_info[0]:
|
||||||
|
tb += u''.join(traceback.format_exception(*sys.exc_info()[1].exc_info))
|
||||||
|
tb += compat_str(traceback.format_exc())
|
||||||
|
else:
|
||||||
tb_data = traceback.format_list(traceback.extract_stack())
|
tb_data = traceback.format_list(traceback.extract_stack())
|
||||||
tb = u''.join(tb_data)
|
tb = u''.join(tb_data)
|
||||||
self.to_stderr(tb)
|
self.to_stderr(tb)
|
||||||
if not self.params.get('ignoreerrors', False):
|
if not self.params.get('ignoreerrors', False):
|
||||||
raise DownloadError(message)
|
if sys.exc_info()[0] and hasattr(sys.exc_info()[1], 'exc_info') and sys.exc_info()[1].exc_info[0]:
|
||||||
|
exc_info = sys.exc_info()[1].exc_info
|
||||||
|
else:
|
||||||
|
exc_info = sys.exc_info()
|
||||||
|
raise DownloadError(message, exc_info)
|
||||||
self._download_retcode = 1
|
self._download_retcode = 1
|
||||||
|
|
||||||
|
def report_warning(self, message):
|
||||||
|
'''
|
||||||
|
Print the message to stderr, it will be prefixed with 'WARNING:'
|
||||||
|
If stderr is a tty file the 'WARNING:' will be colored
|
||||||
|
'''
|
||||||
|
if sys.stderr.isatty():
|
||||||
|
_msg_header=u'\033[0;33mWARNING:\033[0m'
|
||||||
|
else:
|
||||||
|
_msg_header=u'WARNING:'
|
||||||
|
warning_message=u'%s %s' % (_msg_header,message)
|
||||||
|
self.to_stderr(warning_message)
|
||||||
|
|
||||||
|
def report_error(self, message, tb=None):
|
||||||
|
'''
|
||||||
|
Do the same as trouble, but prefixes the message with 'ERROR:', colored
|
||||||
|
in red if stderr is a tty file.
|
||||||
|
'''
|
||||||
|
if sys.stderr.isatty():
|
||||||
|
_msg_header = u'\033[0;31mERROR:\033[0m'
|
||||||
|
else:
|
||||||
|
_msg_header = u'ERROR:'
|
||||||
|
error_message = u'%s %s' % (_msg_header, message)
|
||||||
|
self.trouble(error_message, tb)
|
||||||
|
|
||||||
def slow_down(self, start_time, byte_counter):
|
def slow_down(self, start_time, byte_counter):
|
||||||
"""Sleep if the download speed is over the rate limit."""
|
"""Sleep if the download speed is over the rate limit."""
|
||||||
rate_limit = self.params.get('ratelimit', None)
|
rate_limit = self.params.get('ratelimit', None)
|
||||||
@@ -265,7 +303,7 @@ class FileDownloader(object):
|
|||||||
return
|
return
|
||||||
os.rename(encodeFilename(old_filename), encodeFilename(new_filename))
|
os.rename(encodeFilename(old_filename), encodeFilename(new_filename))
|
||||||
except (IOError, OSError) as err:
|
except (IOError, OSError) as err:
|
||||||
self.trouble(u'ERROR: unable to rename file')
|
self.report_error(u'unable to rename file')
|
||||||
|
|
||||||
def try_utime(self, filename, last_modified_hdr):
|
def try_utime(self, filename, last_modified_hdr):
|
||||||
"""Try to set the last-modified time of the given file."""
|
"""Try to set the last-modified time of the given file."""
|
||||||
@@ -289,9 +327,9 @@ class FileDownloader(object):
|
|||||||
""" Report that the description file is being written """
|
""" Report that the description file is being written """
|
||||||
self.to_screen(u'[info] Writing video description to: ' + descfn)
|
self.to_screen(u'[info] Writing video description to: ' + descfn)
|
||||||
|
|
||||||
def report_writesubtitles(self, srtfn):
|
def report_writesubtitles(self, sub_filename):
|
||||||
""" Report that the subtitles file is being written """
|
""" Report that the subtitles file is being written """
|
||||||
self.to_screen(u'[info] Writing video subtitles to: ' + srtfn)
|
self.to_screen(u'[info] Writing video subtitles to: ' + sub_filename)
|
||||||
|
|
||||||
def report_writeinfojson(self, infofn):
|
def report_writeinfojson(self, infofn):
|
||||||
""" Report that the metadata file has been written """
|
""" Report that the metadata file has been written """
|
||||||
@@ -360,8 +398,11 @@ class FileDownloader(object):
|
|||||||
|
|
||||||
filename = self.params['outtmpl'] % template_dict
|
filename = self.params['outtmpl'] % template_dict
|
||||||
return filename
|
return filename
|
||||||
except (ValueError, KeyError) as err:
|
except KeyError as err:
|
||||||
self.trouble(u'ERROR: invalid system charset or erroneous output template')
|
self.trouble(u'ERROR: Erroneous output template')
|
||||||
|
return None
|
||||||
|
except ValueError as err:
|
||||||
|
self.trouble(u'ERROR: Insufficient system charset ' + repr(preferredencoding()))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _match_entry(self, info_dict):
|
def _match_entry(self, info_dict):
|
||||||
@@ -370,12 +411,10 @@ class FileDownloader(object):
|
|||||||
title = info_dict['title']
|
title = info_dict['title']
|
||||||
matchtitle = self.params.get('matchtitle', False)
|
matchtitle = self.params.get('matchtitle', False)
|
||||||
if matchtitle:
|
if matchtitle:
|
||||||
matchtitle = matchtitle.decode('utf8')
|
|
||||||
if not re.search(matchtitle, title, re.IGNORECASE):
|
if not re.search(matchtitle, title, re.IGNORECASE):
|
||||||
return u'[download] "' + title + '" title did not match pattern "' + matchtitle + '"'
|
return u'[download] "' + title + '" title did not match pattern "' + matchtitle + '"'
|
||||||
rejecttitle = self.params.get('rejecttitle', False)
|
rejecttitle = self.params.get('rejecttitle', False)
|
||||||
if rejecttitle:
|
if rejecttitle:
|
||||||
rejecttitle = rejecttitle.decode('utf8')
|
|
||||||
if re.search(rejecttitle, title, re.IGNORECASE):
|
if re.search(rejecttitle, title, re.IGNORECASE):
|
||||||
return u'"' + title + '" title matched reject pattern "' + rejecttitle + '"'
|
return u'"' + title + '" title matched reject pattern "' + rejecttitle + '"'
|
||||||
return None
|
return None
|
||||||
@@ -427,7 +466,7 @@ class FileDownloader(object):
|
|||||||
if dn != '' and not os.path.exists(dn): # dn is already encoded
|
if dn != '' and not os.path.exists(dn): # dn is already encoded
|
||||||
os.makedirs(dn)
|
os.makedirs(dn)
|
||||||
except (OSError, IOError) as err:
|
except (OSError, IOError) as err:
|
||||||
self.trouble(u'ERROR: unable to create directory ' + compat_str(err))
|
self.report_error(u'unable to create directory ' + compat_str(err))
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.params.get('writedescription', False):
|
if self.params.get('writedescription', False):
|
||||||
@@ -437,20 +476,47 @@ class FileDownloader(object):
|
|||||||
with io.open(encodeFilename(descfn), 'w', encoding='utf-8') as descfile:
|
with io.open(encodeFilename(descfn), 'w', encoding='utf-8') as descfile:
|
||||||
descfile.write(info_dict['description'])
|
descfile.write(info_dict['description'])
|
||||||
except (OSError, IOError):
|
except (OSError, IOError):
|
||||||
self.trouble(u'ERROR: Cannot write description file ' + descfn)
|
self.report_error(u'Cannot write description file ' + descfn)
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.params.get('writesubtitles', False) and 'subtitles' in info_dict and info_dict['subtitles']:
|
if self.params.get('writesubtitles', False) and 'subtitles' in info_dict and info_dict['subtitles']:
|
||||||
# subtitles download errors are already managed as troubles in relevant IE
|
# subtitles download errors are already managed as troubles in relevant IE
|
||||||
# that way it will silently go on when used with unsupporting IE
|
# that way it will silently go on when used with unsupporting IE
|
||||||
|
subtitle = info_dict['subtitles'][0]
|
||||||
|
(sub_error, sub_lang, sub) = subtitle
|
||||||
|
sub_format = self.params.get('subtitlesformat')
|
||||||
|
if sub_error:
|
||||||
|
self.report_warning("Some error while getting the subtitles")
|
||||||
|
else:
|
||||||
try:
|
try:
|
||||||
srtfn = filename.rsplit('.', 1)[0] + u'.srt'
|
sub_filename = filename.rsplit('.', 1)[0] + u'.' + sub_lang + u'.' + sub_format
|
||||||
self.report_writesubtitles(srtfn)
|
self.report_writesubtitles(sub_filename)
|
||||||
with io.open(encodeFilename(srtfn), 'w', encoding='utf-8') as srtfile:
|
with io.open(encodeFilename(sub_filename), 'w', encoding='utf-8') as subfile:
|
||||||
srtfile.write(info_dict['subtitles'])
|
subfile.write(sub)
|
||||||
|
except (OSError, IOError):
|
||||||
|
self.report_error(u'Cannot write subtitles file ' + descfn)
|
||||||
|
return
|
||||||
|
if self.params.get('onlysubtitles', False):
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.params.get('allsubtitles', False) and 'subtitles' in info_dict and info_dict['subtitles']:
|
||||||
|
subtitles = info_dict['subtitles']
|
||||||
|
sub_format = self.params.get('subtitlesformat')
|
||||||
|
for subtitle in subtitles:
|
||||||
|
(sub_error, sub_lang, sub) = subtitle
|
||||||
|
if sub_error:
|
||||||
|
self.report_warning("Some error while getting the subtitles")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
sub_filename = filename.rsplit('.', 1)[0] + u'.' + sub_lang + u'.' + sub_format
|
||||||
|
self.report_writesubtitles(sub_filename)
|
||||||
|
with io.open(encodeFilename(sub_filename), 'w', encoding='utf-8') as subfile:
|
||||||
|
subfile.write(sub)
|
||||||
except (OSError, IOError):
|
except (OSError, IOError):
|
||||||
self.trouble(u'ERROR: Cannot write subtitles file ' + descfn)
|
self.trouble(u'ERROR: Cannot write subtitles file ' + descfn)
|
||||||
return
|
return
|
||||||
|
if self.params.get('onlysubtitles', False):
|
||||||
|
return
|
||||||
|
|
||||||
if self.params.get('writeinfojson', False):
|
if self.params.get('writeinfojson', False):
|
||||||
infofn = filename + u'.info.json'
|
infofn = filename + u'.info.json'
|
||||||
@@ -459,7 +525,7 @@ class FileDownloader(object):
|
|||||||
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'])
|
||||||
write_json_file(json_info_dict, encodeFilename(infofn))
|
write_json_file(json_info_dict, encodeFilename(infofn))
|
||||||
except (OSError, IOError):
|
except (OSError, IOError):
|
||||||
self.trouble(u'ERROR: Cannot write metadata to JSON file ' + infofn)
|
self.report_error(u'Cannot write metadata to JSON file ' + infofn)
|
||||||
return
|
return
|
||||||
|
|
||||||
if not self.params.get('skip_download', False):
|
if not self.params.get('skip_download', False):
|
||||||
@@ -471,17 +537,17 @@ class FileDownloader(object):
|
|||||||
except (OSError, IOError) as err:
|
except (OSError, IOError) as err:
|
||||||
raise UnavailableVideoError()
|
raise UnavailableVideoError()
|
||||||
except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
|
except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
|
||||||
self.trouble(u'ERROR: unable to download video data: %s' % str(err))
|
self.report_error(u'unable to download video data: %s' % str(err))
|
||||||
return
|
return
|
||||||
except (ContentTooShortError, ) as err:
|
except (ContentTooShortError, ) as err:
|
||||||
self.trouble(u'ERROR: content too short (expected %s bytes and served %s)' % (err.expected, err.downloaded))
|
self.report_error(u'content too short (expected %s bytes and served %s)' % (err.expected, err.downloaded))
|
||||||
return
|
return
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
try:
|
try:
|
||||||
self.post_process(filename, info_dict)
|
self.post_process(filename, info_dict)
|
||||||
except (PostProcessingError) as err:
|
except (PostProcessingError) as err:
|
||||||
self.trouble(u'ERROR: postprocessing: %s' % str(err))
|
self.report_error(u'postprocessing: %s' % str(err))
|
||||||
return
|
return
|
||||||
|
|
||||||
def download(self, url_list):
|
def download(self, url_list):
|
||||||
@@ -498,7 +564,7 @@ class FileDownloader(object):
|
|||||||
|
|
||||||
# Warn if the _WORKING attribute is False
|
# Warn if the _WORKING attribute is False
|
||||||
if not ie.working():
|
if not ie.working():
|
||||||
self.to_stderr(u'WARNING: the program functionality for this site has been marked as broken, '
|
self.report_warning(u'the program functionality for this site has been marked as broken, '
|
||||||
u'and will probably not work. If you want to go on, use the -i option.')
|
u'and will probably not work. If you want to go on, use the -i option.')
|
||||||
|
|
||||||
# Suitable InfoExtractor found
|
# Suitable InfoExtractor found
|
||||||
@@ -510,9 +576,12 @@ class FileDownloader(object):
|
|||||||
except ExtractorError as de: # An error we somewhat expected
|
except ExtractorError as de: # An error we somewhat expected
|
||||||
self.trouble(u'ERROR: ' + compat_str(de), de.format_traceback())
|
self.trouble(u'ERROR: ' + compat_str(de), de.format_traceback())
|
||||||
break
|
break
|
||||||
|
except MaxDownloadsReached:
|
||||||
|
self.to_screen(u'[info] Maximum number of downloaded files reached.')
|
||||||
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if self.params.get('ignoreerrors', False):
|
if self.params.get('ignoreerrors', False):
|
||||||
self.trouble(u'ERROR: ' + compat_str(e), tb=compat_str(traceback.format_exc()))
|
self.report_error(u'' + compat_str(e), tb=compat_str(traceback.format_exc()))
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
@@ -526,13 +595,14 @@ class FileDownloader(object):
|
|||||||
self.increment_downloads()
|
self.increment_downloads()
|
||||||
self.process_info(video)
|
self.process_info(video)
|
||||||
except UnavailableVideoError:
|
except UnavailableVideoError:
|
||||||
self.trouble(u'\nERROR: unable to download video')
|
self.to_stderr(u"\n")
|
||||||
|
self.report_error(u'unable to download video')
|
||||||
|
|
||||||
# Suitable InfoExtractor had been found; go to next URL
|
# Suitable InfoExtractor had been found; go to next URL
|
||||||
break
|
break
|
||||||
|
|
||||||
if not suitable_found:
|
if not suitable_found:
|
||||||
self.trouble(u'ERROR: no suitable InfoExtractor: %s' % url)
|
self.report_error(u'no suitable InfoExtractor: %s' % url)
|
||||||
|
|
||||||
return self._download_retcode
|
return self._download_retcode
|
||||||
|
|
||||||
@@ -554,10 +624,10 @@ class FileDownloader(object):
|
|||||||
self.to_stderr(u'ERROR: ' + e.msg)
|
self.to_stderr(u'ERROR: ' + e.msg)
|
||||||
if keep_video is False and not self.params.get('keepvideo', False):
|
if keep_video is False and not self.params.get('keepvideo', False):
|
||||||
try:
|
try:
|
||||||
self.to_stderr(u'Deleting original file %s (pass -k to keep)' % filename)
|
self.to_screen(u'Deleting original file %s (pass -k to keep)' % filename)
|
||||||
os.remove(encodeFilename(filename))
|
os.remove(encodeFilename(filename))
|
||||||
except (IOError, OSError):
|
except (IOError, OSError):
|
||||||
self.to_stderr(u'WARNING: Unable to remove downloaded video file')
|
self.report_warning(u'Unable to remove downloaded video file')
|
||||||
|
|
||||||
def _download_with_rtmpdump(self, filename, url, player_url, page_url):
|
def _download_with_rtmpdump(self, filename, url, player_url, page_url):
|
||||||
self.report_destination(filename)
|
self.report_destination(filename)
|
||||||
@@ -565,9 +635,9 @@ class FileDownloader(object):
|
|||||||
|
|
||||||
# Check for rtmpdump first
|
# Check for rtmpdump first
|
||||||
try:
|
try:
|
||||||
subprocess.call(['rtmpdump', '-h'], stdout=(file(os.path.devnull, 'w')), stderr=subprocess.STDOUT)
|
subprocess.call(['rtmpdump', '-h'], stdout=(open(os.path.devnull, 'w')), stderr=subprocess.STDOUT)
|
||||||
except (OSError, IOError):
|
except (OSError, IOError):
|
||||||
self.trouble(u'ERROR: RTMP download detected but "rtmpdump" could not be run')
|
self.report_error(u'RTMP download detected but "rtmpdump" could not be run')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Download using rtmpdump. rtmpdump returns exit code 2 when
|
# Download using rtmpdump. rtmpdump returns exit code 2 when
|
||||||
@@ -612,7 +682,8 @@ class FileDownloader(object):
|
|||||||
})
|
})
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
self.trouble(u'\nERROR: rtmpdump exited with code %d' % retval)
|
self.to_stderr(u"\n")
|
||||||
|
self.report_error(u'rtmpdump exited with code %d' % retval)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _do_download(self, filename, info_dict):
|
def _do_download(self, filename, info_dict):
|
||||||
@@ -712,7 +783,7 @@ class FileDownloader(object):
|
|||||||
self.report_retry(count, retries)
|
self.report_retry(count, retries)
|
||||||
|
|
||||||
if count > retries:
|
if count > retries:
|
||||||
self.trouble(u'ERROR: giving up after %s retries' % retries)
|
self.report_error(u'giving up after %s retries' % retries)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
data_len = data.info().get('Content-length', None)
|
data_len = data.info().get('Content-length', None)
|
||||||
@@ -748,12 +819,13 @@ class FileDownloader(object):
|
|||||||
filename = self.undo_temp_name(tmpfilename)
|
filename = self.undo_temp_name(tmpfilename)
|
||||||
self.report_destination(filename)
|
self.report_destination(filename)
|
||||||
except (OSError, IOError) as err:
|
except (OSError, IOError) as err:
|
||||||
self.trouble(u'ERROR: unable to open for writing: %s' % str(err))
|
self.report_error(u'unable to open for writing: %s' % str(err))
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
stream.write(data_block)
|
stream.write(data_block)
|
||||||
except (IOError, OSError) as err:
|
except (IOError, OSError) as err:
|
||||||
self.trouble(u'\nERROR: unable to write data: %s' % str(err))
|
self.to_stderr(u"\n")
|
||||||
|
self.report_error(u'unable to write data: %s' % str(err))
|
||||||
return False
|
return False
|
||||||
if not self.params.get('noresizebuffer', False):
|
if not self.params.get('noresizebuffer', False):
|
||||||
block_size = self.best_block_size(after - before, len(data_block))
|
block_size = self.best_block_size(after - before, len(data_block))
|
||||||
@@ -779,7 +851,8 @@ class FileDownloader(object):
|
|||||||
self.slow_down(start, byte_counter - resume_len)
|
self.slow_down(start, byte_counter - resume_len)
|
||||||
|
|
||||||
if stream is None:
|
if stream is None:
|
||||||
self.trouble(u'\nERROR: Did not get any data blocks')
|
self.to_stderr(u"\n")
|
||||||
|
self.report_error(u'Did not get any data blocks')
|
||||||
return False
|
return False
|
||||||
stream.close()
|
stream.close()
|
||||||
self.report_finish()
|
self.report_finish()
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -126,7 +126,7 @@ def parseOpts():
|
|||||||
general.add_option('-i', '--ignore-errors',
|
general.add_option('-i', '--ignore-errors',
|
||||||
action='store_true', dest='ignoreerrors', help='continue on download errors', default=False)
|
action='store_true', dest='ignoreerrors', help='continue on download errors', default=False)
|
||||||
general.add_option('-r', '--rate-limit',
|
general.add_option('-r', '--rate-limit',
|
||||||
dest='ratelimit', metavar='LIMIT', help='download rate limit (e.g. 50k or 44.6m)')
|
dest='ratelimit', metavar='LIMIT', help='maximum download rate (e.g. 50k or 44.6m)')
|
||||||
general.add_option('-R', '--retries',
|
general.add_option('-R', '--retries',
|
||||||
dest='retries', metavar='RETRIES', help='number of retries (default is %default)', default=10)
|
dest='retries', metavar='RETRIES', help='number of retries (default is %default)', default=10)
|
||||||
general.add_option('--buffer-size',
|
general.add_option('--buffer-size',
|
||||||
@@ -173,12 +173,24 @@ def parseOpts():
|
|||||||
action='store', dest='format_limit', metavar='FORMAT', help='highest quality format to download')
|
action='store', dest='format_limit', metavar='FORMAT', help='highest quality format to download')
|
||||||
video_format.add_option('-F', '--list-formats',
|
video_format.add_option('-F', '--list-formats',
|
||||||
action='store_true', dest='listformats', help='list all available formats (currently youtube only)')
|
action='store_true', dest='listformats', help='list all available formats (currently youtube only)')
|
||||||
video_format.add_option('--write-srt',
|
video_format.add_option('--write-sub', '--write-srt',
|
||||||
action='store_true', dest='writesubtitles',
|
action='store_true', dest='writesubtitles',
|
||||||
help='write video closed captions to a .srt file (currently youtube only)', default=False)
|
help='write subtitle file (currently youtube only)', default=False)
|
||||||
video_format.add_option('--srt-lang',
|
video_format.add_option('--only-sub',
|
||||||
|
action='store_true', dest='onlysubtitles',
|
||||||
|
help='downloads only the subtitles (no video)', default=False)
|
||||||
|
video_format.add_option('--all-subs',
|
||||||
|
action='store_true', dest='allsubtitles',
|
||||||
|
help='downloads all the available subtitles of the video (currently youtube only)', default=False)
|
||||||
|
video_format.add_option('--list-subs',
|
||||||
|
action='store_true', dest='listsubtitles',
|
||||||
|
help='lists all available subtitles for the video (currently youtube only)', default=False)
|
||||||
|
video_format.add_option('--sub-format',
|
||||||
|
action='store', dest='subtitlesformat', metavar='LANG',
|
||||||
|
help='subtitle format [srt/sbv] (default=srt) (currently youtube only)', default='srt')
|
||||||
|
video_format.add_option('--sub-lang', '--srt-lang',
|
||||||
action='store', dest='subtitleslang', metavar='LANG',
|
action='store', dest='subtitleslang', metavar='LANG',
|
||||||
help='language of the closed captions to download (optional) use IETF language tags like \'en\'')
|
help='language of the subtitles to download (optional) use IETF language tags like \'en\'')
|
||||||
|
|
||||||
verbosity.add_option('-q', '--quiet',
|
verbosity.add_option('-q', '--quiet',
|
||||||
action='store_true', dest='quiet', help='activates quiet mode', default=False)
|
action='store_true', dest='quiet', help='activates quiet mode', default=False)
|
||||||
@@ -274,12 +286,20 @@ def parseOpts():
|
|||||||
|
|
||||||
xdg_config_home = os.environ.get('XDG_CONFIG_HOME')
|
xdg_config_home = os.environ.get('XDG_CONFIG_HOME')
|
||||||
if xdg_config_home:
|
if xdg_config_home:
|
||||||
userConf = os.path.join(xdg_config_home, 'youtube-dl.conf')
|
userConfFile = os.path.join(xdg_config_home, 'youtube-dl.conf')
|
||||||
else:
|
else:
|
||||||
userConf = os.path.join(os.path.expanduser('~'), '.config', 'youtube-dl.conf')
|
userConfFile = os.path.join(os.path.expanduser('~'), '.config', 'youtube-dl.conf')
|
||||||
argv = _readOptions('/etc/youtube-dl.conf') + _readOptions(userConf) + sys.argv[1:]
|
systemConf = _readOptions('/etc/youtube-dl.conf')
|
||||||
|
userConf = _readOptions(userConfFile)
|
||||||
|
commandLineConf = sys.argv[1:]
|
||||||
|
argv = systemConf + userConf + commandLineConf
|
||||||
opts, args = parser.parse_args(argv)
|
opts, args = parser.parse_args(argv)
|
||||||
|
|
||||||
|
if opts.verbose:
|
||||||
|
print(u'[debug] System config: ' + repr(systemConf))
|
||||||
|
print(u'[debug] User config: ' + repr(userConf))
|
||||||
|
print(u'[debug] Command-line args: ' + repr(commandLineConf))
|
||||||
|
|
||||||
return parser, opts, args
|
return parser, opts, args
|
||||||
|
|
||||||
def _real_main():
|
def _real_main():
|
||||||
@@ -412,6 +432,7 @@ def _real_main():
|
|||||||
or (opts.useid and u'%(id)s.%(ext)s')
|
or (opts.useid and u'%(id)s.%(ext)s')
|
||||||
or (opts.autonumber and u'%(autonumber)s-%(id)s.%(ext)s')
|
or (opts.autonumber and u'%(autonumber)s-%(id)s.%(ext)s')
|
||||||
or u'%(id)s.%(ext)s')
|
or u'%(id)s.%(ext)s')
|
||||||
|
|
||||||
# File downloader
|
# File downloader
|
||||||
fd = FileDownloader({
|
fd = FileDownloader({
|
||||||
'usenetrc': opts.usenetrc,
|
'usenetrc': opts.usenetrc,
|
||||||
@@ -449,9 +470,13 @@ def _real_main():
|
|||||||
'writedescription': opts.writedescription,
|
'writedescription': opts.writedescription,
|
||||||
'writeinfojson': opts.writeinfojson,
|
'writeinfojson': opts.writeinfojson,
|
||||||
'writesubtitles': opts.writesubtitles,
|
'writesubtitles': opts.writesubtitles,
|
||||||
|
'onlysubtitles': opts.onlysubtitles,
|
||||||
|
'allsubtitles': opts.allsubtitles,
|
||||||
|
'listsubtitles': opts.listsubtitles,
|
||||||
|
'subtitlesformat': opts.subtitlesformat,
|
||||||
'subtitleslang': opts.subtitleslang,
|
'subtitleslang': opts.subtitleslang,
|
||||||
'matchtitle': opts.matchtitle,
|
'matchtitle': decodeOption(opts.matchtitle),
|
||||||
'rejecttitle': opts.rejecttitle,
|
'rejecttitle': decodeOption(opts.rejecttitle),
|
||||||
'max_downloads': opts.max_downloads,
|
'max_downloads': opts.max_downloads,
|
||||||
'prefer_free_formats': opts.prefer_free_formats,
|
'prefer_free_formats': opts.prefer_free_formats,
|
||||||
'verbose': opts.verbose,
|
'verbose': opts.verbose,
|
||||||
|
@@ -77,10 +77,8 @@ def update_self(to_screen, verbose, filename):
|
|||||||
|
|
||||||
to_screen(u'Updating to version ' + versions_info['latest'] + '...')
|
to_screen(u'Updating to version ' + versions_info['latest'] + '...')
|
||||||
version = versions_info['versions'][versions_info['latest']]
|
version = versions_info['versions'][versions_info['latest']]
|
||||||
if version.get('notes'):
|
|
||||||
to_screen(u'PLEASE NOTE:')
|
print_notes(versions_info['versions'])
|
||||||
for note in version['notes']:
|
|
||||||
to_screen(note)
|
|
||||||
|
|
||||||
if not os.access(filename, os.W_OK):
|
if not os.access(filename, os.W_OK):
|
||||||
to_screen(u'ERROR: no write permissions on %s' % filename)
|
to_screen(u'ERROR: no write permissions on %s' % filename)
|
||||||
@@ -158,3 +156,13 @@ del "%s"
|
|||||||
return
|
return
|
||||||
|
|
||||||
to_screen(u'Updated youtube-dl. Restart youtube-dl to use the new version.')
|
to_screen(u'Updated youtube-dl. Restart youtube-dl to use the new version.')
|
||||||
|
|
||||||
|
def print_notes(versions, fromVersion=__version__):
|
||||||
|
notes = []
|
||||||
|
for v,vdata in sorted(versions.items()):
|
||||||
|
if v > fromVersion:
|
||||||
|
notes.extend(vdata.get('notes', []))
|
||||||
|
if notes:
|
||||||
|
to_screen(u'PLEASE NOTE:')
|
||||||
|
for note in notes:
|
||||||
|
to_screen(note)
|
||||||
|
@@ -311,7 +311,7 @@ def clean_html(html):
|
|||||||
html = re.sub('<.*?>', '', html)
|
html = re.sub('<.*?>', '', html)
|
||||||
# Replace html entities
|
# Replace html entities
|
||||||
html = unescapeHTML(html)
|
html = unescapeHTML(html)
|
||||||
return html
|
return html.strip()
|
||||||
|
|
||||||
|
|
||||||
def sanitize_open(filename, open_mode):
|
def sanitize_open(filename, open_mode):
|
||||||
@@ -329,7 +329,7 @@ def sanitize_open(filename, open_mode):
|
|||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
import msvcrt
|
import msvcrt
|
||||||
msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
|
msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
|
||||||
return (sys.stdout, filename)
|
return (sys.stdout.buffer if hasattr(sys.stdout, 'buffer') else sys.stdout, filename)
|
||||||
stream = open(encodeFilename(filename), open_mode)
|
stream = open(encodeFilename(filename), open_mode)
|
||||||
return (stream, filename)
|
return (stream, filename)
|
||||||
except (IOError, OSError) as err:
|
except (IOError, OSError) as err:
|
||||||
@@ -420,6 +420,14 @@ def encodeFilename(s):
|
|||||||
encoding = 'utf-8'
|
encoding = 'utf-8'
|
||||||
return s.encode(encoding, 'ignore')
|
return s.encode(encoding, 'ignore')
|
||||||
|
|
||||||
|
def decodeOption(optval):
|
||||||
|
if optval is None:
|
||||||
|
return optval
|
||||||
|
if isinstance(optval, bytes):
|
||||||
|
optval = optval.decode(preferredencoding())
|
||||||
|
|
||||||
|
assert isinstance(optval, compat_str)
|
||||||
|
return optval
|
||||||
|
|
||||||
class ExtractorError(Exception):
|
class ExtractorError(Exception):
|
||||||
"""Error during info extraction."""
|
"""Error during info extraction."""
|
||||||
@@ -427,6 +435,7 @@ class ExtractorError(Exception):
|
|||||||
""" tb, if given, is the original traceback (so that it can be printed out). """
|
""" tb, if given, is the original traceback (so that it can be printed out). """
|
||||||
super(ExtractorError, self).__init__(msg)
|
super(ExtractorError, self).__init__(msg)
|
||||||
self.traceback = tb
|
self.traceback = tb
|
||||||
|
self.exc_info = sys.exc_info() # preserve original exception
|
||||||
|
|
||||||
def format_traceback(self):
|
def format_traceback(self):
|
||||||
if self.traceback is None:
|
if self.traceback is None:
|
||||||
@@ -441,7 +450,10 @@ class DownloadError(Exception):
|
|||||||
configured to continue on errors. They will contain the appropriate
|
configured to continue on errors. They will contain the appropriate
|
||||||
error message.
|
error message.
|
||||||
"""
|
"""
|
||||||
pass
|
def __init__(self, msg, exc_info=None):
|
||||||
|
""" exc_info, if given, is the original exception that caused the trouble (as returned by sys.exc_info()). """
|
||||||
|
super(DownloadError, self).__init__(msg)
|
||||||
|
self.exc_info = exc_info
|
||||||
|
|
||||||
|
|
||||||
class SameFileError(Exception):
|
class SameFileError(Exception):
|
||||||
|
@@ -1,2 +1,2 @@
|
|||||||
|
|
||||||
__version__ = '2013.02.19'
|
__version__ = '2013.04.03'
|
||||||
|
Reference in New Issue
Block a user