Compare commits
381 Commits
2013.02.02
...
2013.06.21
Author | SHA1 | Date | |
---|---|---|---|
759d525301 | |||
fcfa188548 | |||
f4c8bbcfc2 | |||
038a3a1a61 | |||
587c68b2cd | |||
377fdf5dde | |||
5c67601931 | |||
68f54207a3 | |||
bb47437686 | |||
213b715893 | |||
449d5c910c | |||
0251f9c9c0 | |||
8bc7c3d858 | |||
af44c94862 | |||
36ed7177f0 | |||
32aa88bcae | |||
31513ea6b9 | |||
88cebbd7b8 | |||
fb8f7280bc | |||
f380401bbd | |||
9abc6c8b31 | |||
8cd252f115 | |||
53f72b11e5 | |||
ee55fcbe12 | |||
78d3442b12 | |||
979a9dd4c4 | |||
d5979c5d55 | |||
8027175600 | |||
3054ff0cbe | |||
cd453d38bb | |||
f5a290eed9 | |||
ecb3e676a5 | |||
8b59a98610 | |||
8409501206 | |||
be95cac157 | |||
476203d025 | |||
468e2e926b | |||
ac3e9394e7 | |||
868d62a509 | |||
157b864a01 | |||
951b9dfd94 | |||
1142d31164 | |||
9131bde941 | |||
1132c10dc2 | |||
c978a96c02 | |||
71e458d437 | |||
57bde0d9c7 | |||
50b4d25980 | |||
eda60e8251 | |||
c794cbbb19 | |||
4a76d1dbe5 | |||
418f734a58 | |||
dc1c355b72 | |||
1b2b22ed9f | |||
f2cd958c0a | |||
57adeaea87 | |||
8f3f1aef05 | |||
51d2453c7a | |||
45014296be | |||
afef36c950 | |||
b31756c18e | |||
f008688520 | |||
5b68ea215b | |||
b1d568f0bc | |||
17bd1b2f41 | |||
5b0d3cc0cd | |||
d4f76f1674 | |||
340fa21198 | |||
de5d66d431 | |||
7bdb17d4d5 | |||
419c64b107 | |||
99a5ae3f8e | |||
c7563c528b | |||
e30e9318da | |||
5c51028d38 | |||
c1d58e1c67 | |||
02030ff7fe | |||
f45c185fa9 | |||
1bd96c3a60 | |||
929f85d851 | |||
98d4a4e6bc | |||
fb2f83360c | |||
3c5e7729e1 | |||
5a853e1423 | |||
2f58b12dad | |||
59f4fd4dc6 | |||
5738240ee8 | |||
86fd453ea8 | |||
c83411b9ee | |||
057c9938a1 | |||
9259966132 | |||
b08980412e | |||
532a1e0429 | |||
2a36c352a0 | |||
1a2adf3f49 | |||
43b62accbb | |||
be74864ace | |||
0ae456f08a | |||
0f75d25991 | |||
67129e4a15 | |||
dfb9323cf9 | |||
7f5bd09baf | |||
02d5eb935f | |||
94ca71b7cc | |||
b338f1b154 | |||
486f0c9476 | |||
d96680f58d | |||
f8602d3242 | |||
0c021ad171 | |||
086d7b4500 | |||
891629c84a | |||
ea6d901e51 | |||
4539dd30e6 | |||
c43e57242e | |||
db8fd71ca9 | |||
f4f316881d | |||
0e16f09474 | |||
09dd418f53 | |||
decd1d1737 | |||
180e689f7e | |||
7da5556ac2 | |||
f23a03a89b | |||
84e4682f0e | |||
1f99511210 | |||
0d94f2474c | |||
480b6c1e8b | |||
95464f14d1 | |||
c34407d16c | |||
5e34d2ebbf | |||
815dd2ffa8 | |||
ecd5fb49c5 | |||
b86174e7a3 | |||
2e2038dc35 | |||
46bfb42258 | |||
feecf22511 | |||
4c4f15eb78 | |||
104ccdb8b4 | |||
6ccff79594 | |||
aed523ecc1 | |||
d496a75d0a | |||
5c01dd1e73 | |||
11d9224e3b | |||
34c29ba1d7 | |||
6cd657f9f2 | |||
4ae9e55822 | |||
8749b71273 | |||
dbc50fdf82 | |||
b1d2ef9255 | |||
5fb16555af | |||
ba7c775a04 | |||
fe348844d9 | |||
767e00277f | |||
6ce533a220 | |||
08b2ac745a | |||
46a127eecb | |||
fc63faf070 | |||
9665577802 | |||
434aca5b14 | |||
e31852aba9 | |||
37254abc36 | |||
a11ea50319 | |||
81df121dd3 | |||
50f6412eb8 | |||
bf50b0383e | |||
bd55852517 | |||
4c9f7a9988 | |||
aba8df23ed | |||
3820df0106 | |||
e74c504f91 | |||
fa70605db2 | |||
0d173446ff | |||
320e26a0af | |||
a3d689cfb3 | |||
59cc5d9380 | |||
28535652ab | |||
7b670a4483 | |||
69fc019f26 | |||
613bf66939 | |||
9edb0916f4 | |||
f4b659f782 | |||
c70446c7df | |||
c76cb6d548 | |||
71f37e90ef | |||
75b5c590a8 | |||
4469666780 | |||
c15e024141 | |||
8cb94542f4 | |||
c681a03918 | |||
30f2999962 | |||
74e3452b9e | |||
9e1cf0c200 | |||
e11eb11906 | |||
c04bca6f60 | |||
b0936ef423 | |||
41a6eb949a | |||
f17ce13a92 | |||
8c416ad29a | |||
c72938240e | |||
e905b6f80e | |||
6de8f1afb7 | |||
9341212642 | |||
f7a9721e16 | |||
089e843b0f | |||
c8056d866a | |||
49da66e459 | |||
fb6c319904 | |||
5a8d13199c | |||
dce9027045 | |||
feba604e92 | |||
d22f65413a | |||
0599ef8c08 | |||
bfdf469295 | |||
32c96387c1 | |||
c8c5443bb5 | |||
a60b854d90 | |||
b8ad4f02a2 | |||
d281274bf2 | |||
b625bc2c31 | |||
f4381ab88a | |||
744435f2a4 | |||
855703e55e | |||
927c8c4924 | |||
0ba994e9e3 | |||
af9ad45cd4 | |||
e0fee250c3 | |||
72ca05016d | |||
844d1f9fa1 | |||
213c31ae16 | |||
04f3d551a0 | |||
e8600d69fd | |||
b03d65c237 | |||
8743974189 | |||
dc36bc9434 | |||
bce878a7c1 | |||
532d797824 | |||
146c12a2da | |||
d39919c03e | |||
df2dedeefb | |||
adb029ed81 | |||
43ff1a347d | |||
14294236bf | |||
c2b293ba30 | |||
37cd9f522f | |||
f33154cd39 | |||
bafeed9f5d | |||
ef767f9fd5 | |||
bc97f6d60c | |||
90a99c1b5e | |||
f375d4b7de | |||
fa41fbd318 | |||
6a205c8876 | |||
0fb3756409 | |||
fbbdf475b1 | |||
c238be3e3a | |||
1bf2801e6a | |||
c9c8402093 | |||
6060788083 | |||
e3700fc9e4 | |||
b693216d8d | |||
46b9d8295d | |||
7decf8951c | |||
1f46c15262 | |||
0cd358676c | |||
43113d92cc | |||
7eab8dc750 | |||
44e939514e | |||
95506f1235 | |||
a91556fd74 | |||
1447f728b5 | |||
d2c690828a | |||
cfa90f4adc | |||
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 | |||
a0d6fe7b92 | |||
c9fa1cbab6 | |||
8a38a194fb | |||
6ac7f082c4 | |||
f6e6da9525 | |||
597cc8a455 | |||
3370abd509 | |||
631f73978c | |||
e5f30ade10 | |||
6622d22c79 | |||
4e1582f372 | |||
967897fd22 | |||
f918ec7ea2 | |||
a2ae43a55f | |||
7ae153ee9c | |||
f7b567ff84 | |||
f2e237adc8 | |||
2e5457be1d | |||
7f9d41a55e | |||
8207626bbe | |||
df8db1aa21 | |||
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 | |||
c8cd8e5f55 | |||
471cf47796 | |||
d8f64574a4 | |||
e711babbd1 | |||
a72b0f2b6f | |||
434eb6f26b | |||
197080b10b | |||
7796e8c2cb | |||
6d4363368a | |||
414638cd50 | |||
2a9983b78f | |||
b17c974a88 | |||
5717d91ab7 | |||
79eb0287ab | |||
58994225bc | |||
59d4c2fe1b | |||
3a468f2d8b | |||
1ad5d872b9 | |||
355fc8e944 | |||
380a29dbf7 | |||
1528d6642d | |||
7311fef854 | |||
906417c7c5 | |||
6aabe82035 | |||
f0877a445e | |||
da06e2daf8 | |||
d3f5f9f6b9 | |||
bfc6ea7935 | |||
8edc2cf8ca | |||
fb778e66df | |||
3a9918d37f | |||
ccb0cae134 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -17,3 +17,4 @@ youtube-dl.tar.gz
|
||||
.coverage
|
||||
cover/
|
||||
updates_key.pem
|
||||
*.egg-info
|
@ -8,6 +8,7 @@ notifications:
|
||||
email:
|
||||
- filippo.valsorda@gmail.com
|
||||
- phihag@phihag.de
|
||||
- jaime.marquinez.ferrandiz+travis@gmail.com
|
||||
# irc:
|
||||
# channels:
|
||||
# - "irc.freenode.org#youtube-dl"
|
||||
|
@ -1,3 +1,5 @@
|
||||
include README.md
|
||||
include test/*.py
|
||||
include test/*.json
|
||||
include test/*.json
|
||||
include youtube-dl.bash-completion
|
||||
include youtube-dl.1
|
||||
|
23
Makefile
23
Makefile
@ -1,14 +1,27 @@
|
||||
all: youtube-dl README.md README.txt youtube-dl.1 youtube-dl.bash-completion
|
||||
|
||||
clean:
|
||||
rm -rf youtube-dl youtube-dl.exe youtube-dl.1 youtube-dl.bash-completion README.txt MANIFEST build/ dist/ .coverage cover/ youtube-dl.tar.gz
|
||||
rm -rf youtube-dl.1 youtube-dl.bash-completion README.txt MANIFEST build/ dist/ .coverage cover/ youtube-dl.tar.gz
|
||||
|
||||
cleanall: clean
|
||||
rm -f youtube-dl youtube-dl.exe
|
||||
|
||||
PREFIX=/usr/local
|
||||
BINDIR=$(PREFIX)/bin
|
||||
MANDIR=$(PREFIX)/man
|
||||
SYSCONFDIR=/etc
|
||||
PYTHON=/usr/bin/env python
|
||||
|
||||
# set SYSCONFDIR to /etc if PREFIX=/usr or PREFIX=/usr/local
|
||||
ifeq ($(PREFIX),/usr)
|
||||
SYSCONFDIR=/etc
|
||||
else
|
||||
ifeq ($(PREFIX),/usr/local)
|
||||
SYSCONFDIR=/etc
|
||||
else
|
||||
SYSCONFDIR=$(PREFIX)/etc
|
||||
endif
|
||||
endif
|
||||
|
||||
install: youtube-dl youtube-dl.1 youtube-dl.bash-completion
|
||||
install -d $(DESTDIR)$(BINDIR)
|
||||
install -m 755 youtube-dl $(DESTDIR)$(BINDIR)
|
||||
@ -23,7 +36,9 @@ test:
|
||||
|
||||
tar: youtube-dl.tar.gz
|
||||
|
||||
.PHONY: all clean install test tar
|
||||
.PHONY: all clean install test tar bash-completion pypi-files
|
||||
|
||||
pypi-files: youtube-dl.bash-completion README.txt youtube-dl.1
|
||||
|
||||
youtube-dl: youtube_dl/*.py
|
||||
zip --quiet youtube-dl youtube_dl/*.py
|
||||
@ -45,6 +60,8 @@ youtube-dl.1: README.md
|
||||
youtube-dl.bash-completion: youtube_dl/*.py devscripts/bash-completion.in
|
||||
python devscripts/bash-completion.py
|
||||
|
||||
bash-completion: youtube-dl.bash-completion
|
||||
|
||||
youtube-dl.tar.gz: youtube-dl README.md README.txt youtube-dl.1 youtube-dl.bash-completion
|
||||
@tar -czf youtube-dl.tar.gz --transform "s|^|youtube-dl/|" --owner 0 --group 0 \
|
||||
--exclude '*.DS_Store' \
|
||||
|
228
README.md
228
README.md
@ -14,112 +14,137 @@ your Unix box, on Windows or on Mac OS X. It is released to the public domain,
|
||||
which means you can modify it, redistribute it or use it however you like.
|
||||
|
||||
# OPTIONS
|
||||
-h, --help print this help text and exit
|
||||
--version print program version and exit
|
||||
-U, --update update this program to latest version
|
||||
-i, --ignore-errors continue on download errors
|
||||
-r, --rate-limit LIMIT download rate limit (e.g. 50k or 44.6m)
|
||||
-R, --retries RETRIES number of retries (default is 10)
|
||||
--buffer-size SIZE size of download buffer (e.g. 1024 or 16k) (default
|
||||
is 1024)
|
||||
--no-resize-buffer do not automatically adjust the buffer size. By
|
||||
default, the buffer size is automatically resized
|
||||
from an initial value of SIZE.
|
||||
--dump-user-agent display the current browser identification
|
||||
--user-agent UA specify a custom user agent
|
||||
--list-extractors List all supported extractors and the URLs they
|
||||
would handle
|
||||
-h, --help print this help text and exit
|
||||
--version print program version and exit
|
||||
-U, --update update this program to latest version
|
||||
-i, --ignore-errors continue on download errors
|
||||
-r, --rate-limit LIMIT maximum download rate (e.g. 50k or 44.6m)
|
||||
-R, --retries RETRIES number of retries (default is 10)
|
||||
--buffer-size SIZE size of download buffer (e.g. 1024 or 16k)
|
||||
(default is 1024)
|
||||
--no-resize-buffer do not automatically adjust the buffer size. By
|
||||
default, the buffer size is automatically resized
|
||||
from an initial value of SIZE.
|
||||
--dump-user-agent display the current browser identification
|
||||
--user-agent UA specify a custom user agent
|
||||
--referer REF specify a custom referer, use if the video access
|
||||
is restricted to one domain
|
||||
--list-extractors List all supported extractors and the URLs they
|
||||
would handle
|
||||
--proxy URL Use the specified HTTP/HTTPS proxy
|
||||
--no-check-certificate Suppress HTTPS certificate validation.
|
||||
|
||||
## Video Selection:
|
||||
--playlist-start NUMBER playlist video to start at (default is 1)
|
||||
--playlist-end NUMBER playlist video to end at (default is last)
|
||||
--match-title REGEX download only matching titles (regex or caseless
|
||||
sub-string)
|
||||
--reject-title REGEX skip download for matching titles (regex or
|
||||
caseless sub-string)
|
||||
--max-downloads NUMBER Abort after downloading NUMBER files
|
||||
--min-filesize SIZE Do not download any videos smaller than SIZE (e.g.
|
||||
50k or 44.6m)
|
||||
--max-filesize SIZE Do not download any videos larger than SIZE (e.g.
|
||||
50k or 44.6m)
|
||||
--playlist-start NUMBER playlist video to start at (default is 1)
|
||||
--playlist-end NUMBER playlist video to end at (default is last)
|
||||
--match-title REGEX download only matching titles (regex or caseless
|
||||
sub-string)
|
||||
--reject-title REGEX skip download for matching titles (regex or
|
||||
caseless sub-string)
|
||||
--max-downloads NUMBER Abort after downloading NUMBER files
|
||||
--min-filesize SIZE Do not download any videos smaller than SIZE
|
||||
(e.g. 50k or 44.6m)
|
||||
--max-filesize SIZE Do not download any videos larger than SIZE (e.g.
|
||||
50k or 44.6m)
|
||||
--date DATE download only videos uploaded in this date
|
||||
--datebefore DATE download only videos uploaded before this date
|
||||
--dateafter DATE download only videos uploaded after this date
|
||||
|
||||
## Filesystem Options:
|
||||
-t, --title use title in file name
|
||||
--id use video ID in file name
|
||||
-l, --literal [deprecated] alias of --title
|
||||
-A, --auto-number number downloaded files starting from 00000
|
||||
-o, --output TEMPLATE output filename template. Use %(title)s to get the
|
||||
title, %(uploader)s for the uploader name,
|
||||
%(uploader_id)s for the uploader nickname if
|
||||
different, %(autonumber)s to get an automatically
|
||||
incremented number, %(ext)s for the filename
|
||||
extension, %(upload_date)s for the upload date
|
||||
(YYYYMMDD), %(extractor)s for the provider
|
||||
(youtube, metacafe, etc), %(id)s for the video id
|
||||
and %% for a literal percent. Use - to output to
|
||||
stdout. Can also be used to download to a different
|
||||
directory, for example with -o '/my/downloads/%(upl
|
||||
oader)s/%(title)s-%(id)s.%(ext)s' .
|
||||
--restrict-filenames Restrict filenames to only ASCII characters, and
|
||||
avoid "&" and spaces in filenames
|
||||
-a, --batch-file FILE file containing URLs to download ('-' for stdin)
|
||||
-w, --no-overwrites do not overwrite files
|
||||
-c, --continue resume partially downloaded files
|
||||
--no-continue do not resume partially downloaded files (restart
|
||||
from beginning)
|
||||
--cookies FILE file to read cookies from and dump cookie jar in
|
||||
--no-part do not use .part files
|
||||
--no-mtime do not use the Last-modified header to set the file
|
||||
modification time
|
||||
--write-description write video description to a .description file
|
||||
--write-info-json write video metadata to a .info.json file
|
||||
-t, --title use title in file name (default)
|
||||
--id use only video ID in file name
|
||||
-l, --literal [deprecated] alias of --title
|
||||
-A, --auto-number number downloaded files starting from 00000
|
||||
-o, --output TEMPLATE output filename template. Use %(title)s to get
|
||||
the title, %(uploader)s for the uploader name,
|
||||
%(uploader_id)s for the uploader nickname if
|
||||
different, %(autonumber)s to get an automatically
|
||||
incremented number, %(ext)s for the filename
|
||||
extension, %(upload_date)s for the upload date
|
||||
(YYYYMMDD), %(extractor)s for the provider
|
||||
(youtube, metacafe, etc), %(id)s for the video id
|
||||
, %(playlist)s for the playlist the video is in,
|
||||
%(playlist_index)s for the position in the
|
||||
playlist and %% for a literal percent. Use - to
|
||||
output to stdout. Can also be used to download to
|
||||
a different directory, for example with -o '/my/d
|
||||
ownloads/%(uploader)s/%(title)s-%(id)s.%(ext)s' .
|
||||
--autonumber-size NUMBER Specifies the number of digits in %(autonumber)s
|
||||
when it is present in output filename template or
|
||||
--autonumber option is given
|
||||
--restrict-filenames Restrict filenames to only ASCII characters, and
|
||||
avoid "&" and spaces in filenames
|
||||
-a, --batch-file FILE file containing URLs to download ('-' for stdin)
|
||||
-w, --no-overwrites do not overwrite files
|
||||
-c, --continue resume partially downloaded files
|
||||
--no-continue do not resume partially downloaded files (restart
|
||||
from beginning)
|
||||
--cookies FILE file to read cookies from and dump cookie jar in
|
||||
--no-part do not use .part files
|
||||
--no-mtime do not use the Last-modified header to set the
|
||||
file modification time
|
||||
--write-description write video description to a .description file
|
||||
--write-info-json write video metadata to a .info.json file
|
||||
--write-thumbnail write thumbnail image to disk
|
||||
|
||||
## Verbosity / Simulation Options:
|
||||
-q, --quiet activates quiet mode
|
||||
-s, --simulate do not download the video and do not write anything
|
||||
to disk
|
||||
--skip-download do not download the video
|
||||
-g, --get-url simulate, quiet but print URL
|
||||
-e, --get-title simulate, quiet but print title
|
||||
--get-thumbnail simulate, quiet but print thumbnail URL
|
||||
--get-description simulate, quiet but print video description
|
||||
--get-filename simulate, quiet but print output filename
|
||||
--get-format simulate, quiet but print output format
|
||||
--no-progress do not print progress bar
|
||||
--console-title display progress in console titlebar
|
||||
-v, --verbose print various debugging information
|
||||
-q, --quiet activates quiet mode
|
||||
-s, --simulate do not download the video and do not write
|
||||
anything to disk
|
||||
--skip-download do not download the video
|
||||
-g, --get-url simulate, quiet but print URL
|
||||
-e, --get-title simulate, quiet but print title
|
||||
--get-id simulate, quiet but print id
|
||||
--get-thumbnail simulate, quiet but print thumbnail URL
|
||||
--get-description simulate, quiet but print video description
|
||||
--get-filename simulate, quiet but print output filename
|
||||
--get-format simulate, quiet but print output format
|
||||
--newline output progress bar as new lines
|
||||
--no-progress do not print progress bar
|
||||
--console-title display progress in console titlebar
|
||||
-v, --verbose print various debugging information
|
||||
--dump-intermediate-pages print downloaded pages to debug problems(very
|
||||
verbose)
|
||||
|
||||
## Video Format Options:
|
||||
-f, --format FORMAT video format code
|
||||
--all-formats download all available video formats
|
||||
--prefer-free-formats prefer free video formats unless a specific one is
|
||||
requested
|
||||
--max-quality FORMAT highest quality format to download
|
||||
-F, --list-formats list all available formats (currently youtube only)
|
||||
--write-srt write video closed captions to a .srt file
|
||||
(currently youtube only)
|
||||
--srt-lang LANG language of the closed captions to download
|
||||
(optional) use IETF language tags like 'en'
|
||||
-f, --format FORMAT video format code, specifiy the order of
|
||||
preference using slashes: "-f 22/17/18"
|
||||
--all-formats download all available video formats
|
||||
--prefer-free-formats prefer free video formats unless a specific one
|
||||
is requested
|
||||
--max-quality FORMAT highest quality format to download
|
||||
-F, --list-formats list all available formats (currently youtube
|
||||
only)
|
||||
--write-sub write subtitle file (currently youtube only)
|
||||
--only-sub [deprecated] alias of --skip-download
|
||||
--all-subs downloads all the available subtitles of the
|
||||
video (currently youtube only)
|
||||
--list-subs lists all available subtitles for the video
|
||||
(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:
|
||||
-u, --username USERNAME account username
|
||||
-p, --password PASSWORD account password
|
||||
-n, --netrc use .netrc authentication data
|
||||
-u, --username USERNAME account username
|
||||
-p, --password PASSWORD account password
|
||||
-n, --netrc use .netrc authentication data
|
||||
|
||||
## Post-processing Options:
|
||||
-x, --extract-audio convert video files to audio-only files (requires
|
||||
ffmpeg or avconv and ffprobe or avprobe)
|
||||
--audio-format FORMAT "best", "aac", "vorbis", "mp3", "m4a", "opus", or
|
||||
"wav"; best by default
|
||||
--audio-quality QUALITY ffmpeg/avconv audio quality specification, insert a
|
||||
value between 0 (better) and 9 (worse) for VBR or a
|
||||
specific bitrate like 128K (default 5)
|
||||
--recode-video FORMAT Encode the video to another format if necessary
|
||||
(currently supported: mp4|flv|ogg|webm)
|
||||
-k, --keep-video keeps the video file on disk after the post-
|
||||
processing; the video is erased by default
|
||||
--no-post-overwrites do not overwrite post-processed files; the post-
|
||||
processed files are overwritten by default
|
||||
-x, --extract-audio convert video files to audio-only files (requires
|
||||
ffmpeg or avconv and ffprobe or avprobe)
|
||||
--audio-format FORMAT "best", "aac", "vorbis", "mp3", "m4a", "opus", or
|
||||
"wav"; best by default
|
||||
--audio-quality QUALITY ffmpeg/avconv audio quality specification, insert
|
||||
a value between 0 (better) and 9 (worse) for VBR
|
||||
or a specific bitrate like 128K (default 5)
|
||||
--recode-video FORMAT Encode the video to another format if necessary
|
||||
(currently supported: mp4|flv|ogg|webm)
|
||||
-k, --keep-video keeps the video file on disk after the post-
|
||||
processing; the video is erased by default
|
||||
--no-post-overwrites do not overwrite post-processed files; the post-
|
||||
processed files are overwritten by default
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
@ -137,6 +162,8 @@ The `-o` option allows users to indicate a template for the output file names. T
|
||||
- `ext`: The sequence will be replaced by the appropriate extension (like flv or mp4).
|
||||
- `epoch`: The sequence will be replaced by the Unix epoch when creating the file.
|
||||
- `autonumber`: The sequence will be replaced by a five-digit number that will be increased with each download, starting at zero.
|
||||
- `playlist`: The name or the id of the playlist that contains the video.
|
||||
- `playlist_index`: The index of the video in the playlist, a five-digit number.
|
||||
|
||||
The current default template is `%(id)s.%(ext)s`, but that will be switchted to `%(title)s-%(id)s.%(ext)s` (which can be requested with `-t` at the moment).
|
||||
|
||||
@ -147,6 +174,19 @@ In some cases, you don't want special characters such as 中, spaces, or &, such
|
||||
$ youtube-dl --get-filename -o "%(title)s.%(ext)s" BaW_jenozKc --restrict-filenames
|
||||
youtube-dl_test_video_.mp4 # A simple file name
|
||||
|
||||
# VIDEO SELECTION
|
||||
|
||||
Videos can be filtered by their upload date using the options `--date`, `--datebefore` or `--dateafter`, they accept dates in two formats:
|
||||
|
||||
- Absolute dates: Dates in the format `YYYYMMDD`.
|
||||
- Relative dates: Dates in the format `(now|today)[+-][0-9](day|week|month|year)(s)?`
|
||||
|
||||
Examples:
|
||||
|
||||
$ youtube-dl --dateafter now-6months #will only download the videos uploaded in the last 6 months
|
||||
$ youtube-dl --date 19700101 #will only download the videos uploaded in January 1, 1970
|
||||
$ youtube-dl --dateafter 20000101 --datebefore 20100101 #will only download the videos uploaded between 2000 and 2010
|
||||
|
||||
# FAQ
|
||||
|
||||
### Can you please put the -b option back?
|
||||
|
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.io/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)
|
||||
|
||||
|
@ -20,19 +20,19 @@ if [ ! -z "`git tag | grep "$version"`" ]; then echo 'ERROR: version already pre
|
||||
if [ ! -z "`git status --porcelain | grep -v CHANGELOG`" ]; then echo 'ERROR: the working directory is not clean; commit or stash changes'; exit 1; fi
|
||||
if [ ! -f "updates_key.pem" ]; then echo 'ERROR: updates_key.pem missing'; exit 1; fi
|
||||
|
||||
echo "\n### First of all, testing..."
|
||||
make clean
|
||||
nosetests --with-coverage --cover-package=youtube_dl --cover-html test || exit 1
|
||||
/bin/echo -e "\n### First of all, testing..."
|
||||
make cleanall
|
||||
nosetests --verbose --with-coverage --cover-package=youtube_dl --cover-html test --stop || exit 1
|
||||
|
||||
echo "\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
|
||||
|
||||
echo "\n### Committing CHANGELOG README.md and youtube_dl/version.py..."
|
||||
/bin/echo -e "\n### Committing CHANGELOG README.md and youtube_dl/version.py..."
|
||||
make README.md
|
||||
git add CHANGELOG README.md youtube_dl/version.py
|
||||
git commit -m "release $version"
|
||||
|
||||
echo "\n### Now tagging, signing and pushing..."
|
||||
/bin/echo -e "\n### Now tagging, signing and pushing..."
|
||||
git tag -s -m "Release $version" "$version"
|
||||
git show "$version"
|
||||
read -p "Is it good, can I push? (y/n) " -n 1
|
||||
@ -42,7 +42,7 @@ MASTER=$(git rev-parse --abbrev-ref HEAD)
|
||||
git push origin $MASTER:master
|
||||
git push origin "$version"
|
||||
|
||||
echo "\n### OK, now it is time to build the binaries..."
|
||||
/bin/echo -e "\n### OK, now it is time to build the binaries..."
|
||||
REV=$(git rev-parse HEAD)
|
||||
make youtube-dl youtube-dl.tar.gz
|
||||
wget "http://jeromelaheurte.net:8142/download/rg3/youtube-dl/youtube-dl.exe?rev=$REV" -O youtube-dl.exe || \
|
||||
@ -57,11 +57,11 @@ RELEASE_FILES="youtube-dl youtube-dl.exe youtube-dl-$version.tar.gz"
|
||||
(cd build/$version/ && sha512sum $RELEASE_FILES > SHA2-512SUMS)
|
||||
git checkout HEAD -- youtube-dl youtube-dl.exe
|
||||
|
||||
echo "\n### Signing and uploading the new binaries to youtube-dl.org..."
|
||||
/bin/echo -e "\n### Signing and uploading the new binaries to youtube-dl.org..."
|
||||
for f in $RELEASE_FILES; do gpg --detach-sig "build/$version/$f"; done
|
||||
scp -r "build/$version" ytdl@youtube-dl.org:html/downloads/
|
||||
|
||||
echo "\n### Now switching to gh-pages..."
|
||||
/bin/echo -e "\n### Now switching to gh-pages..."
|
||||
git clone --branch gh-pages --single-branch . build/gh-pages
|
||||
ROOT=$(pwd)
|
||||
(
|
||||
@ -69,6 +69,7 @@ ROOT=$(pwd)
|
||||
ORIGIN_URL=$(git config --get remote.origin.url)
|
||||
cd build/gh-pages
|
||||
"$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/generate-download.py"
|
||||
"$ROOT/devscripts/gh-pages/update-copyright.py"
|
||||
@ -83,7 +84,9 @@ ROOT=$(pwd)
|
||||
)
|
||||
rm -rf build
|
||||
|
||||
make pypi-files
|
||||
echo "Uploading to PyPi ..."
|
||||
pip sdist upload
|
||||
python setup.py sdist upload
|
||||
make clean
|
||||
|
||||
echo "\n### DONE!"
|
||||
/bin/echo -e "\n### DONE!"
|
||||
|
@ -40,7 +40,7 @@ raw_input()
|
||||
|
||||
filename = sys.argv[0]
|
||||
|
||||
UPDATE_URL = "http://rg3.github.com/youtube-dl/update/"
|
||||
UPDATE_URL = "http://rg3.github.io/youtube-dl/update/"
|
||||
VERSION_URL = UPDATE_URL + 'LATEST_VERSION'
|
||||
JSON_URL = UPDATE_URL + 'versions.json'
|
||||
UPDATES_RSA_KEY = (0x9d60ee4d8f805312fdb15a62f87b95bd66177b91df176765d13514a0f1754bcd2057295c5b6f1d35daa6742c3ffc9a82d3e118861c207995a8031e151d863c9927e304576bc80692bc8e094896fcf11b66f3e29e04e3a71e9a11558558acea1840aec37fc396fb6b65dc81a1c4144e03bd1c011de62e3f1357b327d08426fe93, 65537)
|
||||
|
@ -29,6 +29,7 @@
|
||||
"simulate": false,
|
||||
"skip_download": false,
|
||||
"subtitleslang": null,
|
||||
"subtitlesformat": "srt",
|
||||
"test": true,
|
||||
"updatetime": true,
|
||||
"usenetrc": false,
|
||||
@ -36,5 +37,8 @@
|
||||
"verbose": true,
|
||||
"writedescription": false,
|
||||
"writeinfojson": true,
|
||||
"writesubtitles": false
|
||||
}
|
||||
"writesubtitles": false,
|
||||
"onlysubtitles": false,
|
||||
"allsubtitles": false,
|
||||
"listssubtitles": false
|
||||
}
|
||||
|
@ -7,16 +7,43 @@ import unittest
|
||||
import os
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from youtube_dl.InfoExtractors import YoutubeIE, YoutubePlaylistIE
|
||||
from youtube_dl.InfoExtractors import YoutubeIE, YoutubePlaylistIE, YoutubeChannelIE, JustinTVIE
|
||||
|
||||
class TestAllURLsMatching(unittest.TestCase):
|
||||
def test_youtube_playlist_matching(self):
|
||||
self.assertTrue(YoutubePlaylistIE().suitable(u'ECUl4u3cNGP61MdtwGTqZA0MreSaDybji8'))
|
||||
self.assertTrue(YoutubePlaylistIE().suitable(u'PL63F0C78739B09958'))
|
||||
self.assertFalse(YoutubePlaylistIE().suitable(u'PLtS2H6bU1M'))
|
||||
self.assertTrue(YoutubePlaylistIE.suitable(u'ECUl4u3cNGP61MdtwGTqZA0MreSaDybji8'))
|
||||
self.assertTrue(YoutubePlaylistIE.suitable(u'UUBABnxM4Ar9ten8Mdjj1j0Q')) #585
|
||||
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):
|
||||
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_channel_matching(self):
|
||||
self.assertTrue(YoutubeChannelIE.suitable('https://www.youtube.com/channel/HCtnHdj3df7iM'))
|
||||
self.assertTrue(YoutubeChannelIE.suitable('https://www.youtube.com/channel/HCtnHdj3df7iM?feature=gb_ch_rec'))
|
||||
self.assertTrue(YoutubeChannelIE.suitable('https://www.youtube.com/channel/HCtnHdj3df7iM/videos'))
|
||||
|
||||
def test_justin_tv_channelid_matching(self):
|
||||
self.assertTrue(JustinTVIE.suitable(u"justin.tv/vanillatv"))
|
||||
self.assertTrue(JustinTVIE.suitable(u"twitch.tv/vanillatv"))
|
||||
self.assertTrue(JustinTVIE.suitable(u"www.justin.tv/vanillatv"))
|
||||
self.assertTrue(JustinTVIE.suitable(u"www.twitch.tv/vanillatv"))
|
||||
self.assertTrue(JustinTVIE.suitable(u"http://www.justin.tv/vanillatv"))
|
||||
self.assertTrue(JustinTVIE.suitable(u"http://www.twitch.tv/vanillatv"))
|
||||
self.assertTrue(JustinTVIE.suitable(u"http://www.justin.tv/vanillatv/"))
|
||||
self.assertTrue(JustinTVIE.suitable(u"http://www.twitch.tv/vanillatv/"))
|
||||
|
||||
def test_justintv_videoid_matching(self):
|
||||
self.assertTrue(JustinTVIE.suitable(u"http://www.twitch.tv/vanillatv/b/328087483"))
|
||||
|
||||
def test_justin_tv_chapterid_matching(self):
|
||||
self.assertTrue(JustinTVIE.suitable(u"http://www.twitch.tv/tsm_theoddone/c/2349361"))
|
||||
|
||||
def test_youtube_extract(self):
|
||||
self.assertEqual(YoutubeIE()._extract_id('http://www.youtube.com/watch?&v=BaW_jenozKc'), 'BaW_jenozKc')
|
||||
|
@ -7,8 +7,8 @@ import os
|
||||
import json
|
||||
import unittest
|
||||
import sys
|
||||
import hashlib
|
||||
import socket
|
||||
import binascii
|
||||
|
||||
# Allow direct execution
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
@ -20,6 +20,8 @@ from youtube_dl.utils import *
|
||||
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")
|
||||
|
||||
RETRIES = 3
|
||||
|
||||
# General configuration (from __init__, not very elegant...)
|
||||
jar = compat_cookiejar.CookieJar()
|
||||
cookie_processor = compat_urllib_request.HTTPCookieProcessor(jar)
|
||||
@ -36,11 +38,16 @@ def _try_rm(filename):
|
||||
if ose.errno != errno.ENOENT:
|
||||
raise
|
||||
|
||||
md5 = lambda s: hashlib.md5(s.encode('utf-8')).hexdigest()
|
||||
|
||||
class FileDownloader(youtube_dl.FileDownloader):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.to_stderr = self.to_screen
|
||||
self.processed_info_dicts = []
|
||||
return youtube_dl.FileDownloader.__init__(self, *args, **kwargs)
|
||||
def report_warning(self, message):
|
||||
# Don't accept warnings during tests
|
||||
raise ExtractorError(message)
|
||||
def process_info(self, info_dict):
|
||||
self.processed_info_dicts.append(info_dict)
|
||||
return youtube_dl.FileDownloader.process_info(self, info_dict)
|
||||
@ -56,6 +63,7 @@ with io.open(PARAMETERS_FILE, encoding='utf-8') as pf:
|
||||
|
||||
|
||||
class TestDownload(unittest.TestCase):
|
||||
maxDiff = None
|
||||
def setUp(self):
|
||||
self.parameters = parameters
|
||||
self.defs = defs
|
||||
@ -64,7 +72,7 @@ class TestDownload(unittest.TestCase):
|
||||
def generator(test_case):
|
||||
|
||||
def test_template(self):
|
||||
ie = getattr(youtube_dl.InfoExtractors, test_case['name'] + 'IE')
|
||||
ie = youtube_dl.InfoExtractors.get_info_extractor(test_case['name'])
|
||||
if not ie._WORKING:
|
||||
print('Skipping: IE marked as not _WORKING')
|
||||
return
|
||||
@ -79,9 +87,8 @@ def generator(test_case):
|
||||
params.update(test_case.get('params', {}))
|
||||
|
||||
fd = FileDownloader(params)
|
||||
fd.add_info_extractor(ie())
|
||||
for ien in test_case.get('add_ie', []):
|
||||
fd.add_info_extractor(getattr(youtube_dl.InfoExtractors, ien + 'IE')())
|
||||
for ie in youtube_dl.InfoExtractors.gen_extractors():
|
||||
fd.add_info_extractor(ie)
|
||||
finished_hook_called = set()
|
||||
def _hook(status):
|
||||
if status['status'] == 'finished':
|
||||
@ -94,7 +101,19 @@ def generator(test_case):
|
||||
_try_rm(tc['file'] + '.part')
|
||||
_try_rm(tc['file'] + '.info.json')
|
||||
try:
|
||||
fd.download([test_case['url']])
|
||||
for retry in range(1, RETRIES + 1):
|
||||
try:
|
||||
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:
|
||||
if not test_case.get('params', {}).get('skip_download', False):
|
||||
@ -107,7 +126,21 @@ def generator(test_case):
|
||||
with io.open(tc['file'] + '.info.json', encoding='utf-8') as infof:
|
||||
info_dict = json.load(infof)
|
||||
for (info_field, value) in tc.get('info_dict', {}).items():
|
||||
self.assertEqual(value, info_dict.get(info_field))
|
||||
if isinstance(value, compat_str) and value.startswith('md5:'):
|
||||
self.assertEqual(value, 'md5:' + md5(info_dict.get(info_field)))
|
||||
else:
|
||||
self.assertEqual(value, info_dict.get(info_field))
|
||||
|
||||
# If checkable fields are missing from the test case, print the info_dict
|
||||
test_info_dict = dict((key, value if not isinstance(value, compat_str) or len(value) < 250 else 'md5:' + md5(value))
|
||||
for key, value in info_dict.items()
|
||||
if value and key in ('title', 'description', 'uploader', 'upload_date', 'uploader_id', 'location'))
|
||||
if not all(key in tc.get('info_dict', {}).keys() for key in test_info_dict.keys()):
|
||||
sys.stderr.write(u'\n"info_dict": ' + json.dumps(test_info_dict, ensure_ascii=False, indent=2) + u'\n')
|
||||
|
||||
# Check for the presence of mandatory fields
|
||||
for key in ('id', 'url', 'title', 'ext'):
|
||||
self.assertTrue(key in info_dict.keys() and info_dict[key])
|
||||
finally:
|
||||
for tc in test_cases:
|
||||
_try_rm(tc['file'])
|
||||
|
@ -14,6 +14,8 @@ from youtube_dl.utils import timeconvert
|
||||
from youtube_dl.utils import sanitize_filename
|
||||
from youtube_dl.utils import unescapeHTML
|
||||
from youtube_dl.utils import orderedSet
|
||||
from youtube_dl.utils import DateRange
|
||||
from youtube_dl.utils import unified_strdate
|
||||
|
||||
if sys.version_info < (3, 0):
|
||||
_compat_str = lambda b: b.decode('unicode-escape')
|
||||
@ -95,6 +97,20 @@ class TestUtil(unittest.TestCase):
|
||||
|
||||
def test_unescape_html(self):
|
||||
self.assertEqual(unescapeHTML(_compat_str('%20;')), _compat_str('%20;'))
|
||||
|
||||
def test_daterange(self):
|
||||
_20century = DateRange("19000101","20000101")
|
||||
self.assertFalse("17890714" in _20century)
|
||||
_ac = DateRange("00010101")
|
||||
self.assertTrue("19690721" in _ac)
|
||||
_firstmilenium = DateRange(end="10000101")
|
||||
self.assertTrue("07110427" in _firstmilenium)
|
||||
|
||||
def test_unified_dates(self):
|
||||
self.assertEqual(unified_strdate('December 21, 2010'), '20101221')
|
||||
self.assertEqual(unified_strdate('8/7/2009'), '20090708')
|
||||
self.assertEqual(unified_strdate('Dec 14, 2012'), '20121214')
|
||||
self.assertEqual(unified_strdate('2012/10/11 01:56:38 +0000'), '20121011')
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
@ -8,8 +8,9 @@ import json
|
||||
import os
|
||||
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, YoutubeChannelIE
|
||||
from youtube_dl.utils import *
|
||||
from youtube_dl.FileDownloader import FileDownloader
|
||||
|
||||
PARAMETERS_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "parameters.json")
|
||||
with io.open(PARAMETERS_FILE, encoding='utf-8') as pf:
|
||||
@ -22,52 +23,92 @@ proxy_handler = compat_urllib_request.ProxyHandler()
|
||||
opener = compat_urllib_request.build_opener(proxy_handler, cookie_processor, YoutubeDLHandler())
|
||||
compat_urllib_request.install_opener(opener)
|
||||
|
||||
class FakeDownloader(object):
|
||||
class FakeDownloader(FileDownloader):
|
||||
def __init__(self):
|
||||
self.result = []
|
||||
self.params = parameters
|
||||
def to_screen(self, s):
|
||||
print(s)
|
||||
def trouble(self, s):
|
||||
def trouble(self, s, tb=None):
|
||||
raise Exception(s)
|
||||
def download(self, x):
|
||||
self.result.append(x)
|
||||
def extract_info(self, url):
|
||||
self.result.append(url)
|
||||
return url
|
||||
|
||||
class TestYoutubeLists(unittest.TestCase):
|
||||
def assertIsPlaylist(self,info):
|
||||
"""Make sure the info has '_type' set to 'playlist'"""
|
||||
self.assertEqual(info['_type'], 'playlist')
|
||||
|
||||
def test_youtube_playlist(self):
|
||||
DL = FakeDownloader()
|
||||
IE = YoutubePlaylistIE(DL)
|
||||
IE.extract('https://www.youtube.com/playlist?list=PLwiyx1dc3P2JR9N8gQaQN_BCvlSlap7re')
|
||||
self.assertEqual(DL.result, [
|
||||
['http://www.youtube.com/watch?v=bV9L5Ht9LgY'],
|
||||
['http://www.youtube.com/watch?v=FXxLjLQi3Fg'],
|
||||
['http://www.youtube.com/watch?v=tU3Bgo5qJZE']
|
||||
])
|
||||
dl = FakeDownloader()
|
||||
ie = YoutubePlaylistIE(dl)
|
||||
result = ie.extract('https://www.youtube.com/playlist?list=PLwiyx1dc3P2JR9N8gQaQN_BCvlSlap7re')[0]
|
||||
self.assertIsPlaylist(result)
|
||||
self.assertEqual(result['title'], 'ytdl test PL')
|
||||
ytie_results = [YoutubeIE()._extract_id(url['url']) for url in result['entries']]
|
||||
self.assertEqual(ytie_results, [ 'bV9L5Ht9LgY', 'FXxLjLQi3Fg', 'tU3Bgo5qJZE'])
|
||||
|
||||
def test_issue_673(self):
|
||||
dl = FakeDownloader()
|
||||
ie = YoutubePlaylistIE(dl)
|
||||
result = ie.extract('PLBB231211A4F62143')[0]
|
||||
self.assertTrue(len(result['entries']) > 25)
|
||||
|
||||
def test_youtube_playlist_long(self):
|
||||
DL = FakeDownloader()
|
||||
IE = YoutubePlaylistIE(DL)
|
||||
IE.extract('https://www.youtube.com/playlist?list=UUBABnxM4Ar9ten8Mdjj1j0Q')
|
||||
self.assertTrue(len(DL.result) >= 799)
|
||||
dl = FakeDownloader()
|
||||
ie = YoutubePlaylistIE(dl)
|
||||
result = ie.extract('https://www.youtube.com/playlist?list=UUBABnxM4Ar9ten8Mdjj1j0Q')[0]
|
||||
self.assertIsPlaylist(result)
|
||||
self.assertTrue(len(result['entries']) >= 799)
|
||||
|
||||
def test_youtube_playlist_with_deleted(self):
|
||||
#651
|
||||
dl = FakeDownloader()
|
||||
ie = YoutubePlaylistIE(dl)
|
||||
result = ie.extract('https://www.youtube.com/playlist?list=PLwP_SiAcdui0KVebT0mU9Apz359a4ubsC')[0]
|
||||
ytie_results = [YoutubeIE()._extract_id(url['url']) for url in result['entries']]
|
||||
self.assertFalse('pElCt5oNDuI' in ytie_results)
|
||||
self.assertFalse('KdPEApIVdWM' in ytie_results)
|
||||
|
||||
def test_youtube_playlist_empty(self):
|
||||
dl = FakeDownloader()
|
||||
ie = YoutubePlaylistIE(dl)
|
||||
result = ie.extract('https://www.youtube.com/playlist?list=PLtPgu7CB4gbZDA7i_euNxn75ISqxwZPYx')[0]
|
||||
self.assertIsPlaylist(result)
|
||||
self.assertEqual(len(result['entries']), 0)
|
||||
|
||||
def test_youtube_course(self):
|
||||
DL = FakeDownloader()
|
||||
IE = YoutubePlaylistIE(DL)
|
||||
dl = FakeDownloader()
|
||||
ie = YoutubePlaylistIE(dl)
|
||||
# TODO find a > 100 (paginating?) videos course
|
||||
IE.extract('https://www.youtube.com/course?list=ECUl4u3cNGP61MdtwGTqZA0MreSaDybji8')
|
||||
self.assertEqual(DL.result[0], ['http://www.youtube.com/watch?v=j9WZyLZCBzs'])
|
||||
self.assertEqual(len(DL.result), 25)
|
||||
self.assertEqual(DL.result[-1], ['http://www.youtube.com/watch?v=rYefUsYuEp0'])
|
||||
result = ie.extract('https://www.youtube.com/course?list=ECUl4u3cNGP61MdtwGTqZA0MreSaDybji8')[0]
|
||||
entries = result['entries']
|
||||
self.assertEqual(YoutubeIE()._extract_id(entries[0]['url']), 'j9WZyLZCBzs')
|
||||
self.assertEqual(len(entries), 25)
|
||||
self.assertEqual(YoutubeIE()._extract_id(entries[-1]['url']), 'rYefUsYuEp0')
|
||||
|
||||
def test_youtube_channel(self):
|
||||
# I give up, please find a channel that does paginate and test this like test_youtube_playlist_long
|
||||
pass # TODO
|
||||
dl = FakeDownloader()
|
||||
ie = YoutubeChannelIE(dl)
|
||||
#test paginated channel
|
||||
result = ie.extract('https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w')[0]
|
||||
self.assertTrue(len(result['entries']) > 90)
|
||||
#test autogenerated channel
|
||||
result = ie.extract('https://www.youtube.com/channel/HCtnHdj3df7iM/videos')[0]
|
||||
self.assertTrue(len(result['entries']) >= 18)
|
||||
|
||||
def test_youtube_user(self):
|
||||
DL = FakeDownloader()
|
||||
IE = YoutubeUserIE(DL)
|
||||
IE.extract('https://www.youtube.com/user/TheLinuxFoundation')
|
||||
self.assertTrue(len(DL.result) >= 320)
|
||||
dl = FakeDownloader()
|
||||
ie = YoutubeUserIE(dl)
|
||||
result = ie.extract('https://www.youtube.com/user/TheLinuxFoundation')[0]
|
||||
self.assertTrue(len(result['entries']) >= 320)
|
||||
|
||||
def test_youtube_safe_search(self):
|
||||
dl = FakeDownloader()
|
||||
ie = YoutubePlaylistIE(dl)
|
||||
result = ie.extract('PLtPgu7CB4gbY9oDN3drwC3cMbJggS7dKl')[0]
|
||||
self.assertEqual(len(result['entries']), 2)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
@ -12,6 +12,7 @@ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from youtube_dl.InfoExtractors import YoutubeIE
|
||||
from youtube_dl.utils import *
|
||||
from youtube_dl import FileDownloader
|
||||
|
||||
PARAMETERS_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "parameters.json")
|
||||
with io.open(PARAMETERS_FILE, encoding='utf-8') as pf:
|
||||
@ -24,13 +25,15 @@ proxy_handler = compat_urllib_request.ProxyHandler()
|
||||
opener = compat_urllib_request.build_opener(proxy_handler, cookie_processor, YoutubeDLHandler())
|
||||
compat_urllib_request.install_opener(opener)
|
||||
|
||||
class FakeDownloader(object):
|
||||
class FakeDownloader(FileDownloader):
|
||||
def __init__(self):
|
||||
self.result = []
|
||||
self.params = parameters
|
||||
# Different instances of the downloader can't share the same dictionary
|
||||
# some test set the "sublang" parameter, which would break the md5 checks.
|
||||
self.params = dict(parameters)
|
||||
def to_screen(self, s):
|
||||
print(s)
|
||||
def trouble(self, s):
|
||||
def trouble(self, s, tb=None):
|
||||
raise Exception(s)
|
||||
def download(self, x):
|
||||
self.result.append(x)
|
||||
@ -38,20 +41,71 @@ class FakeDownloader(object):
|
||||
md5 = lambda s: hashlib.md5(s.encode('utf-8')).hexdigest()
|
||||
|
||||
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):
|
||||
DL = FakeDownloader()
|
||||
DL.params['writesubtitles'] = True
|
||||
IE = YoutubeIE(DL)
|
||||
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):
|
||||
DL = FakeDownloader()
|
||||
DL.params['writesubtitles'] = True
|
||||
DL.params['subtitleslang'] = 'it'
|
||||
IE = YoutubeIE(DL)
|
||||
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), 13)
|
||||
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)
|
||||
def test_youtube_automatic_captions(self):
|
||||
DL = FakeDownloader()
|
||||
DL.params['writesubtitles'] = True
|
||||
DL.params['subtitleslang'] = 'it'
|
||||
IE = YoutubeIE(DL)
|
||||
info_dict = IE.extract('8YoUxe5ncPo')
|
||||
sub = info_dict[0]['subtitles'][0]
|
||||
self.assertTrue(sub[2] is not None)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
427
test/tests.json
427
test/tests.json
@ -15,43 +15,76 @@
|
||||
"name": "Dailymotion",
|
||||
"md5": "392c4b85a60a90dc4792da41ce3144eb",
|
||||
"url": "http://www.dailymotion.com/video/x33vw9_tutoriel-de-youtubeur-dl-des-video_tech",
|
||||
"file": "x33vw9.mp4"
|
||||
"file": "x33vw9.mp4",
|
||||
"info_dict": {
|
||||
"uploader": "Alex and Van .",
|
||||
"title": "Tutoriel de Youtubeur\"DL DES VIDEO DE YOUTUBE\""
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Metacafe",
|
||||
"add_ie": ["Youtube"],
|
||||
"url": "http://metacafe.com/watch/yt-_aUehQsCQtM/the_electric_company_short_i_pbs_kids_go/",
|
||||
"file": "_aUehQsCQtM.flv"
|
||||
"file": "_aUehQsCQtM.flv",
|
||||
"info_dict": {
|
||||
"upload_date": "20090102",
|
||||
"title": "The Electric Company | \"Short I\" | PBS KIDS GO!",
|
||||
"description": "md5:2439a8ef6d5a70e380c22f5ad323e5a8",
|
||||
"uploader": "PBS",
|
||||
"uploader_id": "PBS"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "BlipTV",
|
||||
"md5": "b2d849efcf7ee18917e4b4d9ff37cafe",
|
||||
"url": "http://blip.tv/cbr/cbr-exclusive-gotham-city-imposters-bats-vs-jokerz-short-3-5796352",
|
||||
"file": "5779306.m4v"
|
||||
"file": "5779306.m4v",
|
||||
"info_dict": {
|
||||
"upload_date": "20111205",
|
||||
"description": "md5:9bc31f227219cde65e47eeec8d2dc596",
|
||||
"uploader": "Comic Book Resources - CBR TV",
|
||||
"title": "CBR EXCLUSIVE: \"Gotham City Imposters\" Bats VS Jokerz Short 3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "XVideos",
|
||||
"md5": "1d0c835822f0a71a7bf011855db929d0",
|
||||
"url": "http://www.xvideos.com/video939581/funny_porns_by_s_-1",
|
||||
"file": "939581.flv"
|
||||
"file": "939581.flv",
|
||||
"info_dict": {
|
||||
"title": "Funny Porns By >>>>S<<<<<< -1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "YouPorn",
|
||||
"md5": "c37ddbaaa39058c76a7e86c6813423c1",
|
||||
"url": "http://www.youporn.com/watch/505835/sex-ed-is-it-safe-to-masturbate-daily/",
|
||||
"file": "505835.mp4"
|
||||
"file": "505835.mp4",
|
||||
"info_dict": {
|
||||
"upload_date": "20101221",
|
||||
"description": "Love & Sex Answers: http://bit.ly/DanAndJenn -- Is It Unhealthy To Masturbate Daily?",
|
||||
"uploader": "Ask Dan And Jennifer",
|
||||
"title": "Sex Ed: Is It Safe To Masturbate Daily?"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Pornotube",
|
||||
"md5": "374dd6dcedd24234453b295209aa69b6",
|
||||
"url": "http://pornotube.com/c/173/m/1689755/Marilyn-Monroe-Bathing",
|
||||
"file": "1689755.flv"
|
||||
"file": "1689755.flv",
|
||||
"info_dict": {
|
||||
"upload_date": "20090708",
|
||||
"title": "Marilyn-Monroe-Bathing"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "YouJizz",
|
||||
"md5": "07e15fa469ba384c7693fd246905547c",
|
||||
"url": "http://www.youjizz.com/videos/zeichentrick-1-2189178.html",
|
||||
"file": "2189178.flv"
|
||||
"file": "2189178.flv",
|
||||
"info_dict": {
|
||||
"title": "Zeichentrick 1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Vimeo",
|
||||
@ -70,73 +103,102 @@
|
||||
"name": "Soundcloud",
|
||||
"md5": "ebef0a451b909710ed1d7787dddbf0d7",
|
||||
"url": "http://soundcloud.com/ethmusic/lostin-powers-she-so-heavy",
|
||||
"file": "62986583.mp3"
|
||||
"file": "62986583.mp3",
|
||||
"info_dict": {
|
||||
"upload_date": "20121011",
|
||||
"description": "No Downloads untill we record the finished version this weekend, i was too pumped n i had to post it , earl is prolly gonna b hella p.o'd",
|
||||
"uploader": "E.T. ExTerrestrial Music",
|
||||
"title": "Lostin Powers - She so Heavy (SneakPreview) Adrian Ackers Blueprint 1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "StanfordOpenClassroom",
|
||||
"md5": "544a9468546059d4e80d76265b0443b8",
|
||||
"url": "http://openclassroom.stanford.edu/MainFolder/VideoPage.php?course=PracticalUnix&video=intro-environment&speed=100",
|
||||
"file": "PracticalUnix_intro-environment.mp4",
|
||||
"skip": "Currently offline"
|
||||
"info_dict": {
|
||||
"title": "Intro Environment"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "XNXX",
|
||||
"md5": "0831677e2b4761795f68d417e0b7b445",
|
||||
"url": "http://video.xnxx.com/video1135332/lida_naked_funny_actress_5_",
|
||||
"file": "1135332.flv"
|
||||
"file": "1135332.flv",
|
||||
"info_dict": {
|
||||
"title": "lida » Naked Funny Actress (5)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Youku",
|
||||
"url": "http://v.youku.com/v_show/id_XNDgyMDQ2NTQw.html",
|
||||
"file": "XNDgyMDQ2NTQw_part00.flv",
|
||||
"md5": "ffe3f2e435663dc2d1eea34faeff5b5b",
|
||||
"params": { "test": false }
|
||||
"params": { "test": false },
|
||||
"info_dict": {
|
||||
"title": "youtube-dl test video \"'/\\ä↭𝕐"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "NBA",
|
||||
"url": "http://www.nba.com/video/games/nets/2012/12/04/0021200253-okc-bkn-recap.nba/index.html",
|
||||
"file": "0021200253-okc-bkn-recap.nba.mp4",
|
||||
"md5": "c0edcfc37607344e2ff8f13c378c88a4"
|
||||
"md5": "c0edcfc37607344e2ff8f13c378c88a4",
|
||||
"info_dict": {
|
||||
"description": "Kevin Durant scores 32 points and dishes out six assists as the Thunder beat the Nets in Brooklyn.",
|
||||
"title": "Thunder vs. Nets"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "JustinTV",
|
||||
"url": "http://www.twitch.tv/thegamedevhub/b/296128360",
|
||||
"file": "296128360.flv",
|
||||
"md5": "ecaa8a790c22a40770901460af191c9a"
|
||||
"md5": "ecaa8a790c22a40770901460af191c9a",
|
||||
"info_dict": {
|
||||
"upload_date": "20110927",
|
||||
"uploader_id": 25114803,
|
||||
"uploader": "thegamedevhub",
|
||||
"title": "Beginner Series - Scripting With Python Pt.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "MyVideo",
|
||||
"url": "http://www.myvideo.de/watch/8229274/bowling_fail_or_win",
|
||||
"file": "8229274.flv",
|
||||
"md5": "2d2753e8130479ba2cb7e0a37002053e"
|
||||
"md5": "2d2753e8130479ba2cb7e0a37002053e",
|
||||
"info_dict": {
|
||||
"title": "bowling-fail-or-win"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Escapist",
|
||||
"url": "http://www.escapistmagazine.com/videos/view/the-escapist-presents/6618-Breaking-Down-Baldurs-Gate",
|
||||
"file": "6618-Breaking-Down-Baldurs-Gate.flv",
|
||||
"md5": "c6793dbda81388f4264c1ba18684a74d"
|
||||
"file": "6618-Breaking-Down-Baldurs-Gate.mp4",
|
||||
"md5": "c6793dbda81388f4264c1ba18684a74d",
|
||||
"info_dict": {
|
||||
"description": "Baldur's Gate: Original, Modded or Enhanced Edition? I'll break down what you can expect from the new Baldur's Gate: Enhanced Edition.",
|
||||
"uploader": "the-escapist-presents",
|
||||
"title": "Breaking Down Baldur's Gate"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "GooglePlus",
|
||||
"url": "https://plus.google.com/u/0/108897254135232129896/posts/ZButuJc6CtH",
|
||||
"file": "ZButuJc6CtH.flv"
|
||||
"file": "ZButuJc6CtH.flv",
|
||||
"info_dict": {
|
||||
"upload_date": "20120613",
|
||||
"uploader": "井上ヨシマサ",
|
||||
"title": "嘆きの天使 降臨"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "FunnyOrDie",
|
||||
"url": "http://www.funnyordie.com/videos/0732f586d7/heart-shaped-box-literal-video-version",
|
||||
"file": "0732f586d7.mp4",
|
||||
"md5": "f647e9e90064b53b6e046e75d0241fbd"
|
||||
},
|
||||
{
|
||||
"name": "TweetReel",
|
||||
"url": "http://tweetreel.com/?77smq",
|
||||
"file": "77smq.mov",
|
||||
"md5": "56b4d9ca9de467920f3f99a6d91255d6",
|
||||
"md5": "f647e9e90064b53b6e046e75d0241fbd",
|
||||
"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"
|
||||
"description": "Lyrics changed to match the video. Spoken cameo by Obscurus Lupa (from ThatGuyWithTheGlasses.com). Based on a concept by Dustin McLean (DustFilms.com). Performed, edited, and written by David A. Scott.",
|
||||
"title": "Heart-Shaped Box: Literal Video Version"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -165,7 +227,8 @@
|
||||
"file": "20274954.flv",
|
||||
"md5": "088f151799e8f572f84eb62f17d73e5c",
|
||||
"info_dict": {
|
||||
"title": "Young Americans for Liberty February 7, 2012 2:28 AM"
|
||||
"title": "Young Americans for Liberty February 7, 2012 2:28 AM",
|
||||
"uploader": "Young Americans for Liberty"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -173,6 +236,7 @@
|
||||
"url": "http://www.infoq.com/presentations/A-Few-of-My-Favorite-Python-Things",
|
||||
"file": "12-jan-pythonthings.mp4",
|
||||
"info_dict": {
|
||||
"description": "Mike Pirnat presents some tips and tricks, standard libraries and third party packages that make programming in Python a richer experience.",
|
||||
"title": "A Few of My Favorite [Python] Things"
|
||||
},
|
||||
"params": {
|
||||
@ -185,7 +249,10 @@
|
||||
"file": "422212.mp4",
|
||||
"md5": "4e2f5cb088a83cd8cdb7756132f9739d",
|
||||
"info_dict": {
|
||||
"title": "thedailyshow-kristen-stewart part 1"
|
||||
"upload_date": "20121214",
|
||||
"description": "Kristen Stewart",
|
||||
"uploader": "thedailyshow",
|
||||
"title": "thedailyshow-kristen-stewart part 1"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -236,44 +303,332 @@
|
||||
"file": "11885679.m4a",
|
||||
"md5": "d30b5b5f74217410f4689605c35d1fd7",
|
||||
"info_dict": {
|
||||
"title": "youtube-dl project as well - youtube-dl test track 3 \"'/\\\u00e4\u21ad"
|
||||
"title": "youtube-dl project as well - youtube-dl test track 3 \"'/\\\u00e4\u21ad",
|
||||
"uploader_id": "ytdl"
|
||||
}
|
||||
},
|
||||
{
|
||||
"file": "11885680.m4a",
|
||||
"md5": "4eb0a669317cd725f6bbd336a29f923a",
|
||||
"info_dict": {
|
||||
"title": "youtube-dl project as well - youtube-dl test track 4 \"'/\\\u00e4\u21ad"
|
||||
"title": "youtube-dl project as well - youtube-dl test track 4 \"'/\\\u00e4\u21ad",
|
||||
"uploader_id": "ytdl"
|
||||
}
|
||||
},
|
||||
{
|
||||
"file": "11885682.m4a",
|
||||
"md5": "1893e872e263a2705558d1d319ad19e8",
|
||||
"info_dict": {
|
||||
"title": "PH - youtube-dl test track 5 \"'/\\\u00e4\u21ad"
|
||||
"title": "PH - youtube-dl test track 5 \"'/\\\u00e4\u21ad",
|
||||
"uploader_id": "ytdl"
|
||||
}
|
||||
},
|
||||
{
|
||||
"file": "11885683.m4a",
|
||||
"md5": "b673c46f47a216ab1741ae8836af5899",
|
||||
"info_dict": {
|
||||
"title": "PH - youtube-dl test track 6 \"'/\\\u00e4\u21ad"
|
||||
"title": "PH - youtube-dl test track 6 \"'/\\\u00e4\u21ad",
|
||||
"uploader_id": "ytdl"
|
||||
}
|
||||
},
|
||||
{
|
||||
"file": "11885684.m4a",
|
||||
"md5": "1d74534e95df54986da7f5abf7d842b7",
|
||||
"info_dict": {
|
||||
"title": "phihag - youtube-dl test track 7 \"'/\\\u00e4\u21ad"
|
||||
"title": "phihag - youtube-dl test track 7 \"'/\\\u00e4\u21ad",
|
||||
"uploader_id": "ytdl"
|
||||
}
|
||||
},
|
||||
{
|
||||
"file": "11885685.m4a",
|
||||
"md5": "f081f47af8f6ae782ed131d38b9cd1c0",
|
||||
"info_dict": {
|
||||
"title": "phihag - youtube-dl test track 8 \"'/\\\u00e4\u21ad"
|
||||
"title": "phihag - youtube-dl test track 8 \"'/\\\u00e4\u21ad",
|
||||
"uploader_id": "ytdl"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Keek",
|
||||
"url": "http://www.keek.com/ytdl/keeks/NODfbab",
|
||||
"file": "NODfbab.mp4",
|
||||
"md5": "9b0636f8c0f7614afa4ea5e4c6e57e83",
|
||||
"info_dict": {
|
||||
"uploader": "ytdl",
|
||||
"title": "test chars: \"'/\\ä<>This is a test video for youtube-dl.For more information, contact phihag@phihag.de ."
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "TED",
|
||||
"url": "http://www.ted.com/talks/dan_dennett_on_our_consciousness.html",
|
||||
"file": "102.mp4",
|
||||
"md5": "8cd9dfa41ee000ce658fd48fb5d89a61",
|
||||
"info_dict": {
|
||||
"title": "Dan Dennett: The illusion of consciousness",
|
||||
"description": "md5:c6fa72e6eedbd938c9caf6b2702f5922"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "MySpass",
|
||||
"url": "http://www.myspass.de/myspass/shows/tvshows/absolute-mehrheit/Absolute-Mehrheit-vom-17022013-Die-Highlights-Teil-2--/11741/",
|
||||
"file": "11741.mp4",
|
||||
"md5": "0b49f4844a068f8b33f4b7c88405862b",
|
||||
"info_dict": {
|
||||
"description": "Wer kann in die Fußstapfen von Wolfgang Kubicki treten und die Mehrheit der Zuschauer hinter sich versammeln? Wird vielleicht sogar die Absolute Mehrheit geknackt und der Jackpot von 200.000 Euro mit nach Hause genommen?",
|
||||
"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",
|
||||
"info_dict": {
|
||||
"uploader": "www.hodiho.fr",
|
||||
"title": "Régis plante sa Jeep"
|
||||
}
|
||||
},
|
||||
{
|
||||
"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!"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ARD",
|
||||
"url": "http://www.ardmediathek.de/das-erste/tagesschau-in-100-sek?documentId=14077640",
|
||||
"file": "14077640.mp4",
|
||||
"md5": "6ca8824255460c787376353f9e20bbd8",
|
||||
"info_dict": {
|
||||
"title": "11.04.2013 09:23 Uhr - Tagesschau in 100 Sekunden"
|
||||
},
|
||||
"skip": "Requires rtmpdump"
|
||||
},
|
||||
{
|
||||
"name": "Tumblr",
|
||||
"url": "http://resigno.tumblr.com/post/53364321212/e-de-extrema-importancia-que-esse-video-seja",
|
||||
"file": "53364321212.mp4",
|
||||
"md5": "0716d3dd51baf68a28b40fdf1251494e",
|
||||
"info_dict": {
|
||||
"title": "Rafael Lemos | Tumblr"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "SoundcloudSet",
|
||||
"url":"https://soundcloud.com/the-concept-band/sets/the-royal-concept-ep",
|
||||
"playlist":[
|
||||
{
|
||||
"file":"30510138.mp3",
|
||||
"md5":"f9136bf103901728f29e419d2c70f55d",
|
||||
"info_dict": {
|
||||
"upload_date": "20111213",
|
||||
"description": "The Royal Concept from Stockholm\r\nFilip / Povel / David / Magnus\r\nwww.royalconceptband.com",
|
||||
"uploader": "The Royal Concept",
|
||||
"title": "D-D-Dance"
|
||||
}
|
||||
},
|
||||
{
|
||||
"file":"47127625.mp3",
|
||||
"md5":"09b6758a018470570f8fd423c9453dd8",
|
||||
"info_dict": {
|
||||
"upload_date": "20120521",
|
||||
"description": "The Royal Concept from Stockholm\r\nFilip / Povel / David / Magnus\r\nwww.royalconceptband.com",
|
||||
"uploader": "The Royal Concept",
|
||||
"title": "The Royal Concept - Gimme Twice"
|
||||
}
|
||||
},
|
||||
{
|
||||
"file":"47127627.mp3",
|
||||
"md5":"154abd4e418cea19c3b901f1e1306d9c",
|
||||
"info_dict": {
|
||||
"upload_date": "20120521",
|
||||
"uploader": "The Royal Concept",
|
||||
"title": "Goldrushed"
|
||||
}
|
||||
},
|
||||
{
|
||||
"file":"47127629.mp3",
|
||||
"md5":"2f5471edc79ad3f33a683153e96a79c1",
|
||||
"info_dict": {
|
||||
"upload_date": "20120521",
|
||||
"description": "The Royal Concept from Stockholm\r\nFilip / Povel / David / Magnus\r\nwww.royalconceptband.com",
|
||||
"uploader": "The Royal Concept",
|
||||
"title": "In the End"
|
||||
}
|
||||
},
|
||||
{
|
||||
"file":"47127631.mp3",
|
||||
"md5":"f9ba87aa940af7213f98949254f1c6e2",
|
||||
"info_dict": {
|
||||
"upload_date": "20120521",
|
||||
"description": "The Royal Concept from Stockholm\r\nFilip / David / Povel / Magnus\r\nwww.theroyalconceptband.com",
|
||||
"uploader": "The Royal Concept",
|
||||
"title": "Knocked Up"
|
||||
}
|
||||
},
|
||||
{
|
||||
"file":"75206121.mp3",
|
||||
"md5":"f9d1fe9406717e302980c30de4af9353",
|
||||
"info_dict": {
|
||||
"upload_date": "20130116",
|
||||
"description": "The unreleased track World on Fire premiered on the CW's hit show Arrow (8pm/7pm central). \r\nAs a gift to our fans we would like to offer you a free download of the track! ",
|
||||
"uploader": "The Royal Concept",
|
||||
"title": "World On Fire"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name":"Bandcamp",
|
||||
"url":"http://youtube-dl.bandcamp.com/track/youtube-dl-test-song",
|
||||
"file":"1812978515.mp3",
|
||||
"md5":"cdeb30cdae1921719a3cbcab696ef53c",
|
||||
"info_dict": {
|
||||
"title":"youtube-dl test song \"'/\\ä↭"
|
||||
},
|
||||
"skip": "There is a limit of 200 free downloads / month for the test song"
|
||||
},
|
||||
{
|
||||
"name": "RedTube",
|
||||
"url": "http://www.redtube.com/66418",
|
||||
"file": "66418.mp4",
|
||||
"md5": "7b8c22b5e7098a3e1c09709df1126d2d",
|
||||
"info_dict":{
|
||||
"title":"Sucked on a toilet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Photobucket",
|
||||
"url": "http://media.photobucket.com/user/rachaneronas/media/TiredofLinkBuildingTryBacklinkMyDomaincom_zpsc0c3b9fa.mp4.html?filters[term]=search&filters[primary]=videos&filters[secondary]=images&sort=1&o=0",
|
||||
"file": "zpsc0c3b9fa.mp4",
|
||||
"md5": "7dabfb92b0a31f6c16cebc0f8e60ff99",
|
||||
"info_dict": {
|
||||
"upload_date": "20130504",
|
||||
"uploader": "rachaneronas",
|
||||
"title": "Tired of Link Building? Try BacklinkMyDomain.com!"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Ina",
|
||||
"url": "www.ina.fr/video/I12055569/francois-hollande-je-crois-que-c-est-clair-video.html",
|
||||
"file": "I12055569.mp4",
|
||||
"md5": "a667021bf2b41f8dc6049479d9bb38a3",
|
||||
"info_dict":{
|
||||
"title":"François Hollande \"Je crois que c'est clair\""
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Yahoo",
|
||||
"url": "http://screen.yahoo.com/julian-smith-travis-legg-watch-214727115.html",
|
||||
"file": "214727115.flv",
|
||||
"md5": "2e717f169c1be93d84d3794a00d4a325",
|
||||
"info_dict": {
|
||||
"title": "Julian Smith & Travis Legg Watch Julian Smith"
|
||||
},
|
||||
"skip": "Requires rtmpdump"
|
||||
},
|
||||
{
|
||||
"name": "Howcast",
|
||||
"url": "http://www.howcast.com/videos/390161-How-to-Tie-a-Square-Knot-Properly",
|
||||
"file": "390161.mp4",
|
||||
"md5": "1d7ba54e2c9d7dc6935ef39e00529138",
|
||||
"info_dict":{
|
||||
"title":"How to Tie a Square Knot Properly",
|
||||
"description":"The square knot, also known as the reef knot, is one of the oldest, most basic knots to tie, and can be used in many different ways. Here's the proper way to tie a square knot."
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Vine",
|
||||
"url": "https://vine.co/v/b9KOOWX7HUx",
|
||||
"file": "b9KOOWX7HUx.mp4",
|
||||
"md5": "2f36fed6235b16da96ce9b4dc890940d",
|
||||
"info_dict":{
|
||||
"title": "Chicken.",
|
||||
"uploader": "Jack Dorsey"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Flickr",
|
||||
"url": "http://www.flickr.com/photos/forestwander-nature-pictures/5645318632/in/photostream/",
|
||||
"file": "5645318632.mp4",
|
||||
"md5": "6fdc01adbc89d72fc9c4f15b4a4ba87b",
|
||||
"info_dict":{
|
||||
"title": "Dark Hollow Waterfalls",
|
||||
"uploader_id": "forestwander-nature-pictures",
|
||||
"description": "Waterfalls in the Springtime at Dark Hollow Waterfalls. These are located just off of Skyline Drive in Virginia. They are only about 6/10 of a mile hike but it is a pretty steep hill and a good climb back up."
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Teamcoco",
|
||||
"url": "http://teamcoco.com/video/louis-ck-interview-george-w-bush",
|
||||
"file": "19705.mp4",
|
||||
"md5": "27b6f7527da5acf534b15f21b032656e",
|
||||
"info_dict":{
|
||||
"title": "Louis C.K. Interview Pt. 1 11/3/11",
|
||||
"description": "Louis C.K. got starstruck by George W. Bush, so what? Part one."
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "XHamster",
|
||||
"url": "http://xhamster.com/movies/1509445/femaleagent_shy_beauty_takes_the_bait.html",
|
||||
"file": "1509445.flv",
|
||||
"md5": "9f48e0e8d58e3076bb236ff412ab62fa",
|
||||
"info_dict": {
|
||||
"upload_date": "20121014",
|
||||
"uploader_id": "Ruseful2011",
|
||||
"title": "FemaleAgent Shy beauty takes the bait"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Hypem",
|
||||
"url": "http://hypem.com/track/1v6ga/BODYWORK+-+TAME",
|
||||
"file": "1v6ga.mp3",
|
||||
"md5": "b9cc91b5af8995e9f0c1cee04c575828",
|
||||
"info_dict":{
|
||||
"title":"Tame"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Vbox7",
|
||||
"url": "http://vbox7.com/play:249bb972c2",
|
||||
"file": "249bb972c2.flv",
|
||||
"md5": "9c70d6d956f888bdc08c124acc120cfe",
|
||||
"info_dict":{
|
||||
"title":"Смях! Чудо - чист за секунди - Скрита камера"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Gametrailers",
|
||||
"url": "http://www.gametrailers.com/videos/zbvr8i/mirror-s-edge-2-e3-2013--debut-trailer",
|
||||
"file": "zbvr8i.flv",
|
||||
"md5": "c3edbc995ab4081976e16779bd96a878",
|
||||
"info_dict": {
|
||||
"title": "E3 2013: Debut Trailer"
|
||||
},
|
||||
"skip": "Requires rtmpdump"
|
||||
}
|
||||
]
|
||||
|
@ -38,7 +38,7 @@ def rsa_verify(message, signature, key):
|
||||
|
||||
sys.stderr.write(u'Hi! We changed distribution method and now youtube-dl needs to update itself one more time.\n')
|
||||
sys.stderr.write(u'This will only happen once. Simply press enter to go on. Sorry for the trouble!\n')
|
||||
sys.stderr.write(u'From now on, get the binaries from http://rg3.github.com/youtube-dl/download.html, not from the git repository.\n\n')
|
||||
sys.stderr.write(u'From now on, get the binaries from http://rg3.github.io/youtube-dl/download.html, not from the git repository.\n\n')
|
||||
|
||||
try:
|
||||
raw_input()
|
||||
@ -47,7 +47,7 @@ except NameError: # Python 3
|
||||
|
||||
filename = sys.argv[0]
|
||||
|
||||
UPDATE_URL = "http://rg3.github.com/youtube-dl/update/"
|
||||
UPDATE_URL = "http://rg3.github.io/youtube-dl/update/"
|
||||
VERSION_URL = UPDATE_URL + 'LATEST_VERSION'
|
||||
JSON_URL = UPDATE_URL + 'versions.json'
|
||||
UPDATES_RSA_KEY = (0x9d60ee4d8f805312fdb15a62f87b95bd66177b91df176765d13514a0f1754bcd2057295c5b6f1d35daa6742c3ffc9a82d3e118861c207995a8031e151d863c9927e304576bc80692bc8e094896fcf11b66f3e29e04e3a71e9a11558558acea1840aec37fc396fb6b65dc81a1c4144e03bd1c011de62e3f1357b327d08426fe93, 65537)
|
||||
|
@ -7,6 +7,7 @@ import math
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
@ -17,6 +18,7 @@ if os.name == 'nt':
|
||||
import ctypes
|
||||
|
||||
from .utils import *
|
||||
from .InfoExtractors import get_info_extractor
|
||||
|
||||
|
||||
class FileDownloader(object):
|
||||
@ -52,6 +54,7 @@ class FileDownloader(object):
|
||||
quiet: Do not print messages to stdout.
|
||||
forceurl: Force printing final URL.
|
||||
forcetitle: Force printing title.
|
||||
forceid: Force printing ID.
|
||||
forcethumbnail: Force printing thumbnail URL.
|
||||
forcedescription: Force printing description.
|
||||
forcefilename: Force printing final filename.
|
||||
@ -78,12 +81,18 @@ class FileDownloader(object):
|
||||
updatetime: Use the Last-modified header to set output file timestamps.
|
||||
writedescription: Write the video description to a .description file
|
||||
writeinfojson: Write the video description to a .info.json file
|
||||
writesubtitles: Write the video subtitles to a .srt file
|
||||
writethumbnail: Write the thumbnail image to a file
|
||||
writesubtitles: Write the video subtitles to a file
|
||||
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
|
||||
test: Download only first bytes to test the downloader.
|
||||
keepvideo: Keep the video file after post-processing
|
||||
min_filesize: Skip files smaller than this size
|
||||
max_filesize: Skip files larger than this size
|
||||
daterange: A DateRange object, download only if the upload_date is in the range.
|
||||
skip_download: Skip the actual download of the video file
|
||||
"""
|
||||
|
||||
params = None
|
||||
@ -104,7 +113,7 @@ class FileDownloader(object):
|
||||
self.params = params
|
||||
|
||||
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
|
||||
def format_bytes(bytes):
|
||||
@ -116,7 +125,7 @@ class FileDownloader(object):
|
||||
exponent = 0
|
||||
else:
|
||||
exponent = int(math.log(bytes, 1024.0))
|
||||
suffix = 'bkMGTPEZY'[exponent]
|
||||
suffix = ['B','KiB','MiB','GiB','TiB','PiB','EiB','ZiB','YiB'][exponent]
|
||||
converted = float(bytes) / float(1024 ** exponent)
|
||||
return '%.2f%s' % (converted, suffix)
|
||||
|
||||
@ -208,7 +217,7 @@ class FileDownloader(object):
|
||||
# already of type unicode()
|
||||
ctypes.windll.kernel32.SetConsoleTitleW(ctypes.c_wchar_p(message))
|
||||
elif 'TERM' in os.environ:
|
||||
sys.stderr.write('\033]0;%s\007' % message.encode(preferredencoding()))
|
||||
self.to_screen('\033]0;%s\007' % message, skip_eol=True)
|
||||
|
||||
def fixed_template(self):
|
||||
"""Checks if the output template is fixed."""
|
||||
@ -227,13 +236,47 @@ class FileDownloader(object):
|
||||
self.to_stderr(message)
|
||||
if self.params.get('verbose'):
|
||||
if tb is None:
|
||||
tb_data = traceback.format_list(traceback.extract_stack())
|
||||
tb = u''.join(tb_data)
|
||||
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 = u''.join(tb_data)
|
||||
self.to_stderr(tb)
|
||||
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
|
||||
|
||||
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() and os.name != 'nt':
|
||||
_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() and os.name != 'nt':
|
||||
_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):
|
||||
"""Sleep if the download speed is over the rate limit."""
|
||||
rate_limit = self.params.get('ratelimit', None)
|
||||
@ -265,7 +308,7 @@ class FileDownloader(object):
|
||||
return
|
||||
os.rename(encodeFilename(old_filename), encodeFilename(new_filename))
|
||||
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):
|
||||
"""Try to set the last-modified time of the given file."""
|
||||
@ -279,6 +322,9 @@ class FileDownloader(object):
|
||||
filetime = timeconvert(timestr)
|
||||
if filetime is None:
|
||||
return filetime
|
||||
# Ignore obviously invalid dates
|
||||
if filetime == 0:
|
||||
return
|
||||
try:
|
||||
os.utime(filename, (time.time(), filetime))
|
||||
except:
|
||||
@ -289,9 +335,9 @@ class FileDownloader(object):
|
||||
""" Report that the description file is being written """
|
||||
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 """
|
||||
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):
|
||||
""" Report that the metadata file has been written """
|
||||
@ -305,8 +351,13 @@ class FileDownloader(object):
|
||||
"""Report download progress."""
|
||||
if self.params.get('noprogress', False):
|
||||
return
|
||||
self.to_screen(u'\r[download] %s of %s at %s ETA %s' %
|
||||
(percent_str, data_len_str, speed_str, eta_str), skip_eol=True)
|
||||
clear_line = (u'\x1b[K' if sys.stderr.isatty() and os.name != 'nt' else u'')
|
||||
if self.params.get('progress_with_newline', False):
|
||||
self.to_screen(u'[download] %s of %s at %s ETA %s' %
|
||||
(percent_str, data_len_str, speed_str, eta_str))
|
||||
else:
|
||||
self.to_screen(u'\r%s[download] %s of %s at %s ETA %s' %
|
||||
(clear_line, percent_str, data_len_str, speed_str, eta_str), skip_eol=True)
|
||||
self.to_cons_title(u'youtube-dl - %s of %s at %s ETA %s' %
|
||||
(percent_str.strip(), data_len_str.strip(), speed_str.strip(), eta_str.strip()))
|
||||
|
||||
@ -346,7 +397,13 @@ class FileDownloader(object):
|
||||
template_dict = dict(info_dict)
|
||||
|
||||
template_dict['epoch'] = int(time.time())
|
||||
template_dict['autonumber'] = u'%05d' % self._num_downloads
|
||||
autonumber_size = self.params.get('autonumber_size')
|
||||
if autonumber_size is None:
|
||||
autonumber_size = 5
|
||||
autonumber_templ = u'%0' + str(autonumber_size) + u'd'
|
||||
template_dict['autonumber'] = autonumber_templ % self._num_downloads
|
||||
if template_dict['playlist_index'] is not None:
|
||||
template_dict['playlist_index'] = u'%05d' % template_dict['playlist_index']
|
||||
|
||||
sanitize = lambda k,v: sanitize_filename(
|
||||
u'NA' if v is None else compat_str(v),
|
||||
@ -356,8 +413,11 @@ class FileDownloader(object):
|
||||
|
||||
filename = self.params['outtmpl'] % template_dict
|
||||
return filename
|
||||
except (ValueError, KeyError) as err:
|
||||
self.trouble(u'ERROR: invalid system charset or erroneous output template')
|
||||
except KeyError as err:
|
||||
self.report_error(u'Erroneous output template')
|
||||
return None
|
||||
except ValueError as err:
|
||||
self.report_error(u'Insufficient system charset ' + repr(preferredencoding()))
|
||||
return None
|
||||
|
||||
def _match_entry(self, info_dict):
|
||||
@ -366,18 +426,155 @@ class FileDownloader(object):
|
||||
title = info_dict['title']
|
||||
matchtitle = self.params.get('matchtitle', False)
|
||||
if matchtitle:
|
||||
matchtitle = matchtitle.decode('utf8')
|
||||
if not re.search(matchtitle, title, re.IGNORECASE):
|
||||
return u'[download] "' + title + '" title did not match pattern "' + matchtitle + '"'
|
||||
rejecttitle = self.params.get('rejecttitle', False)
|
||||
if rejecttitle:
|
||||
rejecttitle = rejecttitle.decode('utf8')
|
||||
if re.search(rejecttitle, title, re.IGNORECASE):
|
||||
return u'"' + title + '" title matched reject pattern "' + rejecttitle + '"'
|
||||
date = info_dict.get('upload_date', None)
|
||||
if date is not None:
|
||||
dateRange = self.params.get('daterange', DateRange())
|
||||
if date not in dateRange:
|
||||
return u'[download] %s upload date is not in range %s' % (date_from_str(date).isoformat(), dateRange)
|
||||
return None
|
||||
|
||||
def extract_info(self, url, download=True, ie_key=None, extra_info={}):
|
||||
'''
|
||||
Returns a list with a dictionary for each video we find.
|
||||
If 'download', also downloads the videos.
|
||||
extra_info is a dict containing the extra values to add to each result
|
||||
'''
|
||||
|
||||
if ie_key:
|
||||
ie = get_info_extractor(ie_key)()
|
||||
ie.set_downloader(self)
|
||||
ies = [ie]
|
||||
else:
|
||||
ies = self._ies
|
||||
|
||||
for ie in ies:
|
||||
if not ie.suitable(url):
|
||||
continue
|
||||
|
||||
if not ie.working():
|
||||
self.report_warning(u'The program functionality for this site has been marked as broken, '
|
||||
u'and will probably not work.')
|
||||
|
||||
try:
|
||||
ie_result = ie.extract(url)
|
||||
if ie_result is None: # Finished already (backwards compatibility; listformats and friends should be moved here)
|
||||
break
|
||||
if isinstance(ie_result, list):
|
||||
# Backwards compatibility: old IE result format
|
||||
for result in ie_result:
|
||||
result.update(extra_info)
|
||||
ie_result = {
|
||||
'_type': 'compat_list',
|
||||
'entries': ie_result,
|
||||
}
|
||||
else:
|
||||
ie_result.update(extra_info)
|
||||
if 'extractor' not in ie_result:
|
||||
ie_result['extractor'] = ie.IE_NAME
|
||||
return self.process_ie_result(ie_result, download=download)
|
||||
except ExtractorError as de: # An error we somewhat expected
|
||||
self.report_error(compat_str(de), de.format_traceback())
|
||||
break
|
||||
except Exception as e:
|
||||
if self.params.get('ignoreerrors', False):
|
||||
self.report_error(compat_str(e), tb=compat_str(traceback.format_exc()))
|
||||
break
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
self.report_error(u'no suitable InfoExtractor: %s' % url)
|
||||
|
||||
def process_ie_result(self, ie_result, download=True, extra_info={}):
|
||||
"""
|
||||
Take the result of the ie(may be modified) and resolve all unresolved
|
||||
references (URLs, playlist items).
|
||||
|
||||
It will also download the videos if 'download'.
|
||||
Returns the resolved ie_result.
|
||||
"""
|
||||
|
||||
result_type = ie_result.get('_type', 'video') # If not given we suppose it's a video, support the default old system
|
||||
if result_type == 'video':
|
||||
if 'playlist' not in ie_result:
|
||||
# It isn't part of a playlist
|
||||
ie_result['playlist'] = None
|
||||
ie_result['playlist_index'] = None
|
||||
if download:
|
||||
self.process_info(ie_result)
|
||||
return ie_result
|
||||
elif result_type == 'url':
|
||||
# We have to add extra_info to the results because it may be
|
||||
# contained in a playlist
|
||||
return self.extract_info(ie_result['url'],
|
||||
download,
|
||||
ie_key=ie_result.get('ie_key'),
|
||||
extra_info=extra_info)
|
||||
elif result_type == 'playlist':
|
||||
# We process each entry in the playlist
|
||||
playlist = ie_result.get('title', None) or ie_result.get('id', None)
|
||||
self.to_screen(u'[download] Downloading playlist: %s' % playlist)
|
||||
|
||||
playlist_results = []
|
||||
|
||||
n_all_entries = len(ie_result['entries'])
|
||||
playliststart = self.params.get('playliststart', 1) - 1
|
||||
playlistend = self.params.get('playlistend', -1)
|
||||
|
||||
if playlistend == -1:
|
||||
entries = ie_result['entries'][playliststart:]
|
||||
else:
|
||||
entries = ie_result['entries'][playliststart:playlistend]
|
||||
|
||||
n_entries = len(entries)
|
||||
|
||||
self.to_screen(u"[%s] playlist '%s': Collected %d video ids (downloading %d of them)" %
|
||||
(ie_result['extractor'], playlist, n_all_entries, n_entries))
|
||||
|
||||
for i,entry in enumerate(entries,1):
|
||||
self.to_screen(u'[download] Downloading video #%s of %s' %(i, n_entries))
|
||||
extra = {
|
||||
'playlist': playlist,
|
||||
'playlist_index': i + playliststart,
|
||||
}
|
||||
if not 'extractor' in entry:
|
||||
# We set the extractor, if it's an url it will be set then to
|
||||
# the new extractor, but if it's already a video we must make
|
||||
# sure it's present: see issue #877
|
||||
entry['extractor'] = ie_result['extractor']
|
||||
entry_result = self.process_ie_result(entry,
|
||||
download=download,
|
||||
extra_info=extra)
|
||||
playlist_results.append(entry_result)
|
||||
ie_result['entries'] = playlist_results
|
||||
return ie_result
|
||||
elif result_type == 'compat_list':
|
||||
def _fixup(r):
|
||||
r.setdefault('extractor', ie_result['extractor'])
|
||||
return r
|
||||
ie_result['entries'] = [
|
||||
self.process_ie_result(_fixup(r), download=download)
|
||||
for r in ie_result['entries']
|
||||
]
|
||||
return ie_result
|
||||
else:
|
||||
raise Exception('Invalid result type: %s' % result_type)
|
||||
|
||||
def process_info(self, info_dict):
|
||||
"""Process a single dictionary returned by an InfoExtractor."""
|
||||
"""Process a single resolved IE result."""
|
||||
|
||||
assert info_dict.get('_type', 'video') == 'video'
|
||||
#We increment the download the download count here to match the previous behaviour.
|
||||
self.increment_downloads()
|
||||
|
||||
info_dict['fulltitle'] = info_dict['title']
|
||||
if len(info_dict['title']) > 200:
|
||||
info_dict['title'] = info_dict['title'][:197] + u'...'
|
||||
|
||||
# Keep for backwards compatibility
|
||||
info_dict['stitle'] = info_dict['title']
|
||||
@ -400,6 +597,8 @@ class FileDownloader(object):
|
||||
# Forced printings
|
||||
if self.params.get('forcetitle', False):
|
||||
compat_print(info_dict['title'])
|
||||
if self.params.get('forceid', False):
|
||||
compat_print(info_dict['id'])
|
||||
if self.params.get('forceurl', False):
|
||||
compat_print(info_dict['url'])
|
||||
if self.params.get('forcethumbnail', False) and 'thumbnail' in info_dict:
|
||||
@ -420,10 +619,10 @@ class FileDownloader(object):
|
||||
|
||||
try:
|
||||
dn = os.path.dirname(encodeFilename(filename))
|
||||
if dn != '' and not os.path.exists(dn): # dn is already encoded
|
||||
if dn != '' and not os.path.exists(dn):
|
||||
os.makedirs(dn)
|
||||
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
|
||||
|
||||
if self.params.get('writedescription', False):
|
||||
@ -433,20 +632,43 @@ class FileDownloader(object):
|
||||
with io.open(encodeFilename(descfn), 'w', encoding='utf-8') as descfile:
|
||||
descfile.write(info_dict['description'])
|
||||
except (OSError, IOError):
|
||||
self.trouble(u'ERROR: Cannot write description file ' + descfn)
|
||||
self.report_error(u'Cannot write description file ' + descfn)
|
||||
return
|
||||
|
||||
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
|
||||
# that way it will silently go on when used with unsupporting IE
|
||||
try:
|
||||
srtfn = filename.rsplit('.', 1)[0] + u'.srt'
|
||||
self.report_writesubtitles(srtfn)
|
||||
with io.open(encodeFilename(srtfn), 'w', encoding='utf-8') as srtfile:
|
||||
srtfile.write(info_dict['subtitles'])
|
||||
except (OSError, IOError):
|
||||
self.trouble(u'ERROR: Cannot write subtitles file ' + descfn)
|
||||
return
|
||||
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:
|
||||
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):
|
||||
self.report_error(u'Cannot write subtitles file ' + descfn)
|
||||
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):
|
||||
self.report_error(u'Cannot write subtitles file ' + descfn)
|
||||
return
|
||||
|
||||
if self.params.get('writeinfojson', False):
|
||||
infofn = filename + u'.info.json'
|
||||
@ -455,9 +677,23 @@ class FileDownloader(object):
|
||||
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))
|
||||
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
|
||||
|
||||
if self.params.get('writethumbnail', False):
|
||||
if 'thumbnail' in info_dict:
|
||||
thumb_format = info_dict['thumbnail'].rpartition(u'/')[2].rpartition(u'.')[2]
|
||||
if not thumb_format:
|
||||
thumb_format = 'jpg'
|
||||
thumb_filename = filename.rpartition('.')[0] + u'.' + thumb_format
|
||||
self.to_screen(u'[%s] %s: Downloading thumbnail ...' %
|
||||
(info_dict['extractor'], info_dict['id']))
|
||||
uf = compat_urllib_request.urlopen(info_dict['thumbnail'])
|
||||
with open(thumb_filename, 'wb') as thumbf:
|
||||
shutil.copyfileobj(uf, thumbf)
|
||||
self.to_screen(u'[%s] %s: Writing thumbnail to: %s' %
|
||||
(info_dict['extractor'], info_dict['id'], thumb_filename))
|
||||
|
||||
if not self.params.get('skip_download', False):
|
||||
if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(filename)):
|
||||
success = True
|
||||
@ -467,17 +703,17 @@ class FileDownloader(object):
|
||||
except (OSError, IOError) as err:
|
||||
raise UnavailableVideoError()
|
||||
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
|
||||
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
|
||||
|
||||
if success:
|
||||
try:
|
||||
self.post_process(filename, info_dict)
|
||||
except (PostProcessingError) as err:
|
||||
self.trouble(u'ERROR: postprocessing: %s' % str(err))
|
||||
self.report_error(u'postprocessing: %s' % str(err))
|
||||
return
|
||||
|
||||
def download(self, url_list):
|
||||
@ -486,49 +722,14 @@ class FileDownloader(object):
|
||||
raise SameFileError(self.params['outtmpl'])
|
||||
|
||||
for url in url_list:
|
||||
suitable_found = False
|
||||
for ie in self._ies:
|
||||
# Go to next InfoExtractor if not suitable
|
||||
if not ie.suitable(url):
|
||||
continue
|
||||
|
||||
# Warn if the _WORKING attribute is False
|
||||
if not ie.working():
|
||||
self.to_stderr(u'WARNING: 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.')
|
||||
|
||||
# Suitable InfoExtractor found
|
||||
suitable_found = True
|
||||
|
||||
# Extract information from URL and process it
|
||||
try:
|
||||
videos = ie.extract(url)
|
||||
except ExtractorError as de: # An error we somewhat expected
|
||||
self.trouble(u'ERROR: ' + compat_str(de), de.format_traceback())
|
||||
break
|
||||
except Exception as e:
|
||||
if self.params.get('ignoreerrors', False):
|
||||
self.trouble(u'ERROR: ' + compat_str(e), tb=compat_str(traceback.format_exc()))
|
||||
break
|
||||
else:
|
||||
raise
|
||||
|
||||
if len(videos or []) > 1 and self.fixed_template():
|
||||
raise SameFileError(self.params['outtmpl'])
|
||||
|
||||
for video in videos or []:
|
||||
video['extractor'] = ie.IE_NAME
|
||||
try:
|
||||
self.increment_downloads()
|
||||
self.process_info(video)
|
||||
except UnavailableVideoError:
|
||||
self.trouble(u'\nERROR: unable to download video')
|
||||
|
||||
# Suitable InfoExtractor had been found; go to next URL
|
||||
break
|
||||
|
||||
if not suitable_found:
|
||||
self.trouble(u'ERROR: no suitable InfoExtractor: %s' % url)
|
||||
try:
|
||||
#It also downloads the videos
|
||||
videos = self.extract_info(url)
|
||||
except UnavailableVideoError:
|
||||
self.report_error(u'unable to download video')
|
||||
except MaxDownloadsReached:
|
||||
self.to_screen(u'[info] Maximum number of downloaded files reached.')
|
||||
raise
|
||||
|
||||
return self._download_retcode
|
||||
|
||||
@ -550,31 +751,36 @@ class FileDownloader(object):
|
||||
self.to_stderr(u'ERROR: ' + e.msg)
|
||||
if keep_video is False and not self.params.get('keepvideo', False):
|
||||
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))
|
||||
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, play_path, tc_url):
|
||||
self.report_destination(filename)
|
||||
tmpfilename = self.temp_name(filename)
|
||||
|
||||
# Check for rtmpdump first
|
||||
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):
|
||||
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
|
||||
verbosity_option = '--verbose' if self.params.get('verbose', False) else '--quiet'
|
||||
|
||||
# Download using rtmpdump. rtmpdump returns exit code 2 when
|
||||
# the connection was interrumpted and resuming appears to be
|
||||
# possible. This is part of rtmpdump's normal usage, AFAIK.
|
||||
basic_args = ['rtmpdump', '-q', '-r', url, '-o', tmpfilename]
|
||||
basic_args = ['rtmpdump', verbosity_option, '-r', url, '-o', tmpfilename]
|
||||
if player_url is not None:
|
||||
basic_args += ['-W', player_url]
|
||||
basic_args += ['--swfVfy', player_url]
|
||||
if page_url is not None:
|
||||
basic_args += ['--pageUrl', page_url]
|
||||
args = basic_args + [[], ['-e', '-k', '1']][self.params.get('continuedl', False)]
|
||||
if play_path is not None:
|
||||
basic_args += ['--playpath', play_path]
|
||||
if tc_url is not None:
|
||||
basic_args += ['--tcUrl', url]
|
||||
args = basic_args + [[], ['--resume', '--skip', '1']][self.params.get('continuedl', False)]
|
||||
if self.params.get('verbose', False):
|
||||
try:
|
||||
import pipes
|
||||
@ -608,9 +814,41 @@ class FileDownloader(object):
|
||||
})
|
||||
return True
|
||||
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
|
||||
|
||||
def _download_with_mplayer(self, filename, url):
|
||||
self.report_destination(filename)
|
||||
tmpfilename = self.temp_name(filename)
|
||||
|
||||
args = ['mplayer', '-really-quiet', '-vo', 'null', '-vc', 'dummy', '-dumpstream', '-dumpfile', tmpfilename, url]
|
||||
# Check for mplayer first
|
||||
try:
|
||||
subprocess.call(['mplayer', '-h'], stdout=(open(os.path.devnull, 'w')), stderr=subprocess.STDOUT)
|
||||
except (OSError, IOError):
|
||||
self.report_error(u'MMS or RTSP download detected but "%s" could not be run' % args[0] )
|
||||
return False
|
||||
|
||||
# Download using mplayer.
|
||||
retval = subprocess.call(args)
|
||||
if retval == 0:
|
||||
fsize = os.path.getsize(encodeFilename(tmpfilename))
|
||||
self.to_screen(u'\r[%s] %s bytes' % (args[0], fsize))
|
||||
self.try_rename(tmpfilename, filename)
|
||||
self._hook_progress({
|
||||
'downloaded_bytes': fsize,
|
||||
'total_bytes': fsize,
|
||||
'filename': filename,
|
||||
'status': 'finished',
|
||||
})
|
||||
return True
|
||||
else:
|
||||
self.to_stderr(u"\n")
|
||||
self.report_error(u'mplayer exited with code %d' % retval)
|
||||
return False
|
||||
|
||||
|
||||
def _do_download(self, filename, info_dict):
|
||||
url = info_dict['url']
|
||||
|
||||
@ -627,7 +865,13 @@ class FileDownloader(object):
|
||||
if url.startswith('rtmp'):
|
||||
return self._download_with_rtmpdump(filename, url,
|
||||
info_dict.get('player_url', None),
|
||||
info_dict.get('page_url', None))
|
||||
info_dict.get('page_url', None),
|
||||
info_dict.get('play_path', None),
|
||||
info_dict.get('tc_url', None))
|
||||
|
||||
# Attempt to download using mplayer
|
||||
if url.startswith('mms') or url.startswith('rtsp'):
|
||||
return self._download_with_mplayer(filename, url)
|
||||
|
||||
tmpfilename = self.temp_name(filename)
|
||||
stream = None
|
||||
@ -708,7 +952,7 @@ class FileDownloader(object):
|
||||
self.report_retry(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
|
||||
|
||||
data_len = data.info().get('Content-length', None)
|
||||
@ -744,12 +988,13 @@ class FileDownloader(object):
|
||||
filename = self.undo_temp_name(tmpfilename)
|
||||
self.report_destination(filename)
|
||||
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
|
||||
try:
|
||||
stream.write(data_block)
|
||||
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
|
||||
if not self.params.get('noresizebuffer', False):
|
||||
block_size = self.best_block_size(after - before, len(data_block))
|
||||
@ -775,7 +1020,8 @@ class FileDownloader(object):
|
||||
self.slow_down(start, byte_counter - resume_len)
|
||||
|
||||
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
|
||||
stream.close()
|
||||
self.report_finish()
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -85,8 +85,9 @@ class FFmpegPostProcessor(PostProcessor):
|
||||
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
stdout,stderr = p.communicate()
|
||||
if p.returncode != 0:
|
||||
stderr = stderr.decode('utf-8', 'replace')
|
||||
msg = stderr.strip().split('\n')[-1]
|
||||
raise FFmpegPostProcessorError(msg.decode('utf-8', 'replace'))
|
||||
raise FFmpegPostProcessorError(msg)
|
||||
|
||||
def _ffmpeg_filename_argument(self, fn):
|
||||
# ffmpeg broke --, see https://ffmpeg.org/trac/ffmpeg/ticket/2127 for details
|
||||
@ -188,6 +189,11 @@ class FFmpegExtractAudioPP(FFmpegPostProcessor):
|
||||
|
||||
prefix, sep, ext = path.rpartition(u'.') # not os.path.splitext, since the latter does not work on unicode in all setups
|
||||
new_path = prefix + sep + extension
|
||||
|
||||
# If we download foo.mp3 and convert it to... foo.mp3, then don't delete foo.mp3, silly.
|
||||
if new_path == path:
|
||||
self._nopostoverwrites = True
|
||||
|
||||
try:
|
||||
if self._nopostoverwrites and os.path.exists(encodeFilename(new_path)):
|
||||
self._downloader.to_screen(u'[youtube] Post-process file %s exists, skipping' % new_path)
|
||||
@ -210,7 +216,7 @@ class FFmpegExtractAudioPP(FFmpegPostProcessor):
|
||||
self._downloader.to_stderr(u'WARNING: Cannot update utime of audio file')
|
||||
|
||||
information['filepath'] = new_path
|
||||
return False,information
|
||||
return self._nopostoverwrites,information
|
||||
|
||||
class FFmpegVideoConvertor(FFmpegPostProcessor):
|
||||
def __init__(self, downloader=None,preferedformat=None):
|
||||
|
@ -23,10 +23,16 @@ __authors__ = (
|
||||
'Dave Vasilevsky',
|
||||
'Jaime Marquínez Ferrándiz',
|
||||
'Jeff Crouse',
|
||||
'Osama Khalid',
|
||||
'Michael Walter',
|
||||
'M. Yasoob Ullah Khalid',
|
||||
'Julien Fraichard',
|
||||
'Johny Mo Swag',
|
||||
)
|
||||
|
||||
__license__ = 'Public Domain'
|
||||
|
||||
import codecs
|
||||
import getpass
|
||||
import optparse
|
||||
import os
|
||||
@ -45,7 +51,7 @@ from .FileDownloader import *
|
||||
from .InfoExtractors import gen_extractors
|
||||
from .PostProcessor import *
|
||||
|
||||
def parseOpts():
|
||||
def parseOpts(overrideArguments=None):
|
||||
def _readOptions(filename_bytes):
|
||||
try:
|
||||
optionf = open(filename_bytes)
|
||||
@ -125,7 +131,7 @@ def parseOpts():
|
||||
general.add_option('-i', '--ignore-errors',
|
||||
action='store_true', dest='ignoreerrors', help='continue on download errors', default=False)
|
||||
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',
|
||||
dest='retries', metavar='RETRIES', help='number of retries (default is %default)', default=10)
|
||||
general.add_option('--buffer-size',
|
||||
@ -138,9 +144,14 @@ def parseOpts():
|
||||
help='display the current browser identification', default=False)
|
||||
general.add_option('--user-agent',
|
||||
dest='user_agent', help='specify a custom user agent', metavar='UA')
|
||||
general.add_option('--referer',
|
||||
dest='referer', help='specify a custom referer, use if the video access is restricted to one domain',
|
||||
metavar='REF', default=None)
|
||||
general.add_option('--list-extractors',
|
||||
action='store_true', dest='list_extractors',
|
||||
help='List all supported extractors and the URLs they would handle', default=False)
|
||||
general.add_option('--proxy', dest='proxy', default=None, help='Use the specified HTTP/HTTPS proxy', metavar='URL')
|
||||
general.add_option('--no-check-certificate', action='store_true', dest='no_check_certificate', default=False, help='Suppress HTTPS certificate validation.')
|
||||
general.add_option('--test', action='store_true', dest='test', default=False, help=optparse.SUPPRESS_HELP)
|
||||
|
||||
selection.add_option('--playlist-start',
|
||||
@ -152,6 +163,9 @@ def parseOpts():
|
||||
selection.add_option('--max-downloads', metavar='NUMBER', dest='max_downloads', help='Abort after downloading NUMBER files', default=None)
|
||||
selection.add_option('--min-filesize', metavar='SIZE', dest='min_filesize', help="Do not download any videos smaller than SIZE (e.g. 50k or 44.6m)", default=None)
|
||||
selection.add_option('--max-filesize', metavar='SIZE', dest='max_filesize', help="Do not download any videos larger than SIZE (e.g. 50k or 44.6m)", default=None)
|
||||
selection.add_option('--date', metavar='DATE', dest='date', help='download only videos uploaded in this date', default=None)
|
||||
selection.add_option('--datebefore', metavar='DATE', dest='datebefore', help='download only videos uploaded before this date', default=None)
|
||||
selection.add_option('--dateafter', metavar='DATE', dest='dateafter', help='download only videos uploaded after this date', default=None)
|
||||
|
||||
|
||||
authentication.add_option('-u', '--username',
|
||||
@ -163,7 +177,8 @@ def parseOpts():
|
||||
|
||||
|
||||
video_format.add_option('-f', '--format',
|
||||
action='store', dest='format', metavar='FORMAT', help='video format code')
|
||||
action='store', dest='format', metavar='FORMAT',
|
||||
help='video format code, specifiy the order of preference using slashes: "-f 22/17/18"')
|
||||
video_format.add_option('--all-formats',
|
||||
action='store_const', dest='format', help='download all available video formats', const='all')
|
||||
video_format.add_option('--prefer-free-formats',
|
||||
@ -172,12 +187,24 @@ def parseOpts():
|
||||
action='store', dest='format_limit', metavar='FORMAT', help='highest quality format to download')
|
||||
video_format.add_option('-F', '--list-formats',
|
||||
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',
|
||||
help='write video closed captions to a .srt file (currently youtube only)', default=False)
|
||||
video_format.add_option('--srt-lang',
|
||||
help='write subtitle file (currently youtube only)', default=False)
|
||||
video_format.add_option('--only-sub',
|
||||
action='store_true', dest='skip_download',
|
||||
help='[deprecated] alias of --skip-download', 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',
|
||||
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',
|
||||
action='store_true', dest='quiet', help='activates quiet mode', default=False)
|
||||
@ -189,6 +216,8 @@ def parseOpts():
|
||||
action='store_true', dest='geturl', help='simulate, quiet but print URL', default=False)
|
||||
verbosity.add_option('-e', '--get-title',
|
||||
action='store_true', dest='gettitle', help='simulate, quiet but print title', default=False)
|
||||
verbosity.add_option('--get-id',
|
||||
action='store_true', dest='getid', help='simulate, quiet but print id', default=False)
|
||||
verbosity.add_option('--get-thumbnail',
|
||||
action='store_true', dest='getthumbnail',
|
||||
help='simulate, quiet but print thumbnail URL', default=False)
|
||||
@ -201,6 +230,8 @@ def parseOpts():
|
||||
verbosity.add_option('--get-format',
|
||||
action='store_true', dest='getformat',
|
||||
help='simulate, quiet but print output format', default=False)
|
||||
verbosity.add_option('--newline',
|
||||
action='store_true', dest='progress_with_newline', help='output progress bar as new lines', default=False)
|
||||
verbosity.add_option('--no-progress',
|
||||
action='store_true', dest='noprogress', help='do not print progress bar', default=False)
|
||||
verbosity.add_option('--console-title',
|
||||
@ -208,19 +239,33 @@ def parseOpts():
|
||||
help='display progress in console titlebar', default=False)
|
||||
verbosity.add_option('-v', '--verbose',
|
||||
action='store_true', dest='verbose', help='print various debugging information', default=False)
|
||||
|
||||
verbosity.add_option('--dump-intermediate-pages',
|
||||
action='store_true', dest='dump_intermediate_pages', default=False,
|
||||
help='print downloaded pages to debug problems(very verbose)')
|
||||
|
||||
filesystem.add_option('-t', '--title',
|
||||
action='store_true', dest='usetitle', help='use title in file name', default=False)
|
||||
action='store_true', dest='usetitle', help='use title in file name (default)', default=False)
|
||||
filesystem.add_option('--id',
|
||||
action='store_true', dest='useid', help='use video ID in file name', default=False)
|
||||
action='store_true', dest='useid', help='use only video ID in file name', default=False)
|
||||
filesystem.add_option('-l', '--literal',
|
||||
action='store_true', dest='usetitle', help='[deprecated] alias of --title', default=False)
|
||||
filesystem.add_option('-A', '--auto-number',
|
||||
action='store_true', dest='autonumber',
|
||||
help='number downloaded files starting from 00000', default=False)
|
||||
filesystem.add_option('-o', '--output',
|
||||
dest='outtmpl', metavar='TEMPLATE', help='output filename template. Use %(title)s to get the title, %(uploader)s for the uploader name, %(uploader_id)s for the uploader nickname if different, %(autonumber)s to get an automatically incremented number, %(ext)s for the filename extension, %(upload_date)s for the upload date (YYYYMMDD), %(extractor)s for the provider (youtube, metacafe, etc), %(id)s for the video id and %% for a literal percent. Use - to output to stdout. Can also be used to download to a different directory, for example with -o \'/my/downloads/%(uploader)s/%(title)s-%(id)s.%(ext)s\' .')
|
||||
dest='outtmpl', metavar='TEMPLATE',
|
||||
help=('output filename template. Use %(title)s to get the title, '
|
||||
'%(uploader)s for the uploader name, %(uploader_id)s for the uploader nickname if different, '
|
||||
'%(autonumber)s to get an automatically incremented number, '
|
||||
'%(ext)s for the filename extension, %(upload_date)s for the upload date (YYYYMMDD), '
|
||||
'%(extractor)s for the provider (youtube, metacafe, etc), '
|
||||
'%(id)s for the video id , %(playlist)s for the playlist the video is in, '
|
||||
'%(playlist_index)s for the position in the playlist and %% for a literal percent. '
|
||||
'Use - to output to stdout. Can also be used to download to a different directory, '
|
||||
'for example with -o \'/my/downloads/%(uploader)s/%(title)s-%(id)s.%(ext)s\' .'))
|
||||
filesystem.add_option('--autonumber-size',
|
||||
dest='autonumber_size', metavar='NUMBER',
|
||||
help='Specifies the number of digits in %(autonumber)s when it is present in output filename template or --autonumber option is given')
|
||||
filesystem.add_option('--restrict-filenames',
|
||||
action='store_true', dest='restrictfilenames',
|
||||
help='Restrict filenames to only ASCII characters, and avoid "&" and spaces in filenames', default=False)
|
||||
@ -246,6 +291,9 @@ def parseOpts():
|
||||
filesystem.add_option('--write-info-json',
|
||||
action='store_true', dest='writeinfojson',
|
||||
help='write video metadata to a .info.json file', default=False)
|
||||
filesystem.add_option('--write-thumbnail',
|
||||
action='store_true', dest='writethumbnail',
|
||||
help='write thumbnail image to disk', default=False)
|
||||
|
||||
|
||||
postproc.add_option('-x', '--extract-audio', action='store_true', dest='extractaudio', default=False,
|
||||
@ -270,18 +318,35 @@ def parseOpts():
|
||||
parser.add_option_group(authentication)
|
||||
parser.add_option_group(postproc)
|
||||
|
||||
xdg_config_home = os.environ.get('XDG_CONFIG_HOME')
|
||||
if xdg_config_home:
|
||||
userConf = os.path.join(xdg_config_home, 'youtube-dl.conf')
|
||||
if overrideArguments is not None:
|
||||
opts, args = parser.parse_args(overrideArguments)
|
||||
if opts.verbose:
|
||||
print(u'[debug] Override config: ' + repr(overrideArguments))
|
||||
else:
|
||||
userConf = os.path.join(os.path.expanduser('~'), '.config', 'youtube-dl.conf')
|
||||
argv = _readOptions('/etc/youtube-dl.conf') + _readOptions(userConf) + sys.argv[1:]
|
||||
opts, args = parser.parse_args(argv)
|
||||
xdg_config_home = os.environ.get('XDG_CONFIG_HOME')
|
||||
if xdg_config_home:
|
||||
userConfFile = os.path.join(xdg_config_home, 'youtube-dl.conf')
|
||||
else:
|
||||
userConfFile = os.path.join(os.path.expanduser('~'), '.config', 'youtube-dl.conf')
|
||||
systemConf = _readOptions('/etc/youtube-dl.conf')
|
||||
userConf = _readOptions(userConfFile)
|
||||
commandLineConf = sys.argv[1:]
|
||||
argv = systemConf + userConf + commandLineConf
|
||||
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
|
||||
|
||||
def _real_main():
|
||||
parser, opts, args = parseOpts()
|
||||
def _real_main(argv=None):
|
||||
# Compatibility fixes for Windows
|
||||
if sys.platform == 'win32':
|
||||
# https://github.com/rg3/youtube-dl/issues/820
|
||||
codecs.register(lambda name: codecs.lookup('utf-8') if name == 'cp65001' else None)
|
||||
|
||||
parser, opts, args = parseOpts(argv)
|
||||
|
||||
# Open appropriate CookieJar
|
||||
if opts.cookiefile is None:
|
||||
@ -299,6 +364,10 @@ def _real_main():
|
||||
# Set user agent
|
||||
if opts.user_agent is not None:
|
||||
std_headers['User-Agent'] = opts.user_agent
|
||||
|
||||
# Set referer
|
||||
if opts.referer is not None:
|
||||
std_headers['Referer'] = opts.referer
|
||||
|
||||
# Dump user agent
|
||||
if opts.dump_user_agent:
|
||||
@ -323,8 +392,19 @@ def _real_main():
|
||||
|
||||
# General configuration
|
||||
cookie_processor = compat_urllib_request.HTTPCookieProcessor(jar)
|
||||
proxy_handler = compat_urllib_request.ProxyHandler()
|
||||
opener = compat_urllib_request.build_opener(proxy_handler, cookie_processor, YoutubeDLHandler())
|
||||
if opts.proxy is not None:
|
||||
if opts.proxy == '':
|
||||
proxies = {}
|
||||
else:
|
||||
proxies = {'http': opts.proxy, 'https': opts.proxy}
|
||||
else:
|
||||
proxies = compat_urllib_request.getproxies()
|
||||
# Set HTTPS proxy to HTTP one if given (https://github.com/rg3/youtube-dl/issues/805)
|
||||
if 'http' in proxies and 'https' not in proxies:
|
||||
proxies['https'] = proxies['http']
|
||||
proxy_handler = compat_urllib_request.ProxyHandler(proxies)
|
||||
https_handler = make_HTTPS_handler(opts)
|
||||
opener = compat_urllib_request.build_opener(https_handler, proxy_handler, cookie_processor, YoutubeDLHandler())
|
||||
compat_urllib_request.install_opener(opener)
|
||||
socket.setdefaulttimeout(300) # 5 minutes should be enough (famous last words)
|
||||
|
||||
@ -397,6 +477,10 @@ def _real_main():
|
||||
if opts.recodevideo is not None:
|
||||
if opts.recodevideo not in ['mp4', 'flv', 'webm', 'ogg']:
|
||||
parser.error(u'invalid video recode format specified')
|
||||
if opts.date is not None:
|
||||
date = DateRange.day(opts.date)
|
||||
else:
|
||||
date = DateRange(opts.dateafter, opts.datebefore)
|
||||
|
||||
if sys.version_info < (3,):
|
||||
# In Python 2, sys.argv is a bytestring (also note http://bugs.python.org/issue2128 for Windows systems)
|
||||
@ -409,25 +493,28 @@ def _real_main():
|
||||
or (opts.usetitle and u'%(title)s-%(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 u'%(id)s.%(ext)s')
|
||||
or u'%(title)s-%(id)s.%(ext)s')
|
||||
|
||||
# File downloader
|
||||
fd = FileDownloader({
|
||||
'usenetrc': opts.usenetrc,
|
||||
'username': opts.username,
|
||||
'password': opts.password,
|
||||
'quiet': (opts.quiet or opts.geturl or opts.gettitle or opts.getthumbnail or opts.getdescription or opts.getfilename or opts.getformat),
|
||||
'quiet': (opts.quiet or opts.geturl or opts.gettitle or opts.getid or opts.getthumbnail or opts.getdescription or opts.getfilename or opts.getformat),
|
||||
'forceurl': opts.geturl,
|
||||
'forcetitle': opts.gettitle,
|
||||
'forceid': opts.getid,
|
||||
'forcethumbnail': opts.getthumbnail,
|
||||
'forcedescription': opts.getdescription,
|
||||
'forcefilename': opts.getfilename,
|
||||
'forceformat': opts.getformat,
|
||||
'simulate': opts.simulate,
|
||||
'skip_download': (opts.skip_download or opts.simulate or opts.geturl or opts.gettitle or opts.getthumbnail or opts.getdescription or opts.getfilename or opts.getformat),
|
||||
'skip_download': (opts.skip_download or opts.simulate or opts.geturl or opts.gettitle or opts.getid or opts.getthumbnail or opts.getdescription or opts.getfilename or opts.getformat),
|
||||
'format': opts.format,
|
||||
'format_limit': opts.format_limit,
|
||||
'listformats': opts.listformats,
|
||||
'outtmpl': outtmpl,
|
||||
'autonumber_size': opts.autonumber_size,
|
||||
'restrictfilenames': opts.restrictfilenames,
|
||||
'ignoreerrors': opts.ignoreerrors,
|
||||
'ratelimit': opts.ratelimit,
|
||||
@ -437,6 +524,7 @@ def _real_main():
|
||||
'noresizebuffer': opts.noresizebuffer,
|
||||
'continuedl': opts.continue_dl,
|
||||
'noprogress': opts.noprogress,
|
||||
'progress_with_newline': opts.progress_with_newline,
|
||||
'playliststart': opts.playliststart,
|
||||
'playlistend': opts.playlistend,
|
||||
'logtostderr': opts.outtmpl == '-',
|
||||
@ -445,17 +533,23 @@ def _real_main():
|
||||
'updatetime': opts.updatetime,
|
||||
'writedescription': opts.writedescription,
|
||||
'writeinfojson': opts.writeinfojson,
|
||||
'writethumbnail': opts.writethumbnail,
|
||||
'writesubtitles': opts.writesubtitles,
|
||||
'allsubtitles': opts.allsubtitles,
|
||||
'listsubtitles': opts.listsubtitles,
|
||||
'subtitlesformat': opts.subtitlesformat,
|
||||
'subtitleslang': opts.subtitleslang,
|
||||
'matchtitle': opts.matchtitle,
|
||||
'rejecttitle': opts.rejecttitle,
|
||||
'matchtitle': decodeOption(opts.matchtitle),
|
||||
'rejecttitle': decodeOption(opts.rejecttitle),
|
||||
'max_downloads': opts.max_downloads,
|
||||
'prefer_free_formats': opts.prefer_free_formats,
|
||||
'verbose': opts.verbose,
|
||||
'dump_intermediate_pages': opts.dump_intermediate_pages,
|
||||
'test': opts.test,
|
||||
'keepvideo': opts.keepvideo,
|
||||
'min_filesize': opts.min_filesize,
|
||||
'max_filesize': opts.max_filesize
|
||||
'max_filesize': opts.max_filesize,
|
||||
'daterange': date,
|
||||
})
|
||||
|
||||
if opts.verbose:
|
||||
@ -507,9 +601,9 @@ def _real_main():
|
||||
|
||||
sys.exit(retcode)
|
||||
|
||||
def main():
|
||||
def main(argv=None):
|
||||
try:
|
||||
_real_main()
|
||||
_real_main(argv)
|
||||
except DownloadError:
|
||||
sys.exit(1)
|
||||
except SameFileError:
|
||||
|
@ -9,7 +9,8 @@ import sys
|
||||
if __package__ is None and not hasattr(sys, "frozen"):
|
||||
# direct call of __main__.py
|
||||
import os.path
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
path = os.path.realpath(os.path.abspath(__file__))
|
||||
sys.path.append(os.path.dirname(os.path.dirname(path)))
|
||||
|
||||
import youtube_dl
|
||||
|
||||
|
@ -37,7 +37,7 @@ def rsa_verify(message, signature, key):
|
||||
def update_self(to_screen, verbose, filename):
|
||||
"""Update the program file with the latest version from the repository"""
|
||||
|
||||
UPDATE_URL = "http://rg3.github.com/youtube-dl/update/"
|
||||
UPDATE_URL = "http://rg3.github.io/youtube-dl/update/"
|
||||
VERSION_URL = UPDATE_URL + 'LATEST_VERSION'
|
||||
JSON_URL = UPDATE_URL + 'versions.json'
|
||||
UPDATES_RSA_KEY = (0x9d60ee4d8f805312fdb15a62f87b95bd66177b91df176765d13514a0f1754bcd2057295c5b6f1d35daa6742c3ffc9a82d3e118861c207995a8031e151d863c9927e304576bc80692bc8e094896fcf11b66f3e29e04e3a71e9a11558558acea1840aec37fc396fb6b65dc81a1c4144e03bd1c011de62e3f1357b327d08426fe93, 65537)
|
||||
@ -77,10 +77,8 @@ def update_self(to_screen, verbose, filename):
|
||||
|
||||
to_screen(u'Updating to version ' + versions_info['latest'] + '...')
|
||||
version = versions_info['versions'][versions_info['latest']]
|
||||
if version.get('notes'):
|
||||
to_screen(u'PLEASE NOTE:')
|
||||
for note in version['notes']:
|
||||
to_screen(note)
|
||||
|
||||
print_notes(to_screen, versions_info['versions'])
|
||||
|
||||
if not os.access(filename, os.W_OK):
|
||||
to_screen(u'ERROR: no write permissions on %s' % filename)
|
||||
@ -158,3 +156,17 @@ del "%s"
|
||||
return
|
||||
|
||||
to_screen(u'Updated youtube-dl. Restart youtube-dl to use the new version.')
|
||||
|
||||
def get_notes(versions, fromVersion):
|
||||
notes = []
|
||||
for v,vdata in sorted(versions.items()):
|
||||
if v > fromVersion:
|
||||
notes.extend(vdata.get('notes', []))
|
||||
return notes
|
||||
|
||||
def print_notes(to_screen, versions, fromVersion=__version__):
|
||||
notes = get_notes(versions, fromVersion)
|
||||
if notes:
|
||||
to_screen(u'PLEASE NOTE:')
|
||||
for note in notes:
|
||||
to_screen(note)
|
||||
|
@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import errno
|
||||
import gzip
|
||||
import io
|
||||
import json
|
||||
@ -11,7 +12,8 @@ import sys
|
||||
import traceback
|
||||
import zlib
|
||||
import email.utils
|
||||
import json
|
||||
import socket
|
||||
import datetime
|
||||
|
||||
try:
|
||||
import urllib.request as compat_urllib_request
|
||||
@ -148,6 +150,13 @@ try:
|
||||
except NameError:
|
||||
compat_chr = chr
|
||||
|
||||
def compat_ord(c):
|
||||
if type(c) is int: return c
|
||||
else: return ord(c)
|
||||
|
||||
# This is not clearly defined otherwise
|
||||
compiled_regex_type = type(re.compile(''))
|
||||
|
||||
std_headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:10.0) Gecko/20100101 Firefox/10.0',
|
||||
'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
|
||||
@ -311,7 +320,7 @@ def clean_html(html):
|
||||
html = re.sub('<.*?>', '', html)
|
||||
# Replace html entities
|
||||
html = unescapeHTML(html)
|
||||
return html
|
||||
return html.strip()
|
||||
|
||||
|
||||
def sanitize_open(filename, open_mode):
|
||||
@ -329,16 +338,24 @@ def sanitize_open(filename, open_mode):
|
||||
if sys.platform == 'win32':
|
||||
import msvcrt
|
||||
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)
|
||||
return (stream, filename)
|
||||
except (IOError, OSError) as err:
|
||||
# In case of error, try to remove win32 forbidden chars
|
||||
filename = re.sub(u'[/<>:"\\|\\\\?\\*]', u'#', filename)
|
||||
if err.errno in (errno.EACCES,):
|
||||
raise
|
||||
|
||||
# An exception here should be caught in the caller
|
||||
stream = open(encodeFilename(filename), open_mode)
|
||||
return (stream, filename)
|
||||
# In case of error, try to remove win32 forbidden chars
|
||||
alt_filename = os.path.join(
|
||||
re.sub(u'[/<>:"\\|\\\\?\\*]', u'#', path_part)
|
||||
for path_part in os.path.split(filename)
|
||||
)
|
||||
if alt_filename == filename:
|
||||
raise
|
||||
else:
|
||||
# An exception here should be caught in the caller
|
||||
stream = open(encodeFilename(filename), open_mode)
|
||||
return (stream, alt_filename)
|
||||
|
||||
|
||||
def timeconvert(timestr):
|
||||
@ -420,13 +437,48 @@ def encodeFilename(s):
|
||||
encoding = 'utf-8'
|
||||
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
|
||||
|
||||
def formatSeconds(secs):
|
||||
if secs > 3600:
|
||||
return '%d:%02d:%02d' % (secs // 3600, (secs % 3600) // 60, secs % 60)
|
||||
elif secs > 60:
|
||||
return '%d:%02d' % (secs // 60, secs % 60)
|
||||
else:
|
||||
return '%d' % secs
|
||||
|
||||
def make_HTTPS_handler(opts):
|
||||
if sys.version_info < (3,2):
|
||||
# Python's 2.x handler is very simplistic
|
||||
return compat_urllib_request.HTTPSHandler()
|
||||
else:
|
||||
import ssl
|
||||
context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
|
||||
context.set_default_verify_paths()
|
||||
|
||||
context.verify_mode = (ssl.CERT_NONE
|
||||
if opts.no_check_certificate
|
||||
else ssl.CERT_REQUIRED)
|
||||
return compat_urllib_request.HTTPSHandler(context=context)
|
||||
|
||||
class ExtractorError(Exception):
|
||||
"""Error during info extraction."""
|
||||
def __init__(self, msg, tb=None):
|
||||
""" tb, if given, is the original traceback (so that it can be printed out). """
|
||||
|
||||
if not sys.exc_info()[0] in (compat_urllib_error.URLError, socket.timeout, UnavailableVideoError):
|
||||
msg = msg + u'; please report this issue on GitHub.'
|
||||
super(ExtractorError, self).__init__(msg)
|
||||
|
||||
self.traceback = tb
|
||||
self.exc_info = sys.exc_info() # preserve original exception
|
||||
|
||||
def format_traceback(self):
|
||||
if self.traceback is None:
|
||||
@ -441,7 +493,10 @@ class DownloadError(Exception):
|
||||
configured to continue on errors. They will contain the appropriate
|
||||
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):
|
||||
@ -556,3 +611,70 @@ class YoutubeDLHandler(compat_urllib_request.HTTPHandler):
|
||||
|
||||
https_request = http_request
|
||||
https_response = http_response
|
||||
|
||||
def unified_strdate(date_str):
|
||||
"""Return a string with the date in the format YYYYMMDD"""
|
||||
upload_date = None
|
||||
#Replace commas
|
||||
date_str = date_str.replace(',',' ')
|
||||
# %z (UTC offset) is only supported in python>=3.2
|
||||
date_str = re.sub(r' (\+|-)[\d]*$', '', date_str)
|
||||
format_expressions = ['%d %B %Y', '%B %d %Y', '%b %d %Y', '%Y-%m-%d', '%d/%m/%Y', '%Y/%m/%d %H:%M:%S']
|
||||
for expression in format_expressions:
|
||||
try:
|
||||
upload_date = datetime.datetime.strptime(date_str, expression).strftime('%Y%m%d')
|
||||
except:
|
||||
pass
|
||||
return upload_date
|
||||
|
||||
def date_from_str(date_str):
|
||||
"""
|
||||
Return a datetime object from a string in the format YYYYMMDD or
|
||||
(now|today)[+-][0-9](day|week|month|year)(s)?"""
|
||||
today = datetime.date.today()
|
||||
if date_str == 'now'or date_str == 'today':
|
||||
return today
|
||||
match = re.match('(now|today)(?P<sign>[+-])(?P<time>\d+)(?P<unit>day|week|month|year)(s)?', date_str)
|
||||
if match is not None:
|
||||
sign = match.group('sign')
|
||||
time = int(match.group('time'))
|
||||
if sign == '-':
|
||||
time = -time
|
||||
unit = match.group('unit')
|
||||
#A bad aproximation?
|
||||
if unit == 'month':
|
||||
unit = 'day'
|
||||
time *= 30
|
||||
elif unit == 'year':
|
||||
unit = 'day'
|
||||
time *= 365
|
||||
unit += 's'
|
||||
delta = datetime.timedelta(**{unit: time})
|
||||
return today + delta
|
||||
return datetime.datetime.strptime(date_str, "%Y%m%d").date()
|
||||
|
||||
class DateRange(object):
|
||||
"""Represents a time interval between two dates"""
|
||||
def __init__(self, start=None, end=None):
|
||||
"""start and end must be strings in the format accepted by date"""
|
||||
if start is not None:
|
||||
self.start = date_from_str(start)
|
||||
else:
|
||||
self.start = datetime.datetime.min.date()
|
||||
if end is not None:
|
||||
self.end = date_from_str(end)
|
||||
else:
|
||||
self.end = datetime.datetime.max.date()
|
||||
if self.start > self.end:
|
||||
raise ValueError('Date range: "%s" , the start date must be before the end date' % self)
|
||||
@classmethod
|
||||
def day(cls, day):
|
||||
"""Returns a range that only contains the given day"""
|
||||
return cls(day,day)
|
||||
def __contains__(self, date):
|
||||
"""Check if the date is in the range"""
|
||||
if not isinstance(date, datetime.date):
|
||||
date = date_from_str(date)
|
||||
return self.start <= date <= self.end
|
||||
def __str__(self):
|
||||
return '%s - %s' % ( self.start.isoformat(), self.end.isoformat())
|
||||
|
@ -1,2 +1,2 @@
|
||||
|
||||
__version__ = '2013.02.02'
|
||||
__version__ = '2013.06.21'
|
||||
|
Reference in New Issue
Block a user