Compare commits

...

97 Commits

Author SHA1 Message Date
Philipp Hagemeister
c2b293ba30 release 2013.04.03 2013-04-03 19:43:53 +02:00
Philipp Hagemeister
37cd9f522f Restore youtube-dl (update) binary (#770) 2013-04-01 23:43:20 +02:00
Filippo Valsorda
f33154cd39 Merge pull request #764 from jaimeMF/subtitles_not_found
Fix crash when subtitles are not found
2013-03-31 19:02:18 -07:00
Jaime Marquínez Ferrándiz
bafeed9f5d Don't crash in FileDownloader if subtitles couldn't be found and errors are ignored 2013-03-31 12:21:35 +02:00
Jaime Marquínez Ferrándiz
ef767f9fd5 Fix crash when subtitles are not found and the option --all-subs is given 2013-03-31 12:19:13 +02:00
Jaime Marquínez Ferrándiz
bc97f6d60c Use report_error in subtitles error handling 2013-03-31 12:10:12 +02:00
Filippo Valsorda
90a99c1b5e retry on UnavailableVideoError 2013-03-31 03:29:34 +02:00
Filippo Valsorda
f375d4b7de import all IEs when testing to resemble more closely the real env 2013-03-31 03:12:28 +02:00
Filippo Valsorda
fa41fbd318 don't catch YT user URLs in YoutubePlaylistIE (fix #754, fix #763) 2013-03-31 03:02:49 +02:00
Jaime Marquínez Ferrándiz
6a205c8876 More fixes on subtitles errors handling 2013-03-30 14:17:12 +01:00
Jaime Marquínez Ferrándiz
0fb3756409 Fix crash when subtitles are not found 2013-03-30 14:11:33 +01:00
Philipp Hagemeister
fbbdf475b1 Different feed file name 2013-03-29 21:44:11 +01:00
Philipp Hagemeister
c238be3e3a Correct feed title 2013-03-29 21:41:20 +01:00
Philipp Hagemeister
1bf2801e6a release 2013.03.29 2013-03-29 21:22:57 +01:00
Philipp Hagemeister
c9c8402093 Merge pull request #758 from jaimeMF/atom-feed
Add an Atom feed generator in devscripts
2013-03-29 12:50:20 -07:00
Jaime Marquínez Ferrándiz
6060788083 Write a new feed each time, reading from versions.json 2013-03-29 19:42:33 +01:00
Filippo Valsorda
e3700fc9e4 Merge pull request #736 from rg3/retry
Exception stacking and test retry
2013-03-29 09:01:27 -07:00
Filippo Valsorda
b693216d8d Merge pull request #752 from dodo/master
SoundcloudSetIE
2013-03-29 08:40:22 -07:00
Filippo Valsorda
46b9d8295d Merge pull request #730 by @JohnyMoSwag
Support for Worldstarhiphop.com
2013-03-29 16:14:49 +01:00
Filippo Valsorda
7decf8951c fix FunnyOrDieIE, MyVideoIE, TEDIE 2013-03-29 15:59:13 +01:00
Filippo Valsorda
1f46c15262 fix SpiegelIE 2013-03-29 15:31:38 +01:00
Filippo Valsorda
0cd358676c Rebased, fixed and extended LiveLeak.com support
close #757 - close #761
2013-03-29 15:13:24 +01:00
kkalpakloglou
43113d92cc Update InfoExtractors.py 2013-03-29 14:23:09 +01:00
Johny Mo Swag
44e939514e Added test for WorldStarHipHop 2013-03-28 20:05:28 -07:00
Philipp Hagemeister
95506f1235 Merge remote-tracking branch 'jaimeMF/color_error_messages' 2013-03-29 00:25:48 +01:00
Philipp Hagemeister
a91556fd74 Add a note on MaxDownloadsReached (#732, thanks to CBGoodBuddy) 2013-03-29 00:20:13 +01:00
Philipp Hagemeister
1447f728b5 Merge branch 'master' of github.com:rg3/youtube-dl 2013-03-29 00:06:48 +01:00
Filippo Valsorda
898280a056 use sys.stdout.buffer only on Python3 2013-03-28 13:13:03 +01:00
Filippo Valsorda
59b4a2f0e4 Merge pull request #762 from jynnantonix/master
Use sys.stdout.buffer when writing to standard out
2013-03-28 05:11:51 -07:00
Chirantan Ekbote
1ee9778405 Use sys.stdout.buffer instead of sys.stdout
sys.stdout defaults to text mode, we need to use the underlying buffer
instead when writing binary data.

Signed-off-by: Chirantan Ekbote <chirantan.ekbote@gmail.com>
2013-03-27 15:57:11 -04:00
Jaime Marquínez Ferrándiz
db74c11d2b Add an Atom feed generator in devscripts 2013-03-26 18:13:52 +01:00
dodo
5011cded16 SoundcloudSetIE
info extractor for soundcloud sets
2013-03-24 02:24:07 +01:00
Filippo Valsorda
f10b2a9c14 fix KeekIE 2013-03-20 12:13:52 +01:00
Filippo Valsorda
5cb3c0b319 Merge pull request #699 by @iemejia
Removed innecesary function to convert subtitles, improved use of the youtube api
2013-03-20 11:35:55 +01:00
Filippo Valsorda
b9fc428494 add '--write-srt' and '--srt-lang' aliases for backwards compatibility 2013-03-20 11:29:07 +01:00
Ismael Mejia
c0ba104674 Fixed typo in error message when no subtitles were available. 2013-03-20 08:41:54 +01:00
Ismael Mejia
2a4093eaf3 Added new option '--list-subs' to show the available subtitle languages 2013-03-20 08:41:54 +01:00
Ismael Mejia
9e62bc4439 Added new option '--sub-format' to choose the format of the subtitles to downloade (defaut=srt) 2013-03-20 08:41:54 +01:00
Ismael Mejia
553d097442 Refactor subtitle options from srt to the more generic 'sub'.
In order to be more consistent with different subtitle formats.
From:
* --write-srt to --write-sub
* --only-srt to --only-sub
* --all-srt to --all-subs
* --srt-lang to --sub-lang'

Refactored also all the mentions of srt for sub in all the source code.
2013-03-20 08:41:53 +01:00
Ismael Mejia
ae608b8076 Added new option '--all-srt' to download all the subtitles of a video.
Only works in youtube for the moment.
2013-03-20 08:41:53 +01:00
Philipp Hagemeister
c397187061 Spiegel: Support hash at end of URL 2013-03-16 23:52:17 +01:00
Philipp Hagemeister
e32b06e977 Spiegel IE 2013-03-12 01:08:54 +01:00
Philipp Hagemeister
8c42c506cd Add configuration to -v output 2013-03-12 00:10:05 +01:00
Filippo Valsorda
8cc83b8dbe Bubble up all the stack of exceptions and retry download tests on timeout errors 2013-03-09 10:05:43 +01:00
Johny Mo Swag
51af426d89 forgot to fix this. 2013-03-08 22:52:17 -08:00
Johny Mo Swag
08ec0af7c6 catch fatal error 2013-03-08 22:48:05 -08:00
Johny Mo Swag
3b221c5406 removed str used for other project. 2013-03-08 22:39:45 -08:00
Philipp Hagemeister
3d3423574d Fix Unicode handling GenericIE (Fixes #734) 2013-03-08 20:47:06 +01:00
Philipp Hagemeister
e5edd51de4 Clear up error messages (#734) 2013-03-08 20:12:05 +01:00
Johny Mo Swag
64c78d50cc working - worldstarhiphop IE
Support for WorldStarHipHop
2013-03-07 16:27:21 -08:00
Johny Mo Swag
b3bcca0844 clean up 2013-03-07 15:39:17 -08:00
Johny Mo Swag
61e40c88a9 fixed typo 2013-03-06 21:14:46 -08:00
Johny Mo Swag
40634747f7 Support for WorldStarHipHop.com 2013-03-06 21:09:55 -08:00
Philipp Hagemeister
c2e21f2f0d Merge pull request #728 from timdoug/fix-escapist-extension
Escapist videos are acutally .mp4, not .flv
2013-03-06 10:26:18 -08:00
Tim Douglas
47dcd621c0 Escapist videos are acutally .mp4, not .flv 2013-03-06 12:46:45 -05:00
Jaime Marquínez Ferrándiz
c9fa1cbab6 More trouble calls changed in InfoExtractors.py
The calls with the message starting with 'WARNING' have been changed to report_warning instead of report_error
2013-03-05 21:13:17 +01:00
Jaime Marquínez Ferrándiz
e5f30ade10 Use report_error in InfoExtractors.py
Some calls haven't been changed
2013-03-04 15:56:14 +01:00
Jaime Marquínez Ferrándiz
6622d22c79 Use report_error in FileDownloader.py 2013-03-04 11:47:58 +01:00
Jaime Marquínez Ferrándiz
4e1582f372 Use red color when printing error messages 2013-03-04 11:27:25 +01:00
Philipp Hagemeister
967897fd22 Fix Python 3 errors with rmtp downloads 2013-03-03 22:38:38 +01:00
Philipp Hagemeister
f918ec7ea2 Clarify rate limit documentation (Closes #723) 2013-03-03 22:35:26 +01:00
Philipp Hagemeister
a2ae43a55f Remove changed playlist test (#661) 2013-03-03 22:19:19 +01:00
Philipp Hagemeister
7ae153ee9c Remove tweetreel - it has shut down 2013-03-03 22:15:06 +01:00
Philipp Hagemeister
f7b567ff84 Use proper urlparse functions and simplify a bit 2013-03-03 22:09:44 +01:00
Philipp Hagemeister
f2e237adc8 Merge remote-tracking branch 'jcarlosgarciasegovia/master' 2013-03-03 22:04:06 +01:00
Jaime Marquínez Ferrándiz
2e5457be1d Use report_warning in InfoExtractors 2013-03-02 11:24:07 +01:00
Juan Carlos Garcia Segovia
7f9d41a55e Allow downloading http://blip.tv/play/ embeded URLs 2013-03-01 10:22:16 +00:00
Jaime Marquínez Ferrándiz
8207626bbe Use color when printing warning messages 2013-02-28 22:07:29 +01:00
Philipp Hagemeister
691db5ba02 Don't be too clever (Fixes Python 3) 2013-02-26 22:03:43 +01:00
Philipp Hagemeister
acb8752f80 fix tests in Python3, and make them parallelizable 2013-02-26 22:03:33 +01:00
Philipp Hagemeister
679790eee1 Do not user upper-case for non-constants 2013-02-26 20:03:19 +01:00
Philipp Hagemeister
6bf48bd866 Merge remote-tracking branch 'origin/API_YT_playlists' 2013-02-26 19:58:04 +01:00
Philipp Hagemeister
790d4fcbe1 Merge pull request #715 from joksnet/no_video_results
[YT Search] No results if items is not in response
2013-02-26 10:43:35 -08:00
Filippo Valsorda
89de9eb125 Modified Youtube video/playlist matching; fixes #668; fixes #585 2013-02-26 19:06:41 +01:00
Filippo Valsorda
6324fd1d74 Switch YTPlaylistIE to API (relevant: #586); fixes #651; fixes #673; fixes #661 2013-02-26 19:06:28 +01:00
Juan M
9e07cf2955 [YT Search] No results if items is not in response
When a query results of 0 items, the key items is not present in the
api_response dictionary, raising a KeyError.

Intead, look for the key and call trouble if it's not present.
2013-02-26 18:06:43 +01:00
Philipp Hagemeister
f03b88b3fb Merge remote-tracking branch 'joksnet/not_keep_video_message' 2013-02-25 00:35:12 +01:00
Philipp Hagemeister
97d0365f49 release 2013.02.25 2013-02-25 00:28:19 +01:00
Philipp Hagemeister
12887875a2 Fix typo 2013-02-25 00:22:55 +01:00
Philipp Hagemeister
450e709972 Formalize URL creation (prepare for some cleanup in blip.tv:users) 2013-02-24 23:23:50 +01:00
Philipp Hagemeister
9befce2b8c Merge remote-tracking branch 'joksnet/ytsearch_decode_request' 2013-02-24 23:14:34 +01:00
Philipp Hagemeister
cb99797798 Test TED thumbnail 2013-02-24 01:01:20 +01:00
Philipp Hagemeister
f82b28146a Merge remote-tracking branch 'jaimeMF/TED' 2013-02-24 00:59:22 +01:00
Philipp Hagemeister
4dc72b830c Merge remote-tracking branch 'jaimeMF/Steam' 2013-02-24 00:59:03 +01:00
Philipp Hagemeister
ea05129ebd release 2013.02.22 2013-02-24 00:47:08 +01:00
Juan M
35d217133f Message for delete video it's not an error.
When using youtube-dl from another python script with the quiet option
on, and a post procesor for extract the audio. The message of deleting
video shows in the first script logs (as it goes to stderr).

There is no way to keep this quiet as it's treated as an error, even if,
for me, it's not.
2013-02-23 22:52:52 +01:00
Juan M
d1b7a24354 Decode the data requested to the api in utf-8. 2013-02-23 22:47:22 +01:00
Jaime Marquínez Ferrándiz
c85538dba1 TED: get thumbnails 2013-02-23 17:27:49 +01:00
Jaime Marquínez Ferrándiz
60bd48b175 Steam: get thumbnails 2013-02-23 16:48:15 +01:00
Philipp Hagemeister
4be0aa3539 release 2012.02.22 2013-02-22 16:41:36 +01:00
Philipp Hagemeister
f636c34481 Stop early in nosetests (in release script) 2013-02-22 16:40:19 +01:00
Philipp Hagemeister
3bf79c752e Print *all* release notes 2013-02-22 00:36:23 +01:00
Ismael Mejia
cdb130b09a Added new option '--only-srt' to download only the subtitles of a video
Improved option '--srt-lang'
 - it shows the argument in case of missing subtitles
 - added language suffix for non-english languages (e.g. video.it.srt)
2013-02-21 22:12:36 +01:00
Ismael Mejia
2e5d60b7db Removed conversion from youtube closed caption format to srt since youtube api supports the 'srt' format 2013-02-21 20:51:35 +01:00
Philipp Hagemeister
8271226a55 Fix --match-title and --reject-title decoding (Closes #690) 2013-02-21 17:09:39 +01:00
Philipp Hagemeister
1013186a17 Also check for JSLoader of JWSPlayer (thanks to @maximeg, Closes #685) 2013-02-21 16:56:48 +01:00
Philipp Hagemeister
7c038b3c32 Import HTTPErrorProcessor from the correct module (Closes #696) 2013-02-21 16:49:05 +01:00
17 changed files with 1014 additions and 473 deletions

View File

@@ -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

View 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)

View File

@@ -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"

View File

@@ -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
} }

View File

@@ -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')

View File

@@ -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):

View File

@@ -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()

View File

@@ -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()

View File

@@ -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! "
}
} }
] ]

Binary file not shown.

View File

@@ -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

View File

@@ -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,

View File

@@ -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)

View File

@@ -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):

View File

@@ -1,2 +1,2 @@
__version__ = '2013.02.19' __version__ = '2013.04.03'