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
|
.coverage
|
||||||
cover/
|
cover/
|
||||||
updates_key.pem
|
updates_key.pem
|
||||||
|
*.egg-info
|
@ -8,6 +8,7 @@ notifications:
|
|||||||
email:
|
email:
|
||||||
- filippo.valsorda@gmail.com
|
- filippo.valsorda@gmail.com
|
||||||
- phihag@phihag.de
|
- phihag@phihag.de
|
||||||
|
- jaime.marquinez.ferrandiz+travis@gmail.com
|
||||||
# irc:
|
# irc:
|
||||||
# channels:
|
# channels:
|
||||||
# - "irc.freenode.org#youtube-dl"
|
# - "irc.freenode.org#youtube-dl"
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
include README.md
|
include README.md
|
||||||
include test/*.py
|
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
|
all: youtube-dl README.md README.txt youtube-dl.1 youtube-dl.bash-completion
|
||||||
|
|
||||||
clean:
|
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
|
PREFIX=/usr/local
|
||||||
BINDIR=$(PREFIX)/bin
|
BINDIR=$(PREFIX)/bin
|
||||||
MANDIR=$(PREFIX)/man
|
MANDIR=$(PREFIX)/man
|
||||||
SYSCONFDIR=/etc
|
|
||||||
PYTHON=/usr/bin/env python
|
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: youtube-dl youtube-dl.1 youtube-dl.bash-completion
|
||||||
install -d $(DESTDIR)$(BINDIR)
|
install -d $(DESTDIR)$(BINDIR)
|
||||||
install -m 755 youtube-dl $(DESTDIR)$(BINDIR)
|
install -m 755 youtube-dl $(DESTDIR)$(BINDIR)
|
||||||
@ -23,7 +36,9 @@ test:
|
|||||||
|
|
||||||
tar: youtube-dl.tar.gz
|
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
|
youtube-dl: youtube_dl/*.py
|
||||||
zip --quiet 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
|
youtube-dl.bash-completion: youtube_dl/*.py devscripts/bash-completion.in
|
||||||
python devscripts/bash-completion.py
|
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
|
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 \
|
@tar -czf youtube-dl.tar.gz --transform "s|^|youtube-dl/|" --owner 0 --group 0 \
|
||||||
--exclude '*.DS_Store' \
|
--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.
|
which means you can modify it, redistribute it or use it however you like.
|
||||||
|
|
||||||
# OPTIONS
|
# OPTIONS
|
||||||
-h, --help print this help text and exit
|
-h, --help print this help text and exit
|
||||||
--version print program version and exit
|
--version print program version and exit
|
||||||
-U, --update update this program to latest version
|
-U, --update update this program to latest version
|
||||||
-i, --ignore-errors continue on download errors
|
-i, --ignore-errors continue on download errors
|
||||||
-r, --rate-limit LIMIT download rate limit (e.g. 50k or 44.6m)
|
-r, --rate-limit LIMIT maximum download rate (e.g. 50k or 44.6m)
|
||||||
-R, --retries RETRIES number of retries (default is 10)
|
-R, --retries RETRIES number of retries (default is 10)
|
||||||
--buffer-size SIZE size of download buffer (e.g. 1024 or 16k) (default
|
--buffer-size SIZE size of download buffer (e.g. 1024 or 16k)
|
||||||
is 1024)
|
(default is 1024)
|
||||||
--no-resize-buffer do not automatically adjust the buffer size. By
|
--no-resize-buffer do not automatically adjust the buffer size. By
|
||||||
default, the buffer size is automatically resized
|
default, the buffer size is automatically resized
|
||||||
from an initial value of SIZE.
|
from an initial value of SIZE.
|
||||||
--dump-user-agent display the current browser identification
|
--dump-user-agent display the current browser identification
|
||||||
--user-agent UA specify a custom user agent
|
--user-agent UA specify a custom user agent
|
||||||
--list-extractors List all supported extractors and the URLs they
|
--referer REF specify a custom referer, use if the video access
|
||||||
would handle
|
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:
|
## Video Selection:
|
||||||
--playlist-start NUMBER playlist video to start at (default is 1)
|
--playlist-start NUMBER playlist video to start at (default is 1)
|
||||||
--playlist-end NUMBER playlist video to end at (default is last)
|
--playlist-end NUMBER playlist video to end at (default is last)
|
||||||
--match-title REGEX download only matching titles (regex or caseless
|
--match-title REGEX download only matching titles (regex or caseless
|
||||||
sub-string)
|
sub-string)
|
||||||
--reject-title REGEX skip download for matching titles (regex or
|
--reject-title REGEX skip download for matching titles (regex or
|
||||||
caseless sub-string)
|
caseless sub-string)
|
||||||
--max-downloads NUMBER Abort after downloading NUMBER files
|
--max-downloads NUMBER Abort after downloading NUMBER files
|
||||||
--min-filesize SIZE Do not download any videos smaller than SIZE (e.g.
|
--min-filesize SIZE Do not download any videos smaller than SIZE
|
||||||
50k or 44.6m)
|
(e.g. 50k or 44.6m)
|
||||||
--max-filesize SIZE Do not download any videos larger than SIZE (e.g.
|
--max-filesize SIZE Do not download any videos larger than SIZE (e.g.
|
||||||
50k or 44.6m)
|
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:
|
## Filesystem Options:
|
||||||
-t, --title use title in file name
|
-t, --title use title in file name (default)
|
||||||
--id use video ID in file name
|
--id use only video ID in file name
|
||||||
-l, --literal [deprecated] alias of --title
|
-l, --literal [deprecated] alias of --title
|
||||||
-A, --auto-number number downloaded files starting from 00000
|
-A, --auto-number number downloaded files starting from 00000
|
||||||
-o, --output TEMPLATE output filename template. Use %(title)s to get the
|
-o, --output TEMPLATE output filename template. Use %(title)s to get
|
||||||
title, %(uploader)s for the uploader name,
|
the title, %(uploader)s for the uploader name,
|
||||||
%(uploader_id)s for the uploader nickname if
|
%(uploader_id)s for the uploader nickname if
|
||||||
different, %(autonumber)s to get an automatically
|
different, %(autonumber)s to get an automatically
|
||||||
incremented number, %(ext)s for the filename
|
incremented number, %(ext)s for the filename
|
||||||
extension, %(upload_date)s for the upload date
|
extension, %(upload_date)s for the upload date
|
||||||
(YYYYMMDD), %(extractor)s for the provider
|
(YYYYMMDD), %(extractor)s for the provider
|
||||||
(youtube, metacafe, etc), %(id)s for the video id
|
(youtube, metacafe, etc), %(id)s for the video id
|
||||||
and %% for a literal percent. Use - to output to
|
, %(playlist)s for the playlist the video is in,
|
||||||
stdout. Can also be used to download to a different
|
%(playlist_index)s for the position in the
|
||||||
directory, for example with -o '/my/downloads/%(upl
|
playlist and %% for a literal percent. Use - to
|
||||||
oader)s/%(title)s-%(id)s.%(ext)s' .
|
output to stdout. Can also be used to download to
|
||||||
--restrict-filenames Restrict filenames to only ASCII characters, and
|
a different directory, for example with -o '/my/d
|
||||||
avoid "&" and spaces in filenames
|
ownloads/%(uploader)s/%(title)s-%(id)s.%(ext)s' .
|
||||||
-a, --batch-file FILE file containing URLs to download ('-' for stdin)
|
--autonumber-size NUMBER Specifies the number of digits in %(autonumber)s
|
||||||
-w, --no-overwrites do not overwrite files
|
when it is present in output filename template or
|
||||||
-c, --continue resume partially downloaded files
|
--autonumber option is given
|
||||||
--no-continue do not resume partially downloaded files (restart
|
--restrict-filenames Restrict filenames to only ASCII characters, and
|
||||||
from beginning)
|
avoid "&" and spaces in filenames
|
||||||
--cookies FILE file to read cookies from and dump cookie jar in
|
-a, --batch-file FILE file containing URLs to download ('-' for stdin)
|
||||||
--no-part do not use .part files
|
-w, --no-overwrites do not overwrite files
|
||||||
--no-mtime do not use the Last-modified header to set the file
|
-c, --continue resume partially downloaded files
|
||||||
modification time
|
--no-continue do not resume partially downloaded files (restart
|
||||||
--write-description write video description to a .description file
|
from beginning)
|
||||||
--write-info-json write video metadata to a .info.json file
|
--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:
|
## Verbosity / Simulation Options:
|
||||||
-q, --quiet activates quiet mode
|
-q, --quiet activates quiet mode
|
||||||
-s, --simulate do not download the video and do not write anything
|
-s, --simulate do not download the video and do not write
|
||||||
to disk
|
anything to disk
|
||||||
--skip-download do not download the video
|
--skip-download do not download the video
|
||||||
-g, --get-url simulate, quiet but print URL
|
-g, --get-url simulate, quiet but print URL
|
||||||
-e, --get-title simulate, quiet but print title
|
-e, --get-title simulate, quiet but print title
|
||||||
--get-thumbnail simulate, quiet but print thumbnail URL
|
--get-id simulate, quiet but print id
|
||||||
--get-description simulate, quiet but print video description
|
--get-thumbnail simulate, quiet but print thumbnail URL
|
||||||
--get-filename simulate, quiet but print output filename
|
--get-description simulate, quiet but print video description
|
||||||
--get-format simulate, quiet but print output format
|
--get-filename simulate, quiet but print output filename
|
||||||
--no-progress do not print progress bar
|
--get-format simulate, quiet but print output format
|
||||||
--console-title display progress in console titlebar
|
--newline output progress bar as new lines
|
||||||
-v, --verbose print various debugging information
|
--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:
|
## Video Format Options:
|
||||||
-f, --format FORMAT video format code
|
-f, --format FORMAT video format code, specifiy the order of
|
||||||
--all-formats download all available video formats
|
preference using slashes: "-f 22/17/18"
|
||||||
--prefer-free-formats prefer free video formats unless a specific one is
|
--all-formats download all available video formats
|
||||||
requested
|
--prefer-free-formats prefer free video formats unless a specific one
|
||||||
--max-quality FORMAT highest quality format to download
|
is requested
|
||||||
-F, --list-formats list all available formats (currently youtube only)
|
--max-quality FORMAT highest quality format to download
|
||||||
--write-srt write video closed captions to a .srt file
|
-F, --list-formats list all available formats (currently youtube
|
||||||
(currently youtube only)
|
only)
|
||||||
--srt-lang LANG language of the closed captions to download
|
--write-sub write subtitle file (currently youtube only)
|
||||||
(optional) use IETF language tags like 'en'
|
--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:
|
## Authentication Options:
|
||||||
-u, --username USERNAME account username
|
-u, --username USERNAME account username
|
||||||
-p, --password PASSWORD account password
|
-p, --password PASSWORD account password
|
||||||
-n, --netrc use .netrc authentication data
|
-n, --netrc use .netrc authentication data
|
||||||
|
|
||||||
## Post-processing Options:
|
## Post-processing Options:
|
||||||
-x, --extract-audio convert video files to audio-only files (requires
|
-x, --extract-audio convert video files to audio-only files (requires
|
||||||
ffmpeg or avconv and ffprobe or avprobe)
|
ffmpeg or avconv and ffprobe or avprobe)
|
||||||
--audio-format FORMAT "best", "aac", "vorbis", "mp3", "m4a", "opus", or
|
--audio-format FORMAT "best", "aac", "vorbis", "mp3", "m4a", "opus", or
|
||||||
"wav"; best by default
|
"wav"; best by default
|
||||||
--audio-quality QUALITY ffmpeg/avconv audio quality specification, insert a
|
--audio-quality QUALITY ffmpeg/avconv audio quality specification, insert
|
||||||
value between 0 (better) and 9 (worse) for VBR or a
|
a value between 0 (better) and 9 (worse) for VBR
|
||||||
specific bitrate like 128K (default 5)
|
or a specific bitrate like 128K (default 5)
|
||||||
--recode-video FORMAT Encode the video to another format if necessary
|
--recode-video FORMAT Encode the video to another format if necessary
|
||||||
(currently supported: mp4|flv|ogg|webm)
|
(currently supported: mp4|flv|ogg|webm)
|
||||||
-k, --keep-video keeps the video file on disk after the post-
|
-k, --keep-video keeps the video file on disk after the post-
|
||||||
processing; the video is erased by default
|
processing; the video is erased by default
|
||||||
--no-post-overwrites do not overwrite post-processed files; the post-
|
--no-post-overwrites do not overwrite post-processed files; the post-
|
||||||
processed files are overwritten by default
|
processed files are overwritten by default
|
||||||
|
|
||||||
# CONFIGURATION
|
# 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).
|
- `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.
|
- `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.
|
- `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).
|
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 --get-filename -o "%(title)s.%(ext)s" BaW_jenozKc --restrict-filenames
|
||||||
youtube-dl_test_video_.mp4 # A simple file name
|
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
|
# FAQ
|
||||||
|
|
||||||
### Can you please put the -b option back?
|
### 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 [ ! -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
|
if [ ! -f "updates_key.pem" ]; then echo 'ERROR: updates_key.pem missing'; exit 1; fi
|
||||||
|
|
||||||
echo "\n### First of all, testing..."
|
/bin/echo -e "\n### First of all, testing..."
|
||||||
make clean
|
make cleanall
|
||||||
nosetests --with-coverage --cover-package=youtube_dl --cover-html test || exit 1
|
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
|
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
|
make README.md
|
||||||
git add CHANGELOG README.md youtube_dl/version.py
|
git add CHANGELOG README.md youtube_dl/version.py
|
||||||
git commit -m "release $version"
|
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 tag -s -m "Release $version" "$version"
|
||||||
git show "$version"
|
git show "$version"
|
||||||
read -p "Is it good, can I push? (y/n) " -n 1
|
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 $MASTER:master
|
||||||
git push origin "$version"
|
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)
|
REV=$(git rev-parse HEAD)
|
||||||
make youtube-dl youtube-dl.tar.gz
|
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 || \
|
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)
|
(cd build/$version/ && sha512sum $RELEASE_FILES > SHA2-512SUMS)
|
||||||
git checkout HEAD -- youtube-dl youtube-dl.exe
|
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
|
for f in $RELEASE_FILES; do gpg --detach-sig "build/$version/$f"; done
|
||||||
scp -r "build/$version" ytdl@youtube-dl.org:html/downloads/
|
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
|
git clone --branch gh-pages --single-branch . build/gh-pages
|
||||||
ROOT=$(pwd)
|
ROOT=$(pwd)
|
||||||
(
|
(
|
||||||
@ -69,6 +69,7 @@ ROOT=$(pwd)
|
|||||||
ORIGIN_URL=$(git config --get remote.origin.url)
|
ORIGIN_URL=$(git config --get remote.origin.url)
|
||||||
cd build/gh-pages
|
cd build/gh-pages
|
||||||
"$ROOT/devscripts/gh-pages/add-version.py" $version
|
"$ROOT/devscripts/gh-pages/add-version.py" $version
|
||||||
|
"$ROOT/devscripts/gh-pages/update-feed.py"
|
||||||
"$ROOT/devscripts/gh-pages/sign-versions.py" < "$ROOT/updates_key.pem"
|
"$ROOT/devscripts/gh-pages/sign-versions.py" < "$ROOT/updates_key.pem"
|
||||||
"$ROOT/devscripts/gh-pages/generate-download.py"
|
"$ROOT/devscripts/gh-pages/generate-download.py"
|
||||||
"$ROOT/devscripts/gh-pages/update-copyright.py"
|
"$ROOT/devscripts/gh-pages/update-copyright.py"
|
||||||
@ -83,7 +84,9 @@ ROOT=$(pwd)
|
|||||||
)
|
)
|
||||||
rm -rf build
|
rm -rf build
|
||||||
|
|
||||||
|
make pypi-files
|
||||||
echo "Uploading to PyPi ..."
|
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]
|
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'
|
VERSION_URL = UPDATE_URL + 'LATEST_VERSION'
|
||||||
JSON_URL = UPDATE_URL + 'versions.json'
|
JSON_URL = UPDATE_URL + 'versions.json'
|
||||||
UPDATES_RSA_KEY = (0x9d60ee4d8f805312fdb15a62f87b95bd66177b91df176765d13514a0f1754bcd2057295c5b6f1d35daa6742c3ffc9a82d3e118861c207995a8031e151d863c9927e304576bc80692bc8e094896fcf11b66f3e29e04e3a71e9a11558558acea1840aec37fc396fb6b65dc81a1c4144e03bd1c011de62e3f1357b327d08426fe93, 65537)
|
UPDATES_RSA_KEY = (0x9d60ee4d8f805312fdb15a62f87b95bd66177b91df176765d13514a0f1754bcd2057295c5b6f1d35daa6742c3ffc9a82d3e118861c207995a8031e151d863c9927e304576bc80692bc8e094896fcf11b66f3e29e04e3a71e9a11558558acea1840aec37fc396fb6b65dc81a1c4144e03bd1c011de62e3f1357b327d08426fe93, 65537)
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
"simulate": false,
|
"simulate": false,
|
||||||
"skip_download": false,
|
"skip_download": false,
|
||||||
"subtitleslang": null,
|
"subtitleslang": null,
|
||||||
|
"subtitlesformat": "srt",
|
||||||
"test": true,
|
"test": true,
|
||||||
"updatetime": true,
|
"updatetime": true,
|
||||||
"usenetrc": false,
|
"usenetrc": false,
|
||||||
@ -36,5 +37,8 @@
|
|||||||
"verbose": true,
|
"verbose": true,
|
||||||
"writedescription": false,
|
"writedescription": false,
|
||||||
"writeinfojson": true,
|
"writeinfojson": true,
|
||||||
"writesubtitles": false
|
"writesubtitles": false,
|
||||||
|
"onlysubtitles": false,
|
||||||
|
"allsubtitles": false,
|
||||||
|
"listssubtitles": false
|
||||||
}
|
}
|
@ -7,16 +7,43 @@ import unittest
|
|||||||
import os
|
import os
|
||||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
from youtube_dl.InfoExtractors import YoutubeIE, YoutubePlaylistIE
|
from youtube_dl.InfoExtractors import YoutubeIE, YoutubePlaylistIE, YoutubeChannelIE, JustinTVIE
|
||||||
|
|
||||||
class TestAllURLsMatching(unittest.TestCase):
|
class TestAllURLsMatching(unittest.TestCase):
|
||||||
def test_youtube_playlist_matching(self):
|
def test_youtube_playlist_matching(self):
|
||||||
self.assertTrue(YoutubePlaylistIE().suitable(u'ECUl4u3cNGP61MdtwGTqZA0MreSaDybji8'))
|
self.assertTrue(YoutubePlaylistIE.suitable(u'ECUl4u3cNGP61MdtwGTqZA0MreSaDybji8'))
|
||||||
self.assertTrue(YoutubePlaylistIE().suitable(u'PL63F0C78739B09958'))
|
self.assertTrue(YoutubePlaylistIE.suitable(u'UUBABnxM4Ar9ten8Mdjj1j0Q')) #585
|
||||||
self.assertFalse(YoutubePlaylistIE().suitable(u'PLtS2H6bU1M'))
|
self.assertTrue(YoutubePlaylistIE.suitable(u'PL63F0C78739B09958'))
|
||||||
|
self.assertTrue(YoutubePlaylistIE.suitable(u'https://www.youtube.com/playlist?list=UUBABnxM4Ar9ten8Mdjj1j0Q'))
|
||||||
|
self.assertTrue(YoutubePlaylistIE.suitable(u'https://www.youtube.com/course?list=ECUl4u3cNGP61MdtwGTqZA0MreSaDybji8'))
|
||||||
|
self.assertTrue(YoutubePlaylistIE.suitable(u'https://www.youtube.com/playlist?list=PLwP_SiAcdui0KVebT0mU9Apz359a4ubsC'))
|
||||||
|
self.assertTrue(YoutubePlaylistIE.suitable(u'https://www.youtube.com/watch?v=AV6J6_AeFEQ&playnext=1&list=PL4023E734DA416012')) #668
|
||||||
|
self.assertFalse(YoutubePlaylistIE.suitable(u'PLtS2H6bU1M'))
|
||||||
|
|
||||||
def test_youtube_matching(self):
|
def test_youtube_matching(self):
|
||||||
self.assertTrue(YoutubeIE().suitable(u'PLtS2H6bU1M'))
|
self.assertTrue(YoutubeIE.suitable(u'PLtS2H6bU1M'))
|
||||||
|
self.assertFalse(YoutubeIE.suitable(u'https://www.youtube.com/watch?v=AV6J6_AeFEQ&playnext=1&list=PL4023E734DA416012')) #668
|
||||||
|
|
||||||
|
def test_youtube_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):
|
def test_youtube_extract(self):
|
||||||
self.assertEqual(YoutubeIE()._extract_id('http://www.youtube.com/watch?&v=BaW_jenozKc'), 'BaW_jenozKc')
|
self.assertEqual(YoutubeIE()._extract_id('http://www.youtube.com/watch?&v=BaW_jenozKc'), 'BaW_jenozKc')
|
||||||
|
@ -7,8 +7,8 @@ import os
|
|||||||
import json
|
import json
|
||||||
import unittest
|
import unittest
|
||||||
import sys
|
import sys
|
||||||
import hashlib
|
|
||||||
import socket
|
import socket
|
||||||
|
import binascii
|
||||||
|
|
||||||
# Allow direct execution
|
# Allow direct execution
|
||||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
@ -20,6 +20,8 @@ from youtube_dl.utils import *
|
|||||||
DEF_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'tests.json')
|
DEF_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'tests.json')
|
||||||
PARAMETERS_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "parameters.json")
|
PARAMETERS_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "parameters.json")
|
||||||
|
|
||||||
|
RETRIES = 3
|
||||||
|
|
||||||
# General configuration (from __init__, not very elegant...)
|
# General configuration (from __init__, not very elegant...)
|
||||||
jar = compat_cookiejar.CookieJar()
|
jar = compat_cookiejar.CookieJar()
|
||||||
cookie_processor = compat_urllib_request.HTTPCookieProcessor(jar)
|
cookie_processor = compat_urllib_request.HTTPCookieProcessor(jar)
|
||||||
@ -36,11 +38,16 @@ def _try_rm(filename):
|
|||||||
if ose.errno != errno.ENOENT:
|
if ose.errno != errno.ENOENT:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
md5 = lambda s: hashlib.md5(s.encode('utf-8')).hexdigest()
|
||||||
|
|
||||||
class FileDownloader(youtube_dl.FileDownloader):
|
class FileDownloader(youtube_dl.FileDownloader):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.to_stderr = self.to_screen
|
self.to_stderr = self.to_screen
|
||||||
self.processed_info_dicts = []
|
self.processed_info_dicts = []
|
||||||
return youtube_dl.FileDownloader.__init__(self, *args, **kwargs)
|
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):
|
def process_info(self, info_dict):
|
||||||
self.processed_info_dicts.append(info_dict)
|
self.processed_info_dicts.append(info_dict)
|
||||||
return youtube_dl.FileDownloader.process_info(self, 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):
|
class TestDownload(unittest.TestCase):
|
||||||
|
maxDiff = None
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.parameters = parameters
|
self.parameters = parameters
|
||||||
self.defs = defs
|
self.defs = defs
|
||||||
@ -64,7 +72,7 @@ class TestDownload(unittest.TestCase):
|
|||||||
def generator(test_case):
|
def generator(test_case):
|
||||||
|
|
||||||
def test_template(self):
|
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:
|
if not ie._WORKING:
|
||||||
print('Skipping: IE marked as not _WORKING')
|
print('Skipping: IE marked as not _WORKING')
|
||||||
return
|
return
|
||||||
@ -79,9 +87,8 @@ def generator(test_case):
|
|||||||
params.update(test_case.get('params', {}))
|
params.update(test_case.get('params', {}))
|
||||||
|
|
||||||
fd = FileDownloader(params)
|
fd = FileDownloader(params)
|
||||||
fd.add_info_extractor(ie())
|
for ie in youtube_dl.InfoExtractors.gen_extractors():
|
||||||
for ien in test_case.get('add_ie', []):
|
fd.add_info_extractor(ie)
|
||||||
fd.add_info_extractor(getattr(youtube_dl.InfoExtractors, ien + 'IE')())
|
|
||||||
finished_hook_called = set()
|
finished_hook_called = set()
|
||||||
def _hook(status):
|
def _hook(status):
|
||||||
if status['status'] == 'finished':
|
if status['status'] == 'finished':
|
||||||
@ -94,7 +101,19 @@ def generator(test_case):
|
|||||||
_try_rm(tc['file'] + '.part')
|
_try_rm(tc['file'] + '.part')
|
||||||
_try_rm(tc['file'] + '.info.json')
|
_try_rm(tc['file'] + '.info.json')
|
||||||
try:
|
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:
|
for tc in test_cases:
|
||||||
if not test_case.get('params', {}).get('skip_download', False):
|
if not test_case.get('params', {}).get('skip_download', False):
|
||||||
@ -107,7 +126,21 @@ def generator(test_case):
|
|||||||
with io.open(tc['file'] + '.info.json', encoding='utf-8') as infof:
|
with io.open(tc['file'] + '.info.json', encoding='utf-8') as infof:
|
||||||
info_dict = json.load(infof)
|
info_dict = json.load(infof)
|
||||||
for (info_field, value) in tc.get('info_dict', {}).items():
|
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:
|
finally:
|
||||||
for tc in test_cases:
|
for tc in test_cases:
|
||||||
_try_rm(tc['file'])
|
_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 sanitize_filename
|
||||||
from youtube_dl.utils import unescapeHTML
|
from youtube_dl.utils import unescapeHTML
|
||||||
from youtube_dl.utils import orderedSet
|
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):
|
if sys.version_info < (3, 0):
|
||||||
_compat_str = lambda b: b.decode('unicode-escape')
|
_compat_str = lambda b: b.decode('unicode-escape')
|
||||||
@ -96,5 +98,19 @@ class TestUtil(unittest.TestCase):
|
|||||||
def test_unescape_html(self):
|
def test_unescape_html(self):
|
||||||
self.assertEqual(unescapeHTML(_compat_str('%20;')), _compat_str('%20;'))
|
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__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -8,8 +8,9 @@ import json
|
|||||||
import os
|
import os
|
||||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
from youtube_dl.InfoExtractors import YoutubeUserIE,YoutubePlaylistIE
|
from youtube_dl.InfoExtractors import YoutubeUserIE, YoutubePlaylistIE, YoutubeIE, YoutubeChannelIE
|
||||||
from youtube_dl.utils import *
|
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")
|
PARAMETERS_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "parameters.json")
|
||||||
with io.open(PARAMETERS_FILE, encoding='utf-8') as pf:
|
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())
|
opener = compat_urllib_request.build_opener(proxy_handler, cookie_processor, YoutubeDLHandler())
|
||||||
compat_urllib_request.install_opener(opener)
|
compat_urllib_request.install_opener(opener)
|
||||||
|
|
||||||
class FakeDownloader(object):
|
class FakeDownloader(FileDownloader):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.result = []
|
self.result = []
|
||||||
self.params = parameters
|
self.params = parameters
|
||||||
def to_screen(self, s):
|
def to_screen(self, s):
|
||||||
print(s)
|
print(s)
|
||||||
def trouble(self, s):
|
def trouble(self, s, tb=None):
|
||||||
raise Exception(s)
|
raise Exception(s)
|
||||||
def download(self, x):
|
def extract_info(self, url):
|
||||||
self.result.append(x)
|
self.result.append(url)
|
||||||
|
return url
|
||||||
|
|
||||||
class TestYoutubeLists(unittest.TestCase):
|
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):
|
def test_youtube_playlist(self):
|
||||||
DL = FakeDownloader()
|
dl = FakeDownloader()
|
||||||
IE = YoutubePlaylistIE(DL)
|
ie = YoutubePlaylistIE(dl)
|
||||||
IE.extract('https://www.youtube.com/playlist?list=PLwiyx1dc3P2JR9N8gQaQN_BCvlSlap7re')
|
result = ie.extract('https://www.youtube.com/playlist?list=PLwiyx1dc3P2JR9N8gQaQN_BCvlSlap7re')[0]
|
||||||
self.assertEqual(DL.result, [
|
self.assertIsPlaylist(result)
|
||||||
['http://www.youtube.com/watch?v=bV9L5Ht9LgY'],
|
self.assertEqual(result['title'], 'ytdl test PL')
|
||||||
['http://www.youtube.com/watch?v=FXxLjLQi3Fg'],
|
ytie_results = [YoutubeIE()._extract_id(url['url']) for url in result['entries']]
|
||||||
['http://www.youtube.com/watch?v=tU3Bgo5qJZE']
|
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):
|
def test_youtube_playlist_long(self):
|
||||||
DL = FakeDownloader()
|
dl = FakeDownloader()
|
||||||
IE = YoutubePlaylistIE(DL)
|
ie = YoutubePlaylistIE(dl)
|
||||||
IE.extract('https://www.youtube.com/playlist?list=UUBABnxM4Ar9ten8Mdjj1j0Q')
|
result = ie.extract('https://www.youtube.com/playlist?list=UUBABnxM4Ar9ten8Mdjj1j0Q')[0]
|
||||||
self.assertTrue(len(DL.result) >= 799)
|
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):
|
def test_youtube_course(self):
|
||||||
DL = FakeDownloader()
|
dl = FakeDownloader()
|
||||||
IE = YoutubePlaylistIE(DL)
|
ie = YoutubePlaylistIE(dl)
|
||||||
# TODO find a > 100 (paginating?) videos course
|
# TODO find a > 100 (paginating?) videos course
|
||||||
IE.extract('https://www.youtube.com/course?list=ECUl4u3cNGP61MdtwGTqZA0MreSaDybji8')
|
result = ie.extract('https://www.youtube.com/course?list=ECUl4u3cNGP61MdtwGTqZA0MreSaDybji8')[0]
|
||||||
self.assertEqual(DL.result[0], ['http://www.youtube.com/watch?v=j9WZyLZCBzs'])
|
entries = result['entries']
|
||||||
self.assertEqual(len(DL.result), 25)
|
self.assertEqual(YoutubeIE()._extract_id(entries[0]['url']), 'j9WZyLZCBzs')
|
||||||
self.assertEqual(DL.result[-1], ['http://www.youtube.com/watch?v=rYefUsYuEp0'])
|
self.assertEqual(len(entries), 25)
|
||||||
|
self.assertEqual(YoutubeIE()._extract_id(entries[-1]['url']), 'rYefUsYuEp0')
|
||||||
|
|
||||||
def test_youtube_channel(self):
|
def test_youtube_channel(self):
|
||||||
# I give up, please find a channel that does paginate and test this like test_youtube_playlist_long
|
dl = FakeDownloader()
|
||||||
pass # TODO
|
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):
|
def test_youtube_user(self):
|
||||||
DL = FakeDownloader()
|
dl = FakeDownloader()
|
||||||
IE = YoutubeUserIE(DL)
|
ie = YoutubeUserIE(dl)
|
||||||
IE.extract('https://www.youtube.com/user/TheLinuxFoundation')
|
result = ie.extract('https://www.youtube.com/user/TheLinuxFoundation')[0]
|
||||||
self.assertTrue(len(DL.result) >= 320)
|
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__':
|
if __name__ == '__main__':
|
||||||
unittest.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.InfoExtractors import YoutubeIE
|
||||||
from youtube_dl.utils import *
|
from youtube_dl.utils import *
|
||||||
|
from youtube_dl import FileDownloader
|
||||||
|
|
||||||
PARAMETERS_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "parameters.json")
|
PARAMETERS_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "parameters.json")
|
||||||
with io.open(PARAMETERS_FILE, encoding='utf-8') as pf:
|
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())
|
opener = compat_urllib_request.build_opener(proxy_handler, cookie_processor, YoutubeDLHandler())
|
||||||
compat_urllib_request.install_opener(opener)
|
compat_urllib_request.install_opener(opener)
|
||||||
|
|
||||||
class FakeDownloader(object):
|
class FakeDownloader(FileDownloader):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.result = []
|
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):
|
def to_screen(self, s):
|
||||||
print(s)
|
print(s)
|
||||||
def trouble(self, s):
|
def trouble(self, s, tb=None):
|
||||||
raise Exception(s)
|
raise Exception(s)
|
||||||
def download(self, x):
|
def download(self, x):
|
||||||
self.result.append(x)
|
self.result.append(x)
|
||||||
@ -38,20 +41,71 @@ class FakeDownloader(object):
|
|||||||
md5 = lambda s: hashlib.md5(s.encode('utf-8')).hexdigest()
|
md5 = lambda s: hashlib.md5(s.encode('utf-8')).hexdigest()
|
||||||
|
|
||||||
class TestYoutubeSubtitles(unittest.TestCase):
|
class TestYoutubeSubtitles(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
DL = FakeDownloader()
|
||||||
|
DL.params['allsubtitles'] = False
|
||||||
|
DL.params['writesubtitles'] = False
|
||||||
|
DL.params['subtitlesformat'] = 'srt'
|
||||||
|
DL.params['listsubtitles'] = False
|
||||||
|
def test_youtube_no_subtitles(self):
|
||||||
|
DL = FakeDownloader()
|
||||||
|
DL.params['writesubtitles'] = False
|
||||||
|
IE = YoutubeIE(DL)
|
||||||
|
info_dict = IE.extract('QRS8MkLhQmM')
|
||||||
|
subtitles = info_dict[0]['subtitles']
|
||||||
|
self.assertEqual(subtitles, None)
|
||||||
def test_youtube_subtitles(self):
|
def test_youtube_subtitles(self):
|
||||||
DL = FakeDownloader()
|
DL = FakeDownloader()
|
||||||
DL.params['writesubtitles'] = True
|
DL.params['writesubtitles'] = True
|
||||||
IE = YoutubeIE(DL)
|
IE = YoutubeIE(DL)
|
||||||
info_dict = IE.extract('QRS8MkLhQmM')
|
info_dict = IE.extract('QRS8MkLhQmM')
|
||||||
self.assertEqual(md5(info_dict[0]['subtitles']), 'c3228550d59116f3c29fba370b55d033')
|
sub = info_dict[0]['subtitles'][0]
|
||||||
|
self.assertEqual(md5(sub[2]), '4cd9278a35ba2305f47354ee13472260')
|
||||||
def test_youtube_subtitles_it(self):
|
def test_youtube_subtitles_it(self):
|
||||||
DL = FakeDownloader()
|
DL = FakeDownloader()
|
||||||
DL.params['writesubtitles'] = True
|
DL.params['writesubtitles'] = True
|
||||||
DL.params['subtitleslang'] = 'it'
|
DL.params['subtitleslang'] = 'it'
|
||||||
IE = YoutubeIE(DL)
|
IE = YoutubeIE(DL)
|
||||||
info_dict = IE.extract('QRS8MkLhQmM')
|
info_dict = IE.extract('QRS8MkLhQmM')
|
||||||
self.assertEqual(md5(info_dict[0]['subtitles']), '132a88a0daf8e1520f393eb58f1f646a')
|
sub = info_dict[0]['subtitles'][0]
|
||||||
|
self.assertEqual(md5(sub[2]), '164a51f16f260476a05b50fe4c2f161d')
|
||||||
|
def test_youtube_onlysubtitles(self):
|
||||||
|
DL = FakeDownloader()
|
||||||
|
DL.params['writesubtitles'] = True
|
||||||
|
DL.params['onlysubtitles'] = True
|
||||||
|
IE = YoutubeIE(DL)
|
||||||
|
info_dict = IE.extract('QRS8MkLhQmM')
|
||||||
|
sub = info_dict[0]['subtitles'][0]
|
||||||
|
self.assertEqual(md5(sub[2]), '4cd9278a35ba2305f47354ee13472260')
|
||||||
|
def test_youtube_allsubtitles(self):
|
||||||
|
DL = FakeDownloader()
|
||||||
|
DL.params['allsubtitles'] = True
|
||||||
|
IE = YoutubeIE(DL)
|
||||||
|
info_dict = IE.extract('QRS8MkLhQmM')
|
||||||
|
subtitles = info_dict[0]['subtitles']
|
||||||
|
self.assertEqual(len(subtitles), 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__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
427
test/tests.json
427
test/tests.json
@ -15,43 +15,76 @@
|
|||||||
"name": "Dailymotion",
|
"name": "Dailymotion",
|
||||||
"md5": "392c4b85a60a90dc4792da41ce3144eb",
|
"md5": "392c4b85a60a90dc4792da41ce3144eb",
|
||||||
"url": "http://www.dailymotion.com/video/x33vw9_tutoriel-de-youtubeur-dl-des-video_tech",
|
"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",
|
"name": "Metacafe",
|
||||||
"add_ie": ["Youtube"],
|
"add_ie": ["Youtube"],
|
||||||
"url": "http://metacafe.com/watch/yt-_aUehQsCQtM/the_electric_company_short_i_pbs_kids_go/",
|
"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",
|
"name": "BlipTV",
|
||||||
"md5": "b2d849efcf7ee18917e4b4d9ff37cafe",
|
"md5": "b2d849efcf7ee18917e4b4d9ff37cafe",
|
||||||
"url": "http://blip.tv/cbr/cbr-exclusive-gotham-city-imposters-bats-vs-jokerz-short-3-5796352",
|
"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",
|
"name": "XVideos",
|
||||||
"md5": "1d0c835822f0a71a7bf011855db929d0",
|
"md5": "1d0c835822f0a71a7bf011855db929d0",
|
||||||
"url": "http://www.xvideos.com/video939581/funny_porns_by_s_-1",
|
"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",
|
"name": "YouPorn",
|
||||||
"md5": "c37ddbaaa39058c76a7e86c6813423c1",
|
"md5": "c37ddbaaa39058c76a7e86c6813423c1",
|
||||||
"url": "http://www.youporn.com/watch/505835/sex-ed-is-it-safe-to-masturbate-daily/",
|
"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",
|
"name": "Pornotube",
|
||||||
"md5": "374dd6dcedd24234453b295209aa69b6",
|
"md5": "374dd6dcedd24234453b295209aa69b6",
|
||||||
"url": "http://pornotube.com/c/173/m/1689755/Marilyn-Monroe-Bathing",
|
"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",
|
"name": "YouJizz",
|
||||||
"md5": "07e15fa469ba384c7693fd246905547c",
|
"md5": "07e15fa469ba384c7693fd246905547c",
|
||||||
"url": "http://www.youjizz.com/videos/zeichentrick-1-2189178.html",
|
"url": "http://www.youjizz.com/videos/zeichentrick-1-2189178.html",
|
||||||
"file": "2189178.flv"
|
"file": "2189178.flv",
|
||||||
|
"info_dict": {
|
||||||
|
"title": "Zeichentrick 1"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Vimeo",
|
"name": "Vimeo",
|
||||||
@ -70,73 +103,102 @@
|
|||||||
"name": "Soundcloud",
|
"name": "Soundcloud",
|
||||||
"md5": "ebef0a451b909710ed1d7787dddbf0d7",
|
"md5": "ebef0a451b909710ed1d7787dddbf0d7",
|
||||||
"url": "http://soundcloud.com/ethmusic/lostin-powers-she-so-heavy",
|
"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",
|
"name": "StanfordOpenClassroom",
|
||||||
"md5": "544a9468546059d4e80d76265b0443b8",
|
"md5": "544a9468546059d4e80d76265b0443b8",
|
||||||
"url": "http://openclassroom.stanford.edu/MainFolder/VideoPage.php?course=PracticalUnix&video=intro-environment&speed=100",
|
"url": "http://openclassroom.stanford.edu/MainFolder/VideoPage.php?course=PracticalUnix&video=intro-environment&speed=100",
|
||||||
"file": "PracticalUnix_intro-environment.mp4",
|
"file": "PracticalUnix_intro-environment.mp4",
|
||||||
"skip": "Currently offline"
|
"info_dict": {
|
||||||
|
"title": "Intro Environment"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "XNXX",
|
"name": "XNXX",
|
||||||
"md5": "0831677e2b4761795f68d417e0b7b445",
|
"md5": "0831677e2b4761795f68d417e0b7b445",
|
||||||
"url": "http://video.xnxx.com/video1135332/lida_naked_funny_actress_5_",
|
"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",
|
"name": "Youku",
|
||||||
"url": "http://v.youku.com/v_show/id_XNDgyMDQ2NTQw.html",
|
"url": "http://v.youku.com/v_show/id_XNDgyMDQ2NTQw.html",
|
||||||
"file": "XNDgyMDQ2NTQw_part00.flv",
|
"file": "XNDgyMDQ2NTQw_part00.flv",
|
||||||
"md5": "ffe3f2e435663dc2d1eea34faeff5b5b",
|
"md5": "ffe3f2e435663dc2d1eea34faeff5b5b",
|
||||||
"params": { "test": false }
|
"params": { "test": false },
|
||||||
|
"info_dict": {
|
||||||
|
"title": "youtube-dl test video \"'/\\ä↭𝕐"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "NBA",
|
"name": "NBA",
|
||||||
"url": "http://www.nba.com/video/games/nets/2012/12/04/0021200253-okc-bkn-recap.nba/index.html",
|
"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",
|
"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",
|
"name": "JustinTV",
|
||||||
"url": "http://www.twitch.tv/thegamedevhub/b/296128360",
|
"url": "http://www.twitch.tv/thegamedevhub/b/296128360",
|
||||||
"file": "296128360.flv",
|
"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",
|
"name": "MyVideo",
|
||||||
"url": "http://www.myvideo.de/watch/8229274/bowling_fail_or_win",
|
"url": "http://www.myvideo.de/watch/8229274/bowling_fail_or_win",
|
||||||
"file": "8229274.flv",
|
"file": "8229274.flv",
|
||||||
"md5": "2d2753e8130479ba2cb7e0a37002053e"
|
"md5": "2d2753e8130479ba2cb7e0a37002053e",
|
||||||
|
"info_dict": {
|
||||||
|
"title": "bowling-fail-or-win"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Escapist",
|
"name": "Escapist",
|
||||||
"url": "http://www.escapistmagazine.com/videos/view/the-escapist-presents/6618-Breaking-Down-Baldurs-Gate",
|
"url": "http://www.escapistmagazine.com/videos/view/the-escapist-presents/6618-Breaking-Down-Baldurs-Gate",
|
||||||
"file": "6618-Breaking-Down-Baldurs-Gate.flv",
|
"file": "6618-Breaking-Down-Baldurs-Gate.mp4",
|
||||||
"md5": "c6793dbda81388f4264c1ba18684a74d"
|
"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",
|
"name": "GooglePlus",
|
||||||
"url": "https://plus.google.com/u/0/108897254135232129896/posts/ZButuJc6CtH",
|
"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",
|
"name": "FunnyOrDie",
|
||||||
"url": "http://www.funnyordie.com/videos/0732f586d7/heart-shaped-box-literal-video-version",
|
"url": "http://www.funnyordie.com/videos/0732f586d7/heart-shaped-box-literal-video-version",
|
||||||
"file": "0732f586d7.mp4",
|
"file": "0732f586d7.mp4",
|
||||||
"md5": "f647e9e90064b53b6e046e75d0241fbd"
|
"md5": "f647e9e90064b53b6e046e75d0241fbd",
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "TweetReel",
|
|
||||||
"url": "http://tweetreel.com/?77smq",
|
|
||||||
"file": "77smq.mov",
|
|
||||||
"md5": "56b4d9ca9de467920f3f99a6d91255d6",
|
|
||||||
"info_dict": {
|
"info_dict": {
|
||||||
"uploader": "itszero",
|
"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.",
|
||||||
"uploader_id": "itszero",
|
"title": "Heart-Shaped Box: Literal Video Version"
|
||||||
"upload_date": "20091225",
|
|
||||||
"description": "Installing Gentoo Linux on Powerbook G4, it turns out the sleep indicator becomes HDD activity indicator :D"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -165,7 +227,8 @@
|
|||||||
"file": "20274954.flv",
|
"file": "20274954.flv",
|
||||||
"md5": "088f151799e8f572f84eb62f17d73e5c",
|
"md5": "088f151799e8f572f84eb62f17d73e5c",
|
||||||
"info_dict": {
|
"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",
|
"url": "http://www.infoq.com/presentations/A-Few-of-My-Favorite-Python-Things",
|
||||||
"file": "12-jan-pythonthings.mp4",
|
"file": "12-jan-pythonthings.mp4",
|
||||||
"info_dict": {
|
"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"
|
"title": "A Few of My Favorite [Python] Things"
|
||||||
},
|
},
|
||||||
"params": {
|
"params": {
|
||||||
@ -185,7 +249,10 @@
|
|||||||
"file": "422212.mp4",
|
"file": "422212.mp4",
|
||||||
"md5": "4e2f5cb088a83cd8cdb7756132f9739d",
|
"md5": "4e2f5cb088a83cd8cdb7756132f9739d",
|
||||||
"info_dict": {
|
"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",
|
"file": "11885679.m4a",
|
||||||
"md5": "d30b5b5f74217410f4689605c35d1fd7",
|
"md5": "d30b5b5f74217410f4689605c35d1fd7",
|
||||||
"info_dict": {
|
"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",
|
"file": "11885680.m4a",
|
||||||
"md5": "4eb0a669317cd725f6bbd336a29f923a",
|
"md5": "4eb0a669317cd725f6bbd336a29f923a",
|
||||||
"info_dict": {
|
"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",
|
"file": "11885682.m4a",
|
||||||
"md5": "1893e872e263a2705558d1d319ad19e8",
|
"md5": "1893e872e263a2705558d1d319ad19e8",
|
||||||
"info_dict": {
|
"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",
|
"file": "11885683.m4a",
|
||||||
"md5": "b673c46f47a216ab1741ae8836af5899",
|
"md5": "b673c46f47a216ab1741ae8836af5899",
|
||||||
"info_dict": {
|
"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",
|
"file": "11885684.m4a",
|
||||||
"md5": "1d74534e95df54986da7f5abf7d842b7",
|
"md5": "1d74534e95df54986da7f5abf7d842b7",
|
||||||
"info_dict": {
|
"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",
|
"file": "11885685.m4a",
|
||||||
"md5": "f081f47af8f6ae782ed131d38b9cd1c0",
|
"md5": "f081f47af8f6ae782ed131d38b9cd1c0",
|
||||||
"info_dict": {
|
"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'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'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:
|
try:
|
||||||
raw_input()
|
raw_input()
|
||||||
@ -47,7 +47,7 @@ except NameError: # Python 3
|
|||||||
|
|
||||||
filename = sys.argv[0]
|
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'
|
VERSION_URL = UPDATE_URL + 'LATEST_VERSION'
|
||||||
JSON_URL = UPDATE_URL + 'versions.json'
|
JSON_URL = UPDATE_URL + 'versions.json'
|
||||||
UPDATES_RSA_KEY = (0x9d60ee4d8f805312fdb15a62f87b95bd66177b91df176765d13514a0f1754bcd2057295c5b6f1d35daa6742c3ffc9a82d3e118861c207995a8031e151d863c9927e304576bc80692bc8e094896fcf11b66f3e29e04e3a71e9a11558558acea1840aec37fc396fb6b65dc81a1c4144e03bd1c011de62e3f1357b327d08426fe93, 65537)
|
UPDATES_RSA_KEY = (0x9d60ee4d8f805312fdb15a62f87b95bd66177b91df176765d13514a0f1754bcd2057295c5b6f1d35daa6742c3ffc9a82d3e118861c207995a8031e151d863c9927e304576bc80692bc8e094896fcf11b66f3e29e04e3a71e9a11558558acea1840aec37fc396fb6b65dc81a1c4144e03bd1c011de62e3f1357b327d08426fe93, 65537)
|
||||||
|
@ -7,6 +7,7 @@ import math
|
|||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import shutil
|
||||||
import socket
|
import socket
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
@ -17,6 +18,7 @@ if os.name == 'nt':
|
|||||||
import ctypes
|
import ctypes
|
||||||
|
|
||||||
from .utils import *
|
from .utils import *
|
||||||
|
from .InfoExtractors import get_info_extractor
|
||||||
|
|
||||||
|
|
||||||
class FileDownloader(object):
|
class FileDownloader(object):
|
||||||
@ -52,6 +54,7 @@ class FileDownloader(object):
|
|||||||
quiet: Do not print messages to stdout.
|
quiet: Do not print messages to stdout.
|
||||||
forceurl: Force printing final URL.
|
forceurl: Force printing final URL.
|
||||||
forcetitle: Force printing title.
|
forcetitle: Force printing title.
|
||||||
|
forceid: Force printing ID.
|
||||||
forcethumbnail: Force printing thumbnail URL.
|
forcethumbnail: Force printing thumbnail URL.
|
||||||
forcedescription: Force printing description.
|
forcedescription: Force printing description.
|
||||||
forcefilename: Force printing final filename.
|
forcefilename: Force printing final filename.
|
||||||
@ -78,12 +81,18 @@ class FileDownloader(object):
|
|||||||
updatetime: Use the Last-modified header to set output file timestamps.
|
updatetime: Use the Last-modified header to set output file timestamps.
|
||||||
writedescription: Write the video description to a .description file
|
writedescription: Write the video description to a .description file
|
||||||
writeinfojson: Write the video description to a .info.json file
|
writeinfojson: Write the video description to a .info.json file
|
||||||
writesubtitles: Write the video subtitles to a .srt file
|
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
|
subtitleslang: Language of the subtitles to download
|
||||||
test: Download only first bytes to test the downloader.
|
test: Download only first bytes to test the downloader.
|
||||||
keepvideo: Keep the video file after post-processing
|
keepvideo: Keep the video file after post-processing
|
||||||
min_filesize: Skip files smaller than this size
|
min_filesize: Skip files smaller than this size
|
||||||
max_filesize: Skip files larger 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
|
params = None
|
||||||
@ -104,7 +113,7 @@ class FileDownloader(object):
|
|||||||
self.params = params
|
self.params = params
|
||||||
|
|
||||||
if '%(stitle)s' in self.params['outtmpl']:
|
if '%(stitle)s' in self.params['outtmpl']:
|
||||||
self.to_stderr(u'WARNING: %(stitle)s is deprecated. Use the %(title)s and the --restrict-filenames flag(which also secures %(uploader)s et al) instead.')
|
self.report_warning(u'%(stitle)s is deprecated. Use the %(title)s and the --restrict-filenames flag(which also secures %(uploader)s et al) instead.')
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def format_bytes(bytes):
|
def format_bytes(bytes):
|
||||||
@ -116,7 +125,7 @@ class FileDownloader(object):
|
|||||||
exponent = 0
|
exponent = 0
|
||||||
else:
|
else:
|
||||||
exponent = int(math.log(bytes, 1024.0))
|
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)
|
converted = float(bytes) / float(1024 ** exponent)
|
||||||
return '%.2f%s' % (converted, suffix)
|
return '%.2f%s' % (converted, suffix)
|
||||||
|
|
||||||
@ -208,7 +217,7 @@ class FileDownloader(object):
|
|||||||
# already of type unicode()
|
# already of type unicode()
|
||||||
ctypes.windll.kernel32.SetConsoleTitleW(ctypes.c_wchar_p(message))
|
ctypes.windll.kernel32.SetConsoleTitleW(ctypes.c_wchar_p(message))
|
||||||
elif 'TERM' in os.environ:
|
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):
|
def fixed_template(self):
|
||||||
"""Checks if the output template is fixed."""
|
"""Checks if the output template is fixed."""
|
||||||
@ -227,13 +236,47 @@ class FileDownloader(object):
|
|||||||
self.to_stderr(message)
|
self.to_stderr(message)
|
||||||
if self.params.get('verbose'):
|
if self.params.get('verbose'):
|
||||||
if tb is None:
|
if tb is None:
|
||||||
tb_data = traceback.format_list(traceback.extract_stack())
|
if sys.exc_info()[0]: # if .trouble has been called from an except block
|
||||||
tb = u''.join(tb_data)
|
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)
|
self.to_stderr(tb)
|
||||||
if not self.params.get('ignoreerrors', False):
|
if not self.params.get('ignoreerrors', False):
|
||||||
raise DownloadError(message)
|
if sys.exc_info()[0] and hasattr(sys.exc_info()[1], 'exc_info') and sys.exc_info()[1].exc_info[0]:
|
||||||
|
exc_info = sys.exc_info()[1].exc_info
|
||||||
|
else:
|
||||||
|
exc_info = sys.exc_info()
|
||||||
|
raise DownloadError(message, exc_info)
|
||||||
self._download_retcode = 1
|
self._download_retcode = 1
|
||||||
|
|
||||||
|
def report_warning(self, message):
|
||||||
|
'''
|
||||||
|
Print the message to stderr, it will be prefixed with 'WARNING:'
|
||||||
|
If stderr is a tty file the 'WARNING:' will be colored
|
||||||
|
'''
|
||||||
|
if sys.stderr.isatty() 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):
|
def slow_down(self, start_time, byte_counter):
|
||||||
"""Sleep if the download speed is over the rate limit."""
|
"""Sleep if the download speed is over the rate limit."""
|
||||||
rate_limit = self.params.get('ratelimit', None)
|
rate_limit = self.params.get('ratelimit', None)
|
||||||
@ -265,7 +308,7 @@ class FileDownloader(object):
|
|||||||
return
|
return
|
||||||
os.rename(encodeFilename(old_filename), encodeFilename(new_filename))
|
os.rename(encodeFilename(old_filename), encodeFilename(new_filename))
|
||||||
except (IOError, OSError) as err:
|
except (IOError, OSError) as err:
|
||||||
self.trouble(u'ERROR: unable to rename file')
|
self.report_error(u'unable to rename file')
|
||||||
|
|
||||||
def try_utime(self, filename, last_modified_hdr):
|
def try_utime(self, filename, last_modified_hdr):
|
||||||
"""Try to set the last-modified time of the given file."""
|
"""Try to set the last-modified time of the given file."""
|
||||||
@ -279,6 +322,9 @@ class FileDownloader(object):
|
|||||||
filetime = timeconvert(timestr)
|
filetime = timeconvert(timestr)
|
||||||
if filetime is None:
|
if filetime is None:
|
||||||
return filetime
|
return filetime
|
||||||
|
# Ignore obviously invalid dates
|
||||||
|
if filetime == 0:
|
||||||
|
return
|
||||||
try:
|
try:
|
||||||
os.utime(filename, (time.time(), filetime))
|
os.utime(filename, (time.time(), filetime))
|
||||||
except:
|
except:
|
||||||
@ -289,9 +335,9 @@ class FileDownloader(object):
|
|||||||
""" Report that the description file is being written """
|
""" Report that the description file is being written """
|
||||||
self.to_screen(u'[info] Writing video description to: ' + descfn)
|
self.to_screen(u'[info] Writing video description to: ' + descfn)
|
||||||
|
|
||||||
def report_writesubtitles(self, srtfn):
|
def report_writesubtitles(self, sub_filename):
|
||||||
""" Report that the subtitles file is being written """
|
""" Report that the subtitles file is being written """
|
||||||
self.to_screen(u'[info] Writing video subtitles to: ' + srtfn)
|
self.to_screen(u'[info] Writing video subtitles to: ' + sub_filename)
|
||||||
|
|
||||||
def report_writeinfojson(self, infofn):
|
def report_writeinfojson(self, infofn):
|
||||||
""" Report that the metadata file has been written """
|
""" Report that the metadata file has been written """
|
||||||
@ -305,8 +351,13 @@ class FileDownloader(object):
|
|||||||
"""Report download progress."""
|
"""Report download progress."""
|
||||||
if self.params.get('noprogress', False):
|
if self.params.get('noprogress', False):
|
||||||
return
|
return
|
||||||
self.to_screen(u'\r[download] %s of %s at %s ETA %s' %
|
clear_line = (u'\x1b[K' if sys.stderr.isatty() and os.name != 'nt' else u'')
|
||||||
(percent_str, data_len_str, speed_str, eta_str), skip_eol=True)
|
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' %
|
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()))
|
(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 = dict(info_dict)
|
||||||
|
|
||||||
template_dict['epoch'] = int(time.time())
|
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(
|
sanitize = lambda k,v: sanitize_filename(
|
||||||
u'NA' if v is None else compat_str(v),
|
u'NA' if v is None else compat_str(v),
|
||||||
@ -356,8 +413,11 @@ class FileDownloader(object):
|
|||||||
|
|
||||||
filename = self.params['outtmpl'] % template_dict
|
filename = self.params['outtmpl'] % template_dict
|
||||||
return filename
|
return filename
|
||||||
except (ValueError, KeyError) as err:
|
except KeyError as err:
|
||||||
self.trouble(u'ERROR: invalid system charset or erroneous output template')
|
self.report_error(u'Erroneous output template')
|
||||||
|
return None
|
||||||
|
except ValueError as err:
|
||||||
|
self.report_error(u'Insufficient system charset ' + repr(preferredencoding()))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _match_entry(self, info_dict):
|
def _match_entry(self, info_dict):
|
||||||
@ -366,18 +426,155 @@ class FileDownloader(object):
|
|||||||
title = info_dict['title']
|
title = info_dict['title']
|
||||||
matchtitle = self.params.get('matchtitle', False)
|
matchtitle = self.params.get('matchtitle', False)
|
||||||
if matchtitle:
|
if matchtitle:
|
||||||
matchtitle = matchtitle.decode('utf8')
|
|
||||||
if not re.search(matchtitle, title, re.IGNORECASE):
|
if not re.search(matchtitle, title, re.IGNORECASE):
|
||||||
return u'[download] "' + title + '" title did not match pattern "' + matchtitle + '"'
|
return u'[download] "' + title + '" title did not match pattern "' + matchtitle + '"'
|
||||||
rejecttitle = self.params.get('rejecttitle', False)
|
rejecttitle = self.params.get('rejecttitle', False)
|
||||||
if rejecttitle:
|
if rejecttitle:
|
||||||
rejecttitle = rejecttitle.decode('utf8')
|
|
||||||
if re.search(rejecttitle, title, re.IGNORECASE):
|
if re.search(rejecttitle, title, re.IGNORECASE):
|
||||||
return u'"' + title + '" title matched reject pattern "' + rejecttitle + '"'
|
return u'"' + title + '" title matched reject pattern "' + rejecttitle + '"'
|
||||||
|
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
|
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):
|
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
|
# Keep for backwards compatibility
|
||||||
info_dict['stitle'] = info_dict['title']
|
info_dict['stitle'] = info_dict['title']
|
||||||
@ -400,6 +597,8 @@ class FileDownloader(object):
|
|||||||
# Forced printings
|
# Forced printings
|
||||||
if self.params.get('forcetitle', False):
|
if self.params.get('forcetitle', False):
|
||||||
compat_print(info_dict['title'])
|
compat_print(info_dict['title'])
|
||||||
|
if self.params.get('forceid', False):
|
||||||
|
compat_print(info_dict['id'])
|
||||||
if self.params.get('forceurl', False):
|
if self.params.get('forceurl', False):
|
||||||
compat_print(info_dict['url'])
|
compat_print(info_dict['url'])
|
||||||
if self.params.get('forcethumbnail', False) and 'thumbnail' in info_dict:
|
if self.params.get('forcethumbnail', False) and 'thumbnail' in info_dict:
|
||||||
@ -420,10 +619,10 @@ class FileDownloader(object):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
dn = os.path.dirname(encodeFilename(filename))
|
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)
|
os.makedirs(dn)
|
||||||
except (OSError, IOError) as err:
|
except (OSError, IOError) as err:
|
||||||
self.trouble(u'ERROR: unable to create directory ' + compat_str(err))
|
self.report_error(u'unable to create directory ' + compat_str(err))
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.params.get('writedescription', False):
|
if self.params.get('writedescription', False):
|
||||||
@ -433,20 +632,43 @@ class FileDownloader(object):
|
|||||||
with io.open(encodeFilename(descfn), 'w', encoding='utf-8') as descfile:
|
with io.open(encodeFilename(descfn), 'w', encoding='utf-8') as descfile:
|
||||||
descfile.write(info_dict['description'])
|
descfile.write(info_dict['description'])
|
||||||
except (OSError, IOError):
|
except (OSError, IOError):
|
||||||
self.trouble(u'ERROR: Cannot write description file ' + descfn)
|
self.report_error(u'Cannot write description file ' + descfn)
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.params.get('writesubtitles', False) and 'subtitles' in info_dict and info_dict['subtitles']:
|
if self.params.get('writesubtitles', False) and 'subtitles' in info_dict and info_dict['subtitles']:
|
||||||
# subtitles download errors are already managed as troubles in relevant IE
|
# subtitles download errors are already managed as troubles in relevant IE
|
||||||
# that way it will silently go on when used with unsupporting IE
|
# that way it will silently go on when used with unsupporting IE
|
||||||
try:
|
subtitle = info_dict['subtitles'][0]
|
||||||
srtfn = filename.rsplit('.', 1)[0] + u'.srt'
|
(sub_error, sub_lang, sub) = subtitle
|
||||||
self.report_writesubtitles(srtfn)
|
sub_format = self.params.get('subtitlesformat')
|
||||||
with io.open(encodeFilename(srtfn), 'w', encoding='utf-8') as srtfile:
|
if sub_error:
|
||||||
srtfile.write(info_dict['subtitles'])
|
self.report_warning("Some error while getting the subtitles")
|
||||||
except (OSError, IOError):
|
else:
|
||||||
self.trouble(u'ERROR: Cannot write subtitles file ' + descfn)
|
try:
|
||||||
return
|
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):
|
if self.params.get('writeinfojson', False):
|
||||||
infofn = filename + u'.info.json'
|
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'])
|
json_info_dict = dict((k, v) for k,v in info_dict.items() if not k in ['urlhandle'])
|
||||||
write_json_file(json_info_dict, encodeFilename(infofn))
|
write_json_file(json_info_dict, encodeFilename(infofn))
|
||||||
except (OSError, IOError):
|
except (OSError, IOError):
|
||||||
self.trouble(u'ERROR: Cannot write metadata to JSON file ' + infofn)
|
self.report_error(u'Cannot write metadata to JSON file ' + infofn)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if 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 not self.params.get('skip_download', False):
|
||||||
if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(filename)):
|
if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(filename)):
|
||||||
success = True
|
success = True
|
||||||
@ -467,17 +703,17 @@ class FileDownloader(object):
|
|||||||
except (OSError, IOError) as err:
|
except (OSError, IOError) as err:
|
||||||
raise UnavailableVideoError()
|
raise UnavailableVideoError()
|
||||||
except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
|
except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
|
||||||
self.trouble(u'ERROR: unable to download video data: %s' % str(err))
|
self.report_error(u'unable to download video data: %s' % str(err))
|
||||||
return
|
return
|
||||||
except (ContentTooShortError, ) as err:
|
except (ContentTooShortError, ) as err:
|
||||||
self.trouble(u'ERROR: content too short (expected %s bytes and served %s)' % (err.expected, err.downloaded))
|
self.report_error(u'content too short (expected %s bytes and served %s)' % (err.expected, err.downloaded))
|
||||||
return
|
return
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
try:
|
try:
|
||||||
self.post_process(filename, info_dict)
|
self.post_process(filename, info_dict)
|
||||||
except (PostProcessingError) as err:
|
except (PostProcessingError) as err:
|
||||||
self.trouble(u'ERROR: postprocessing: %s' % str(err))
|
self.report_error(u'postprocessing: %s' % str(err))
|
||||||
return
|
return
|
||||||
|
|
||||||
def download(self, url_list):
|
def download(self, url_list):
|
||||||
@ -486,49 +722,14 @@ class FileDownloader(object):
|
|||||||
raise SameFileError(self.params['outtmpl'])
|
raise SameFileError(self.params['outtmpl'])
|
||||||
|
|
||||||
for url in url_list:
|
for url in url_list:
|
||||||
suitable_found = False
|
try:
|
||||||
for ie in self._ies:
|
#It also downloads the videos
|
||||||
# Go to next InfoExtractor if not suitable
|
videos = self.extract_info(url)
|
||||||
if not ie.suitable(url):
|
except UnavailableVideoError:
|
||||||
continue
|
self.report_error(u'unable to download video')
|
||||||
|
except MaxDownloadsReached:
|
||||||
# Warn if the _WORKING attribute is False
|
self.to_screen(u'[info] Maximum number of downloaded files reached.')
|
||||||
if not ie.working():
|
raise
|
||||||
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)
|
|
||||||
|
|
||||||
return self._download_retcode
|
return self._download_retcode
|
||||||
|
|
||||||
@ -550,31 +751,36 @@ class FileDownloader(object):
|
|||||||
self.to_stderr(u'ERROR: ' + e.msg)
|
self.to_stderr(u'ERROR: ' + e.msg)
|
||||||
if keep_video is False and not self.params.get('keepvideo', False):
|
if keep_video is False and not self.params.get('keepvideo', False):
|
||||||
try:
|
try:
|
||||||
self.to_stderr(u'Deleting original file %s (pass -k to keep)' % filename)
|
self.to_screen(u'Deleting original file %s (pass -k to keep)' % filename)
|
||||||
os.remove(encodeFilename(filename))
|
os.remove(encodeFilename(filename))
|
||||||
except (IOError, OSError):
|
except (IOError, OSError):
|
||||||
self.to_stderr(u'WARNING: Unable to remove downloaded video file')
|
self.report_warning(u'Unable to remove downloaded video file')
|
||||||
|
|
||||||
def _download_with_rtmpdump(self, filename, url, player_url, page_url):
|
def _download_with_rtmpdump(self, filename, url, player_url, page_url, play_path, tc_url):
|
||||||
self.report_destination(filename)
|
self.report_destination(filename)
|
||||||
tmpfilename = self.temp_name(filename)
|
tmpfilename = self.temp_name(filename)
|
||||||
|
|
||||||
# Check for rtmpdump first
|
# Check for rtmpdump first
|
||||||
try:
|
try:
|
||||||
subprocess.call(['rtmpdump', '-h'], stdout=(file(os.path.devnull, 'w')), stderr=subprocess.STDOUT)
|
subprocess.call(['rtmpdump', '-h'], stdout=(open(os.path.devnull, 'w')), stderr=subprocess.STDOUT)
|
||||||
except (OSError, IOError):
|
except (OSError, IOError):
|
||||||
self.trouble(u'ERROR: RTMP download detected but "rtmpdump" could not be run')
|
self.report_error(u'RTMP download detected but "rtmpdump" could not be run')
|
||||||
return False
|
return False
|
||||||
|
verbosity_option = '--verbose' if self.params.get('verbose', False) else '--quiet'
|
||||||
|
|
||||||
# Download using rtmpdump. rtmpdump returns exit code 2 when
|
# Download using rtmpdump. rtmpdump returns exit code 2 when
|
||||||
# the connection was interrumpted and resuming appears to be
|
# the connection was interrumpted and resuming appears to be
|
||||||
# possible. This is part of rtmpdump's normal usage, AFAIK.
|
# 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:
|
if player_url is not None:
|
||||||
basic_args += ['-W', player_url]
|
basic_args += ['--swfVfy', player_url]
|
||||||
if page_url is not None:
|
if page_url is not None:
|
||||||
basic_args += ['--pageUrl', page_url]
|
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):
|
if self.params.get('verbose', False):
|
||||||
try:
|
try:
|
||||||
import pipes
|
import pipes
|
||||||
@ -608,9 +814,41 @@ class FileDownloader(object):
|
|||||||
})
|
})
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
self.trouble(u'\nERROR: rtmpdump exited with code %d' % retval)
|
self.to_stderr(u"\n")
|
||||||
|
self.report_error(u'rtmpdump exited with code %d' % retval)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def _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):
|
def _do_download(self, filename, info_dict):
|
||||||
url = info_dict['url']
|
url = info_dict['url']
|
||||||
|
|
||||||
@ -627,7 +865,13 @@ class FileDownloader(object):
|
|||||||
if url.startswith('rtmp'):
|
if url.startswith('rtmp'):
|
||||||
return self._download_with_rtmpdump(filename, url,
|
return self._download_with_rtmpdump(filename, url,
|
||||||
info_dict.get('player_url', None),
|
info_dict.get('player_url', None),
|
||||||
info_dict.get('page_url', None))
|
info_dict.get('page_url', None),
|
||||||
|
info_dict.get('play_path', None),
|
||||||
|
info_dict.get('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)
|
tmpfilename = self.temp_name(filename)
|
||||||
stream = None
|
stream = None
|
||||||
@ -708,7 +952,7 @@ class FileDownloader(object):
|
|||||||
self.report_retry(count, retries)
|
self.report_retry(count, retries)
|
||||||
|
|
||||||
if count > retries:
|
if count > retries:
|
||||||
self.trouble(u'ERROR: giving up after %s retries' % retries)
|
self.report_error(u'giving up after %s retries' % retries)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
data_len = data.info().get('Content-length', None)
|
data_len = data.info().get('Content-length', None)
|
||||||
@ -744,12 +988,13 @@ class FileDownloader(object):
|
|||||||
filename = self.undo_temp_name(tmpfilename)
|
filename = self.undo_temp_name(tmpfilename)
|
||||||
self.report_destination(filename)
|
self.report_destination(filename)
|
||||||
except (OSError, IOError) as err:
|
except (OSError, IOError) as err:
|
||||||
self.trouble(u'ERROR: unable to open for writing: %s' % str(err))
|
self.report_error(u'unable to open for writing: %s' % str(err))
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
stream.write(data_block)
|
stream.write(data_block)
|
||||||
except (IOError, OSError) as err:
|
except (IOError, OSError) as err:
|
||||||
self.trouble(u'\nERROR: unable to write data: %s' % str(err))
|
self.to_stderr(u"\n")
|
||||||
|
self.report_error(u'unable to write data: %s' % str(err))
|
||||||
return False
|
return False
|
||||||
if not self.params.get('noresizebuffer', False):
|
if not self.params.get('noresizebuffer', False):
|
||||||
block_size = self.best_block_size(after - before, len(data_block))
|
block_size = self.best_block_size(after - before, len(data_block))
|
||||||
@ -775,7 +1020,8 @@ class FileDownloader(object):
|
|||||||
self.slow_down(start, byte_counter - resume_len)
|
self.slow_down(start, byte_counter - resume_len)
|
||||||
|
|
||||||
if stream is None:
|
if stream is None:
|
||||||
self.trouble(u'\nERROR: Did not get any data blocks')
|
self.to_stderr(u"\n")
|
||||||
|
self.report_error(u'Did not get any data blocks')
|
||||||
return False
|
return False
|
||||||
stream.close()
|
stream.close()
|
||||||
self.report_finish()
|
self.report_finish()
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -85,8 +85,9 @@ class FFmpegPostProcessor(PostProcessor):
|
|||||||
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
stdout,stderr = p.communicate()
|
stdout,stderr = p.communicate()
|
||||||
if p.returncode != 0:
|
if p.returncode != 0:
|
||||||
|
stderr = stderr.decode('utf-8', 'replace')
|
||||||
msg = stderr.strip().split('\n')[-1]
|
msg = stderr.strip().split('\n')[-1]
|
||||||
raise FFmpegPostProcessorError(msg.decode('utf-8', 'replace'))
|
raise FFmpegPostProcessorError(msg)
|
||||||
|
|
||||||
def _ffmpeg_filename_argument(self, fn):
|
def _ffmpeg_filename_argument(self, fn):
|
||||||
# ffmpeg broke --, see https://ffmpeg.org/trac/ffmpeg/ticket/2127 for details
|
# 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
|
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
|
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:
|
try:
|
||||||
if self._nopostoverwrites and os.path.exists(encodeFilename(new_path)):
|
if self._nopostoverwrites and os.path.exists(encodeFilename(new_path)):
|
||||||
self._downloader.to_screen(u'[youtube] Post-process file %s exists, skipping' % 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')
|
self._downloader.to_stderr(u'WARNING: Cannot update utime of audio file')
|
||||||
|
|
||||||
information['filepath'] = new_path
|
information['filepath'] = new_path
|
||||||
return False,information
|
return self._nopostoverwrites,information
|
||||||
|
|
||||||
class FFmpegVideoConvertor(FFmpegPostProcessor):
|
class FFmpegVideoConvertor(FFmpegPostProcessor):
|
||||||
def __init__(self, downloader=None,preferedformat=None):
|
def __init__(self, downloader=None,preferedformat=None):
|
||||||
|
@ -23,10 +23,16 @@ __authors__ = (
|
|||||||
'Dave Vasilevsky',
|
'Dave Vasilevsky',
|
||||||
'Jaime Marquínez Ferrándiz',
|
'Jaime Marquínez Ferrándiz',
|
||||||
'Jeff Crouse',
|
'Jeff Crouse',
|
||||||
|
'Osama Khalid',
|
||||||
|
'Michael Walter',
|
||||||
|
'M. Yasoob Ullah Khalid',
|
||||||
|
'Julien Fraichard',
|
||||||
|
'Johny Mo Swag',
|
||||||
)
|
)
|
||||||
|
|
||||||
__license__ = 'Public Domain'
|
__license__ = 'Public Domain'
|
||||||
|
|
||||||
|
import codecs
|
||||||
import getpass
|
import getpass
|
||||||
import optparse
|
import optparse
|
||||||
import os
|
import os
|
||||||
@ -45,7 +51,7 @@ from .FileDownloader import *
|
|||||||
from .InfoExtractors import gen_extractors
|
from .InfoExtractors import gen_extractors
|
||||||
from .PostProcessor import *
|
from .PostProcessor import *
|
||||||
|
|
||||||
def parseOpts():
|
def parseOpts(overrideArguments=None):
|
||||||
def _readOptions(filename_bytes):
|
def _readOptions(filename_bytes):
|
||||||
try:
|
try:
|
||||||
optionf = open(filename_bytes)
|
optionf = open(filename_bytes)
|
||||||
@ -125,7 +131,7 @@ def parseOpts():
|
|||||||
general.add_option('-i', '--ignore-errors',
|
general.add_option('-i', '--ignore-errors',
|
||||||
action='store_true', dest='ignoreerrors', help='continue on download errors', default=False)
|
action='store_true', dest='ignoreerrors', help='continue on download errors', default=False)
|
||||||
general.add_option('-r', '--rate-limit',
|
general.add_option('-r', '--rate-limit',
|
||||||
dest='ratelimit', metavar='LIMIT', help='download rate limit (e.g. 50k or 44.6m)')
|
dest='ratelimit', metavar='LIMIT', help='maximum download rate (e.g. 50k or 44.6m)')
|
||||||
general.add_option('-R', '--retries',
|
general.add_option('-R', '--retries',
|
||||||
dest='retries', metavar='RETRIES', help='number of retries (default is %default)', default=10)
|
dest='retries', metavar='RETRIES', help='number of retries (default is %default)', default=10)
|
||||||
general.add_option('--buffer-size',
|
general.add_option('--buffer-size',
|
||||||
@ -138,9 +144,14 @@ def parseOpts():
|
|||||||
help='display the current browser identification', default=False)
|
help='display the current browser identification', default=False)
|
||||||
general.add_option('--user-agent',
|
general.add_option('--user-agent',
|
||||||
dest='user_agent', help='specify a custom user agent', metavar='UA')
|
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',
|
general.add_option('--list-extractors',
|
||||||
action='store_true', dest='list_extractors',
|
action='store_true', dest='list_extractors',
|
||||||
help='List all supported extractors and the URLs they would handle', default=False)
|
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)
|
general.add_option('--test', action='store_true', dest='test', default=False, help=optparse.SUPPRESS_HELP)
|
||||||
|
|
||||||
selection.add_option('--playlist-start',
|
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('--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('--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('--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',
|
authentication.add_option('-u', '--username',
|
||||||
@ -163,7 +177,8 @@ def parseOpts():
|
|||||||
|
|
||||||
|
|
||||||
video_format.add_option('-f', '--format',
|
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',
|
video_format.add_option('--all-formats',
|
||||||
action='store_const', dest='format', help='download all available video formats', const='all')
|
action='store_const', dest='format', help='download all available video formats', const='all')
|
||||||
video_format.add_option('--prefer-free-formats',
|
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')
|
action='store', dest='format_limit', metavar='FORMAT', help='highest quality format to download')
|
||||||
video_format.add_option('-F', '--list-formats',
|
video_format.add_option('-F', '--list-formats',
|
||||||
action='store_true', dest='listformats', help='list all available formats (currently youtube only)')
|
action='store_true', dest='listformats', help='list all available formats (currently youtube only)')
|
||||||
video_format.add_option('--write-srt',
|
video_format.add_option('--write-sub', '--write-srt',
|
||||||
action='store_true', dest='writesubtitles',
|
action='store_true', dest='writesubtitles',
|
||||||
help='write video closed captions to a .srt file (currently youtube only)', default=False)
|
help='write subtitle file (currently youtube only)', default=False)
|
||||||
video_format.add_option('--srt-lang',
|
video_format.add_option('--only-sub',
|
||||||
|
action='store_true', dest='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',
|
action='store', dest='subtitleslang', metavar='LANG',
|
||||||
help='language of the closed captions to download (optional) use IETF language tags like \'en\'')
|
help='language of the subtitles to download (optional) use IETF language tags like \'en\'')
|
||||||
|
|
||||||
verbosity.add_option('-q', '--quiet',
|
verbosity.add_option('-q', '--quiet',
|
||||||
action='store_true', dest='quiet', help='activates quiet mode', default=False)
|
action='store_true', dest='quiet', help='activates quiet mode', default=False)
|
||||||
@ -189,6 +216,8 @@ def parseOpts():
|
|||||||
action='store_true', dest='geturl', help='simulate, quiet but print URL', default=False)
|
action='store_true', dest='geturl', help='simulate, quiet but print URL', default=False)
|
||||||
verbosity.add_option('-e', '--get-title',
|
verbosity.add_option('-e', '--get-title',
|
||||||
action='store_true', dest='gettitle', help='simulate, quiet but print title', default=False)
|
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',
|
verbosity.add_option('--get-thumbnail',
|
||||||
action='store_true', dest='getthumbnail',
|
action='store_true', dest='getthumbnail',
|
||||||
help='simulate, quiet but print thumbnail URL', default=False)
|
help='simulate, quiet but print thumbnail URL', default=False)
|
||||||
@ -201,6 +230,8 @@ def parseOpts():
|
|||||||
verbosity.add_option('--get-format',
|
verbosity.add_option('--get-format',
|
||||||
action='store_true', dest='getformat',
|
action='store_true', dest='getformat',
|
||||||
help='simulate, quiet but print output format', default=False)
|
help='simulate, quiet but print output format', default=False)
|
||||||
|
verbosity.add_option('--newline',
|
||||||
|
action='store_true', dest='progress_with_newline', help='output progress bar as new lines', default=False)
|
||||||
verbosity.add_option('--no-progress',
|
verbosity.add_option('--no-progress',
|
||||||
action='store_true', dest='noprogress', help='do not print progress bar', default=False)
|
action='store_true', dest='noprogress', help='do not print progress bar', default=False)
|
||||||
verbosity.add_option('--console-title',
|
verbosity.add_option('--console-title',
|
||||||
@ -208,19 +239,33 @@ def parseOpts():
|
|||||||
help='display progress in console titlebar', default=False)
|
help='display progress in console titlebar', default=False)
|
||||||
verbosity.add_option('-v', '--verbose',
|
verbosity.add_option('-v', '--verbose',
|
||||||
action='store_true', dest='verbose', help='print various debugging information', default=False)
|
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',
|
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',
|
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',
|
filesystem.add_option('-l', '--literal',
|
||||||
action='store_true', dest='usetitle', help='[deprecated] alias of --title', default=False)
|
action='store_true', dest='usetitle', help='[deprecated] alias of --title', default=False)
|
||||||
filesystem.add_option('-A', '--auto-number',
|
filesystem.add_option('-A', '--auto-number',
|
||||||
action='store_true', dest='autonumber',
|
action='store_true', dest='autonumber',
|
||||||
help='number downloaded files starting from 00000', default=False)
|
help='number downloaded files starting from 00000', default=False)
|
||||||
filesystem.add_option('-o', '--output',
|
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',
|
filesystem.add_option('--restrict-filenames',
|
||||||
action='store_true', dest='restrictfilenames',
|
action='store_true', dest='restrictfilenames',
|
||||||
help='Restrict filenames to only ASCII characters, and avoid "&" and spaces in filenames', default=False)
|
help='Restrict filenames to only ASCII characters, and avoid "&" and spaces in filenames', default=False)
|
||||||
@ -246,6 +291,9 @@ def parseOpts():
|
|||||||
filesystem.add_option('--write-info-json',
|
filesystem.add_option('--write-info-json',
|
||||||
action='store_true', dest='writeinfojson',
|
action='store_true', dest='writeinfojson',
|
||||||
help='write video metadata to a .info.json file', default=False)
|
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,
|
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(authentication)
|
||||||
parser.add_option_group(postproc)
|
parser.add_option_group(postproc)
|
||||||
|
|
||||||
xdg_config_home = os.environ.get('XDG_CONFIG_HOME')
|
if overrideArguments is not None:
|
||||||
if xdg_config_home:
|
opts, args = parser.parse_args(overrideArguments)
|
||||||
userConf = os.path.join(xdg_config_home, 'youtube-dl.conf')
|
if opts.verbose:
|
||||||
|
print(u'[debug] Override config: ' + repr(overrideArguments))
|
||||||
else:
|
else:
|
||||||
userConf = os.path.join(os.path.expanduser('~'), '.config', 'youtube-dl.conf')
|
xdg_config_home = os.environ.get('XDG_CONFIG_HOME')
|
||||||
argv = _readOptions('/etc/youtube-dl.conf') + _readOptions(userConf) + sys.argv[1:]
|
if xdg_config_home:
|
||||||
opts, args = parser.parse_args(argv)
|
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
|
return parser, opts, args
|
||||||
|
|
||||||
def _real_main():
|
def _real_main(argv=None):
|
||||||
parser, opts, args = parseOpts()
|
# 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
|
# Open appropriate CookieJar
|
||||||
if opts.cookiefile is None:
|
if opts.cookiefile is None:
|
||||||
@ -300,6 +365,10 @@ def _real_main():
|
|||||||
if opts.user_agent is not None:
|
if opts.user_agent is not None:
|
||||||
std_headers['User-Agent'] = opts.user_agent
|
std_headers['User-Agent'] = opts.user_agent
|
||||||
|
|
||||||
|
# Set referer
|
||||||
|
if opts.referer is not None:
|
||||||
|
std_headers['Referer'] = opts.referer
|
||||||
|
|
||||||
# Dump user agent
|
# Dump user agent
|
||||||
if opts.dump_user_agent:
|
if opts.dump_user_agent:
|
||||||
print(std_headers['User-Agent'])
|
print(std_headers['User-Agent'])
|
||||||
@ -323,8 +392,19 @@ def _real_main():
|
|||||||
|
|
||||||
# General configuration
|
# General configuration
|
||||||
cookie_processor = compat_urllib_request.HTTPCookieProcessor(jar)
|
cookie_processor = compat_urllib_request.HTTPCookieProcessor(jar)
|
||||||
proxy_handler = compat_urllib_request.ProxyHandler()
|
if opts.proxy is not None:
|
||||||
opener = compat_urllib_request.build_opener(proxy_handler, cookie_processor, YoutubeDLHandler())
|
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)
|
compat_urllib_request.install_opener(opener)
|
||||||
socket.setdefaulttimeout(300) # 5 minutes should be enough (famous last words)
|
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 is not None:
|
||||||
if opts.recodevideo not in ['mp4', 'flv', 'webm', 'ogg']:
|
if opts.recodevideo not in ['mp4', 'flv', 'webm', 'ogg']:
|
||||||
parser.error(u'invalid video recode format specified')
|
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,):
|
if sys.version_info < (3,):
|
||||||
# In Python 2, sys.argv is a bytestring (also note http://bugs.python.org/issue2128 for Windows systems)
|
# 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.usetitle and u'%(title)s-%(id)s.%(ext)s')
|
||||||
or (opts.useid and u'%(id)s.%(ext)s')
|
or (opts.useid and u'%(id)s.%(ext)s')
|
||||||
or (opts.autonumber and u'%(autonumber)s-%(id)s.%(ext)s')
|
or (opts.autonumber and u'%(autonumber)s-%(id)s.%(ext)s')
|
||||||
or u'%(id)s.%(ext)s')
|
or u'%(title)s-%(id)s.%(ext)s')
|
||||||
|
|
||||||
# File downloader
|
# File downloader
|
||||||
fd = FileDownloader({
|
fd = FileDownloader({
|
||||||
'usenetrc': opts.usenetrc,
|
'usenetrc': opts.usenetrc,
|
||||||
'username': opts.username,
|
'username': opts.username,
|
||||||
'password': opts.password,
|
'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,
|
'forceurl': opts.geturl,
|
||||||
'forcetitle': opts.gettitle,
|
'forcetitle': opts.gettitle,
|
||||||
|
'forceid': opts.getid,
|
||||||
'forcethumbnail': opts.getthumbnail,
|
'forcethumbnail': opts.getthumbnail,
|
||||||
'forcedescription': opts.getdescription,
|
'forcedescription': opts.getdescription,
|
||||||
'forcefilename': opts.getfilename,
|
'forcefilename': opts.getfilename,
|
||||||
'forceformat': opts.getformat,
|
'forceformat': opts.getformat,
|
||||||
'simulate': opts.simulate,
|
'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': opts.format,
|
||||||
'format_limit': opts.format_limit,
|
'format_limit': opts.format_limit,
|
||||||
'listformats': opts.listformats,
|
'listformats': opts.listformats,
|
||||||
'outtmpl': outtmpl,
|
'outtmpl': outtmpl,
|
||||||
|
'autonumber_size': opts.autonumber_size,
|
||||||
'restrictfilenames': opts.restrictfilenames,
|
'restrictfilenames': opts.restrictfilenames,
|
||||||
'ignoreerrors': opts.ignoreerrors,
|
'ignoreerrors': opts.ignoreerrors,
|
||||||
'ratelimit': opts.ratelimit,
|
'ratelimit': opts.ratelimit,
|
||||||
@ -437,6 +524,7 @@ def _real_main():
|
|||||||
'noresizebuffer': opts.noresizebuffer,
|
'noresizebuffer': opts.noresizebuffer,
|
||||||
'continuedl': opts.continue_dl,
|
'continuedl': opts.continue_dl,
|
||||||
'noprogress': opts.noprogress,
|
'noprogress': opts.noprogress,
|
||||||
|
'progress_with_newline': opts.progress_with_newline,
|
||||||
'playliststart': opts.playliststart,
|
'playliststart': opts.playliststart,
|
||||||
'playlistend': opts.playlistend,
|
'playlistend': opts.playlistend,
|
||||||
'logtostderr': opts.outtmpl == '-',
|
'logtostderr': opts.outtmpl == '-',
|
||||||
@ -445,17 +533,23 @@ def _real_main():
|
|||||||
'updatetime': opts.updatetime,
|
'updatetime': opts.updatetime,
|
||||||
'writedescription': opts.writedescription,
|
'writedescription': opts.writedescription,
|
||||||
'writeinfojson': opts.writeinfojson,
|
'writeinfojson': opts.writeinfojson,
|
||||||
|
'writethumbnail': opts.writethumbnail,
|
||||||
'writesubtitles': opts.writesubtitles,
|
'writesubtitles': opts.writesubtitles,
|
||||||
|
'allsubtitles': opts.allsubtitles,
|
||||||
|
'listsubtitles': opts.listsubtitles,
|
||||||
|
'subtitlesformat': opts.subtitlesformat,
|
||||||
'subtitleslang': opts.subtitleslang,
|
'subtitleslang': opts.subtitleslang,
|
||||||
'matchtitle': opts.matchtitle,
|
'matchtitle': decodeOption(opts.matchtitle),
|
||||||
'rejecttitle': opts.rejecttitle,
|
'rejecttitle': decodeOption(opts.rejecttitle),
|
||||||
'max_downloads': opts.max_downloads,
|
'max_downloads': opts.max_downloads,
|
||||||
'prefer_free_formats': opts.prefer_free_formats,
|
'prefer_free_formats': opts.prefer_free_formats,
|
||||||
'verbose': opts.verbose,
|
'verbose': opts.verbose,
|
||||||
|
'dump_intermediate_pages': opts.dump_intermediate_pages,
|
||||||
'test': opts.test,
|
'test': opts.test,
|
||||||
'keepvideo': opts.keepvideo,
|
'keepvideo': opts.keepvideo,
|
||||||
'min_filesize': opts.min_filesize,
|
'min_filesize': opts.min_filesize,
|
||||||
'max_filesize': opts.max_filesize
|
'max_filesize': opts.max_filesize,
|
||||||
|
'daterange': date,
|
||||||
})
|
})
|
||||||
|
|
||||||
if opts.verbose:
|
if opts.verbose:
|
||||||
@ -507,9 +601,9 @@ def _real_main():
|
|||||||
|
|
||||||
sys.exit(retcode)
|
sys.exit(retcode)
|
||||||
|
|
||||||
def main():
|
def main(argv=None):
|
||||||
try:
|
try:
|
||||||
_real_main()
|
_real_main(argv)
|
||||||
except DownloadError:
|
except DownloadError:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
except SameFileError:
|
except SameFileError:
|
||||||
|
@ -9,7 +9,8 @@ import sys
|
|||||||
if __package__ is None and not hasattr(sys, "frozen"):
|
if __package__ is None and not hasattr(sys, "frozen"):
|
||||||
# direct call of __main__.py
|
# direct call of __main__.py
|
||||||
import os.path
|
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
|
import youtube_dl
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ def rsa_verify(message, signature, key):
|
|||||||
def update_self(to_screen, verbose, filename):
|
def update_self(to_screen, verbose, filename):
|
||||||
"""Update the program file with the latest version from the repository"""
|
"""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'
|
VERSION_URL = UPDATE_URL + 'LATEST_VERSION'
|
||||||
JSON_URL = UPDATE_URL + 'versions.json'
|
JSON_URL = UPDATE_URL + 'versions.json'
|
||||||
UPDATES_RSA_KEY = (0x9d60ee4d8f805312fdb15a62f87b95bd66177b91df176765d13514a0f1754bcd2057295c5b6f1d35daa6742c3ffc9a82d3e118861c207995a8031e151d863c9927e304576bc80692bc8e094896fcf11b66f3e29e04e3a71e9a11558558acea1840aec37fc396fb6b65dc81a1c4144e03bd1c011de62e3f1357b327d08426fe93, 65537)
|
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'] + '...')
|
to_screen(u'Updating to version ' + versions_info['latest'] + '...')
|
||||||
version = versions_info['versions'][versions_info['latest']]
|
version = versions_info['versions'][versions_info['latest']]
|
||||||
if version.get('notes'):
|
|
||||||
to_screen(u'PLEASE NOTE:')
|
print_notes(to_screen, versions_info['versions'])
|
||||||
for note in version['notes']:
|
|
||||||
to_screen(note)
|
|
||||||
|
|
||||||
if not os.access(filename, os.W_OK):
|
if not os.access(filename, os.W_OK):
|
||||||
to_screen(u'ERROR: no write permissions on %s' % filename)
|
to_screen(u'ERROR: no write permissions on %s' % filename)
|
||||||
@ -158,3 +156,17 @@ del "%s"
|
|||||||
return
|
return
|
||||||
|
|
||||||
to_screen(u'Updated youtube-dl. Restart youtube-dl to use the new version.')
|
to_screen(u'Updated youtube-dl. Restart youtube-dl to use the new version.')
|
||||||
|
|
||||||
|
def 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
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import errno
|
||||||
import gzip
|
import gzip
|
||||||
import io
|
import io
|
||||||
import json
|
import json
|
||||||
@ -11,7 +12,8 @@ import sys
|
|||||||
import traceback
|
import traceback
|
||||||
import zlib
|
import zlib
|
||||||
import email.utils
|
import email.utils
|
||||||
import json
|
import socket
|
||||||
|
import datetime
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import urllib.request as compat_urllib_request
|
import urllib.request as compat_urllib_request
|
||||||
@ -148,6 +150,13 @@ try:
|
|||||||
except NameError:
|
except NameError:
|
||||||
compat_chr = chr
|
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 = {
|
std_headers = {
|
||||||
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:10.0) Gecko/20100101 Firefox/10.0',
|
'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',
|
'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)
|
html = re.sub('<.*?>', '', html)
|
||||||
# Replace html entities
|
# Replace html entities
|
||||||
html = unescapeHTML(html)
|
html = unescapeHTML(html)
|
||||||
return html
|
return html.strip()
|
||||||
|
|
||||||
|
|
||||||
def sanitize_open(filename, open_mode):
|
def sanitize_open(filename, open_mode):
|
||||||
@ -329,16 +338,24 @@ def sanitize_open(filename, open_mode):
|
|||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
import msvcrt
|
import msvcrt
|
||||||
msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
|
msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
|
||||||
return (sys.stdout, filename)
|
return (sys.stdout.buffer if hasattr(sys.stdout, 'buffer') else sys.stdout, filename)
|
||||||
stream = open(encodeFilename(filename), open_mode)
|
stream = open(encodeFilename(filename), open_mode)
|
||||||
return (stream, filename)
|
return (stream, filename)
|
||||||
except (IOError, OSError) as err:
|
except (IOError, OSError) as err:
|
||||||
# In case of error, try to remove win32 forbidden chars
|
if err.errno in (errno.EACCES,):
|
||||||
filename = re.sub(u'[/<>:"\\|\\\\?\\*]', u'#', filename)
|
raise
|
||||||
|
|
||||||
# An exception here should be caught in the caller
|
# In case of error, try to remove win32 forbidden chars
|
||||||
stream = open(encodeFilename(filename), open_mode)
|
alt_filename = os.path.join(
|
||||||
return (stream, filename)
|
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):
|
def timeconvert(timestr):
|
||||||
@ -420,13 +437,48 @@ def encodeFilename(s):
|
|||||||
encoding = 'utf-8'
|
encoding = 'utf-8'
|
||||||
return s.encode(encoding, 'ignore')
|
return s.encode(encoding, 'ignore')
|
||||||
|
|
||||||
|
def decodeOption(optval):
|
||||||
|
if optval is None:
|
||||||
|
return optval
|
||||||
|
if isinstance(optval, bytes):
|
||||||
|
optval = optval.decode(preferredencoding())
|
||||||
|
|
||||||
|
assert isinstance(optval, compat_str)
|
||||||
|
return optval
|
||||||
|
|
||||||
|
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):
|
class ExtractorError(Exception):
|
||||||
"""Error during info extraction."""
|
"""Error during info extraction."""
|
||||||
def __init__(self, msg, tb=None):
|
def __init__(self, msg, tb=None):
|
||||||
""" tb, if given, is the original traceback (so that it can be printed out). """
|
""" tb, if given, is the original traceback (so that it can be printed out). """
|
||||||
|
|
||||||
|
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)
|
super(ExtractorError, self).__init__(msg)
|
||||||
|
|
||||||
self.traceback = tb
|
self.traceback = tb
|
||||||
|
self.exc_info = sys.exc_info() # preserve original exception
|
||||||
|
|
||||||
def format_traceback(self):
|
def format_traceback(self):
|
||||||
if self.traceback is None:
|
if self.traceback is None:
|
||||||
@ -441,7 +493,10 @@ class DownloadError(Exception):
|
|||||||
configured to continue on errors. They will contain the appropriate
|
configured to continue on errors. They will contain the appropriate
|
||||||
error message.
|
error message.
|
||||||
"""
|
"""
|
||||||
pass
|
def __init__(self, msg, exc_info=None):
|
||||||
|
""" exc_info, if given, is the original exception that caused the trouble (as returned by sys.exc_info()). """
|
||||||
|
super(DownloadError, self).__init__(msg)
|
||||||
|
self.exc_info = exc_info
|
||||||
|
|
||||||
|
|
||||||
class SameFileError(Exception):
|
class SameFileError(Exception):
|
||||||
@ -556,3 +611,70 @@ class YoutubeDLHandler(compat_urllib_request.HTTPHandler):
|
|||||||
|
|
||||||
https_request = http_request
|
https_request = http_request
|
||||||
https_response = http_response
|
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