Compare commits
436 Commits
2014.06.24
...
2014.08.23
Author | SHA1 | Date | |
---|---|---|---|
49f3c16543 | |||
2ef6fcb5d8 | |||
38fc045253 | |||
af1fd929c6 | |||
b7b04c9234 | |||
bc0bb6fd30 | |||
430826c9d4 | |||
68909f0c4e | |||
9d048a17d8 | |||
492641d10a | |||
2b9faf5542 | |||
ed2d6a1960 | |||
be843678b1 | |||
c71dfccc98 | |||
1a9ccac7c1 | |||
e330d59abb | |||
394df6d7d0 | |||
218f754940 | |||
a053c3493a | |||
50b294aab8 | |||
756b046f3e | |||
388ac0b18a | |||
ad06434bd3 | |||
bd9820c937 | |||
deda8ac376 | |||
e05f693942 | |||
b27295d2ab | |||
ace52c5713 | |||
e62e150f64 | |||
c44c0a775d | |||
5fcf2dbed0 | |||
91dff03217 | |||
a200f4cee2 | |||
ea6e8d5454 | |||
83d35817f5 | |||
76beff70a8 | |||
61882bf7c6 | |||
cab317a680 | |||
73159f99cc | |||
c15235cd07 | |||
12c3ec3382 | |||
55db73efdf | |||
af40ac054a | |||
a36819731b | |||
181c8655c7 | |||
3b95347bb6 | |||
3b88ee9a7d | |||
55c49908d2 | |||
db9b0b67b7 | |||
35f76e0061 | |||
3f338cd6de | |||
1d01f26ab1 | |||
266c71f971 | |||
e8ee972c6e | |||
f83dda12ad | |||
696d49815e | |||
fe556f1b0c | |||
d5638d974f | |||
938dd254e5 | |||
6493f5d704 | |||
cd6b48365e | |||
4d9bd478f9 | |||
c1d293cfa6 | |||
49807b4ac6 | |||
c990bb3633 | |||
af8322d2f9 | |||
df866e7f2a | |||
664718ff63 | |||
3258263371 | |||
3cfafc4a9b | |||
6f600ff5d6 | |||
90e075da3a | |||
9572013de9 | |||
3a5beb0ca1 | |||
a6da7b6b96 | |||
173a7026d5 | |||
40a90862f4 | |||
511c4325dc | |||
85a699246a | |||
4dc5286e13 | |||
c767dc74b8 | |||
56ca04f662 | |||
eb3680123a | |||
f5273890ee | |||
c7a088a816 | |||
fb17b60811 | |||
1e58804260 | |||
31bf213032 | |||
1cccc41ddc | |||
a91cf27767 | |||
64d02399d8 | |||
5961017202 | |||
d9760fd43c | |||
d42b2d2985 | |||
cccfab6412 | |||
4665664c92 | |||
0adc996bc3 | |||
b42a2a720b | |||
37edd7dd4a | |||
f87b3500c5 | |||
66420a2db4 | |||
6b8492a782 | |||
6de0595eb8 | |||
e48a2c646d | |||
0f831a1a92 | |||
1ce464aba9 | |||
6994e70651 | |||
3e510af38d | |||
5ecd7b0a92 | |||
a229909fa6 | |||
548f31d99c | |||
78b296b0ff | |||
be79b07907 | |||
5537dce84d | |||
493987fefe | |||
c97797a737 | |||
8d7d9d3452 | |||
7a5e7b303c | |||
61aabb9d70 | |||
62af3a0eb5 | |||
60064c53f1 | |||
98eb1c3fa2 | |||
201e9eaa0e | |||
9afa6ede21 | |||
f4776371ae | |||
328a20bf9c | |||
5622f29ae4 | |||
b4f23afbd1 | |||
0138968a6a | |||
c3f0b12b0f | |||
4f31d0f2b7 | |||
bff74bdd1a | |||
10b04ff7f4 | |||
1f7ccb9014 | |||
c7b3209668 | |||
895ba7d1dd | |||
a2a1b0baa2 | |||
8646eb790e | |||
27ace98f51 | |||
a00d73c8c8 | |||
f036a6328e | |||
31bb8d3f51 | |||
4958ae2058 | |||
7e8d73c183 | |||
65bc504db8 | |||
0fc74a0d91 | |||
8d2cc6fbb1 | |||
a954584f63 | |||
cb3ff6fb01 | |||
71aa656d13 | |||
366b1f3cfe | |||
64ce58db38 | |||
11b85ce62e | |||
1220352ff7 | |||
8f3034d871 | |||
7fa547ab02 | |||
3182f3e2dc | |||
cbf915f3f6 | |||
b490b8849a | |||
5d2519e5bf | |||
c3415d1bac | |||
36f3542883 | |||
4cb71e9b6a | |||
4bc7009e8a | |||
16f8e9df8a | |||
b081cebefa | |||
916c145217 | |||
4192b51c7c | |||
052421ff09 | |||
4e99f48817 | |||
a11165ecc6 | |||
fbb2fc5580 | |||
2fe3d240cc | |||
42f4dcfe41 | |||
892e3192fb | |||
7272eab9d0 | |||
ebe832dc37 | |||
825abb8175 | |||
8944ec0109 | |||
c084c93402 | |||
7e660ac113 | |||
d799b47b82 | |||
b7f8116406 | |||
6db274e057 | |||
0c92b57398 | |||
becafcbf0f | |||
92a86f4c1a | |||
dfe029a62c | |||
b0472057a3 | |||
c081b35c27 | |||
9f43890bcd | |||
94a20aa5f8 | |||
94e8df3a7e | |||
37e64addc8 | |||
d82ba23ba5 | |||
0fd7fd71b4 | |||
eae12e3fe3 | |||
798a2cad4f | |||
41c0849429 | |||
a4e5af1184 | |||
b090af5922 | |||
388841f819 | |||
1a2ecbfbc4 | |||
38e292b112 | |||
c4f731262d | |||
07cc63f386 | |||
e42a692f00 | |||
6ec7538bb4 | |||
2871d489a9 | |||
1771ddd85d | |||
5198bf68fc | |||
e00fc35dbe | |||
8904e979df | |||
53eb217661 | |||
9dcb8f3fc7 | |||
1e8ac8364b | |||
754d8a035e | |||
f1f725c6a0 | |||
06c155420f | |||
7dabd2ac45 | |||
df8ba0d2cf | |||
ff1956e07b | |||
caf5a8817b | |||
a850fde1d8 | |||
0e6ebc13d1 | |||
6f5342a201 | |||
264a7044f5 | |||
1a30deca50 | |||
d8624e6a80 | |||
4f95d455ed | |||
468d19a9c1 | |||
9aeaf730ad | |||
db964a33a1 | |||
da8fb85859 | |||
54330a1c3c | |||
9732d77ed2 | |||
199ece7eb8 | |||
1997eb0078 | |||
eef4a7a304 | |||
246168bd72 | |||
7fbf54dc62 | |||
351f373865 | |||
72e785f36a | |||
727d2930f2 | |||
c13bf7c836 | |||
f3308e138d | |||
29546b345b | |||
2c57c7fa5a | |||
b6ea11b967 | |||
b8c74d606a | |||
a5d524ef46 | |||
cceb5ec237 | |||
71a6eaff83 | |||
7fd48d0413 | |||
1b38b5be86 | |||
decf2ae400 | |||
0d989011ff | |||
01b4b74574 | |||
70f767dc65 | |||
e75c24e889 | |||
0cb2056304 | |||
8adec2b9e0 | |||
604f292ab7 | |||
23d3c422ab | |||
0c1ffe980d | |||
5e95cb27d6 | |||
3b86f936c5 | |||
e0942e37aa | |||
c45a6caa95 | |||
61bbddbaa6 | |||
5425626790 | |||
5dc3552d85 | |||
3fbd27f73e | |||
0382ecb78d | |||
72edb6fc8c | |||
66149e3f2b | |||
6e74521d98 | |||
cf01013161 | |||
1e179c7528 | |||
530ed178b7 | |||
74aa18f68f | |||
d9222264a8 | |||
ca14211e93 | |||
b1d65c3369 | |||
b4c538b02b | |||
13059bceb2 | |||
d8894e24a4 | |||
3b09757bac | |||
2f97f76877 | |||
43f0537c06 | |||
a816da0dc3 | |||
7bb49d1057 | |||
1aa42fedee | |||
66aa382eae | |||
ee90ddab94 | |||
172240c0a4 | |||
ad25aee245 | |||
bd1f325b42 | |||
00a82ea805 | |||
b1b01841af | |||
816930c485 | |||
76233cda34 | |||
9dcea39985 | |||
10d00a756a | |||
eb50741129 | |||
3804b01276 | |||
b1298d8e06 | |||
6a46dc8db7 | |||
36cb99f958 | |||
81650f95e2 | |||
34dbcb8505 | |||
c993c829e2 | |||
0d90e0f067 | |||
678f58de4b | |||
c961a0e63e | |||
aaefb347c0 | |||
09018e19a5 | |||
345e37831c | |||
00ac799b68 | |||
133af9385b | |||
40c696e5c6 | |||
d6d5028922 | |||
38ad119f97 | |||
4e415288d7 | |||
fada438acf | |||
1df0ae2170 | |||
d96b9d40f0 | |||
fa19dfccf9 | |||
cdc22cb886 | |||
04c77a54b0 | |||
64a8c39a1f | |||
3d55f2806e | |||
1eb867f33f | |||
e93f4f7578 | |||
45ead916d1 | |||
3a0879c8c8 | |||
ebf361ce18 | |||
953b358668 | |||
3dfd25b3aa | |||
6f66eedc5d | |||
4094b6e36d | |||
c09cbf0ed9 | |||
391d53e1dd | |||
f64ebfe3e5 | |||
fc040bfd05 | |||
c8bf86d50d | |||
61989fb5e9 | |||
6f9d4d542f | |||
b3a8878080 | |||
f4d66a99cf | |||
537ba6f381 | |||
411f691b21 | |||
d6aa1967ad | |||
6e1e0e4b5b | |||
3941669d69 | |||
1aac03797e | |||
459af43494 | |||
f4f7e3cf41 | |||
1fd015516e | |||
76bafa8ffe | |||
8d5797b00f | |||
7571c02c8a | |||
49cbe7c8e3 | |||
ba4133c9eb | |||
b67f1840a1 | |||
165c46690f | |||
16bc9ab601 | |||
15ce1338b4 | |||
0ff30c5333 | |||
6feb2d5e80 | |||
1e07fea200 | |||
7aeb67b39b | |||
93881db22a | |||
64ed7a38f9 | |||
2fd466fcfc | |||
dc2fc73691 | |||
c4808c6009 | |||
c67f584eb3 | |||
29f6ed78e8 | |||
7807ee664d | |||
d518d06efd | |||
25a0cc44b9 | |||
825cdcec3c | |||
41b610acab | |||
0364fa8b65 | |||
849086a1ae | |||
36fbc6887f | |||
a8a98e43f2 | |||
57bdc730e2 | |||
31a196d7f5 | |||
9b27e6c3b4 | |||
62f1f9507f | |||
ee8dda41ae | |||
01ba178097 | |||
78ff59d052 | |||
f3f1cd6b3b | |||
803540e811 | |||
458ade6361 | |||
a69969ee05 | |||
f2b8db57eb | |||
331ae266ff | |||
4242001863 | |||
78338f71ca | |||
f5172a3084 | |||
c7df67edbd | |||
d410fee91d | |||
ba7aa464de | |||
8333034dce | |||
637b6af80f | |||
1044f8afd2 | |||
2f775107f9 | |||
85342674b2 | |||
fd69098a45 | |||
8867f908fc | |||
b7c33124c8 | |||
89a8c423c7 | |||
cea2582df2 | |||
e423e0baaa | |||
60b2dd1285 | |||
e66ab17a36 | |||
cb437dc2ad | |||
0d933b2ad5 | |||
37e3cbe22e | |||
610134730a | |||
212a5e28ba | |||
e5c3a4b549 | |||
1d0668ed5a | |||
d415299a80 | |||
48fbb1003d | |||
3442b30ab2 | |||
305d068362 | |||
a231ce87b5 | |||
a84d20fc14 | |||
9e30092361 | |||
10d5c7aa5f | |||
412f356e04 |
8
Makefile
8
Makefile
@ -6,10 +6,10 @@ clean:
|
|||||||
cleanall: clean
|
cleanall: clean
|
||||||
rm -f youtube-dl youtube-dl.exe
|
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
|
||||||
PYTHON=/usr/bin/env python
|
PYTHON ?= /usr/bin/env python
|
||||||
|
|
||||||
# set SYSCONFDIR to /etc if PREFIX=/usr or PREFIX=/usr/local
|
# set SYSCONFDIR to /etc if PREFIX=/usr or PREFIX=/usr/local
|
||||||
ifeq ($(PREFIX),/usr)
|
ifeq ($(PREFIX),/usr)
|
||||||
|
176
README.md
176
README.md
@ -12,11 +12,19 @@ To install it right away for all UNIX users (Linux, OS X, etc.), type:
|
|||||||
|
|
||||||
If you do not have curl, you can alternatively use a recent wget:
|
If you do not have curl, you can alternatively use a recent wget:
|
||||||
|
|
||||||
sudo wget https://yt-dl.org/downloads/2014.05.13/youtube-dl -O /usr/local/bin/youtube-dl
|
sudo wget https://yt-dl.org/downloads/latest/youtube-dl -O /usr/local/bin/youtube-dl
|
||||||
sudo chmod a+x /usr/local/bin/youtube-dl
|
sudo chmod a+x /usr/local/bin/youtube-dl
|
||||||
|
|
||||||
Windows users can [download a .exe file](https://yt-dl.org/latest/youtube-dl.exe) and place it in their home directory or any other location on their [PATH](http://en.wikipedia.org/wiki/PATH_%28variable%29).
|
Windows users can [download a .exe file](https://yt-dl.org/latest/youtube-dl.exe) and place it in their home directory or any other location on their [PATH](http://en.wikipedia.org/wiki/PATH_%28variable%29).
|
||||||
|
|
||||||
|
OS X users can install **youtube-dl** with [Homebrew](http://brew.sh/).
|
||||||
|
|
||||||
|
brew install youtube-dl
|
||||||
|
|
||||||
|
You can also use pip:
|
||||||
|
|
||||||
|
sudo pip install youtube-dl
|
||||||
|
|
||||||
Alternatively, refer to the developer instructions below for how to check out and work with the git repository. For further options, including PGP signatures, see https://rg3.github.io/youtube-dl/download.html .
|
Alternatively, refer to the developer instructions below for how to check out and work with the git repository. For further options, including PGP signatures, see https://rg3.github.io/youtube-dl/download.html .
|
||||||
|
|
||||||
# DESCRIPTION
|
# DESCRIPTION
|
||||||
@ -38,12 +46,6 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
playlist or the command line) if an error
|
playlist or the command line) if an error
|
||||||
occurs
|
occurs
|
||||||
--dump-user-agent display the current browser identification
|
--dump-user-agent display the current browser identification
|
||||||
--user-agent UA specify a custom user agent
|
|
||||||
--referer REF specify a custom referer, use if the video
|
|
||||||
access is restricted to one domain
|
|
||||||
--add-header FIELD:VALUE specify a custom HTTP header and its value,
|
|
||||||
separated by a colon ':'. You can use this
|
|
||||||
option multiple times
|
|
||||||
--list-extractors List all supported extractors and the URLs
|
--list-extractors List all supported extractors and the URLs
|
||||||
they would handle
|
they would handle
|
||||||
--extractor-descriptions Output descriptions of all supported
|
--extractor-descriptions Output descriptions of all supported
|
||||||
@ -51,34 +53,22 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
--proxy URL Use the specified HTTP/HTTPS proxy. Pass in
|
--proxy URL Use the specified HTTP/HTTPS proxy. Pass in
|
||||||
an empty string (--proxy "") for direct
|
an empty string (--proxy "") for direct
|
||||||
connection
|
connection
|
||||||
--no-check-certificate Suppress HTTPS certificate validation.
|
|
||||||
--prefer-insecure Use an unencrypted connection to retrieve
|
|
||||||
information about the video. (Currently
|
|
||||||
supported only for YouTube)
|
|
||||||
--cache-dir DIR Location in the filesystem where youtube-dl
|
|
||||||
can store some downloaded information
|
|
||||||
permanently. By default $XDG_CACHE_HOME
|
|
||||||
/youtube-dl or ~/.cache/youtube-dl . At the
|
|
||||||
moment, only YouTube player files (for
|
|
||||||
videos with obfuscated signatures) are
|
|
||||||
cached, but that may change.
|
|
||||||
--no-cache-dir Disable filesystem caching
|
|
||||||
--socket-timeout None Time to wait before giving up, in seconds
|
--socket-timeout None Time to wait before giving up, in seconds
|
||||||
--bidi-workaround Work around terminals that lack
|
|
||||||
bidirectional text support. Requires bidiv
|
|
||||||
or fribidi executable in PATH
|
|
||||||
--default-search PREFIX Use this prefix for unqualified URLs. For
|
--default-search PREFIX Use this prefix for unqualified URLs. For
|
||||||
example "gvsearch2:" downloads two videos
|
example "gvsearch2:" downloads two videos
|
||||||
from google videos for youtube-dl "large
|
from google videos for youtube-dl "large
|
||||||
apple". By default (with value "auto")
|
apple". Use the value "auto" to let
|
||||||
youtube-dl guesses.
|
youtube-dl guess ("auto_warning" to emit a
|
||||||
|
warning when guessing). "error" just throws
|
||||||
|
an error. The default value "fixup_error"
|
||||||
|
repairs broken URLs, but emits an error if
|
||||||
|
this is not possible instead of searching.
|
||||||
--ignore-config Do not read configuration files. When given
|
--ignore-config Do not read configuration files. When given
|
||||||
in the global configuration file /etc
|
in the global configuration file /etc
|
||||||
/youtube-dl.conf: do not read the user
|
/youtube-dl.conf: do not read the user
|
||||||
configuration in ~/.config/youtube-dl.conf
|
configuration in ~/.config/youtube-dl.conf
|
||||||
(%APPDATA%/youtube-dl/config.txt on
|
(%APPDATA%/youtube-dl/config.txt on
|
||||||
Windows)
|
Windows)
|
||||||
--encoding ENCODING Force the specified encoding (experimental)
|
|
||||||
|
|
||||||
## 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)
|
||||||
@ -124,9 +114,9 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
of SIZE.
|
of SIZE.
|
||||||
|
|
||||||
## Filesystem Options:
|
## Filesystem Options:
|
||||||
-t, --title use title in file name (default)
|
-a, --batch-file FILE file containing URLs to download ('-' for
|
||||||
|
stdin)
|
||||||
--id use only video ID in file name
|
--id use only video ID in file name
|
||||||
-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
|
-o, --output TEMPLATE output filename template. Use %(title)s to
|
||||||
get the title, %(uploader)s for the
|
get the title, %(uploader)s for the
|
||||||
@ -159,18 +149,15 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
--restrict-filenames Restrict filenames to only ASCII
|
--restrict-filenames Restrict filenames to only ASCII
|
||||||
characters, and avoid "&" and spaces in
|
characters, and avoid "&" and spaces in
|
||||||
filenames
|
filenames
|
||||||
-a, --batch-file FILE file containing URLs to download ('-' for
|
-t, --title [deprecated] use title in file name
|
||||||
stdin)
|
(default)
|
||||||
--load-info FILE json file containing the video information
|
-l, --literal [deprecated] alias of --title
|
||||||
(created with the "--write-json" option)
|
|
||||||
-w, --no-overwrites do not overwrite files
|
-w, --no-overwrites do not overwrite files
|
||||||
-c, --continue force resume of partially downloaded files.
|
-c, --continue force resume of partially downloaded files.
|
||||||
By default, youtube-dl will resume
|
By default, youtube-dl will resume
|
||||||
downloads if possible.
|
downloads if possible.
|
||||||
--no-continue do not resume partially downloaded files
|
--no-continue do not resume partially downloaded files
|
||||||
(restart from beginning)
|
(restart from beginning)
|
||||||
--cookies FILE file to read cookies from and dump cookie
|
|
||||||
jar in
|
|
||||||
--no-part do not use .part files
|
--no-part do not use .part files
|
||||||
--no-mtime do not use the Last-modified header to set
|
--no-mtime do not use the Last-modified header to set
|
||||||
the file modification time
|
the file modification time
|
||||||
@ -180,6 +167,19 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
--write-annotations write video annotations to a .annotation
|
--write-annotations write video annotations to a .annotation
|
||||||
file
|
file
|
||||||
--write-thumbnail write thumbnail image to disk
|
--write-thumbnail write thumbnail image to disk
|
||||||
|
--load-info FILE json file containing the video information
|
||||||
|
(created with the "--write-json" option)
|
||||||
|
--cookies FILE file to read cookies from and dump cookie
|
||||||
|
jar in
|
||||||
|
--cache-dir DIR Location in the filesystem where youtube-dl
|
||||||
|
can store some downloaded information
|
||||||
|
permanently. By default $XDG_CACHE_HOME
|
||||||
|
/youtube-dl or ~/.cache/youtube-dl . At the
|
||||||
|
moment, only YouTube player files (for
|
||||||
|
videos with obfuscated signatures) are
|
||||||
|
cached, but that may change.
|
||||||
|
--no-cache-dir Disable filesystem caching
|
||||||
|
--rm-cache-dir Delete all filesystem cache files
|
||||||
|
|
||||||
## Verbosity / Simulation Options:
|
## Verbosity / Simulation Options:
|
||||||
-q, --quiet activates quiet mode
|
-q, --quiet activates quiet mode
|
||||||
@ -209,6 +209,22 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
problems
|
problems
|
||||||
--print-traffic Display sent and read HTTP traffic
|
--print-traffic Display sent and read HTTP traffic
|
||||||
|
|
||||||
|
## Workarounds:
|
||||||
|
--encoding ENCODING Force the specified encoding (experimental)
|
||||||
|
--no-check-certificate Suppress HTTPS certificate validation.
|
||||||
|
--prefer-insecure Use an unencrypted connection to retrieve
|
||||||
|
information about the video. (Currently
|
||||||
|
supported only for YouTube)
|
||||||
|
--user-agent UA specify a custom user agent
|
||||||
|
--referer REF specify a custom referer, use if the video
|
||||||
|
access is restricted to one domain
|
||||||
|
--add-header FIELD:VALUE specify a custom HTTP header and its value,
|
||||||
|
separated by a colon ':'. You can use this
|
||||||
|
option multiple times
|
||||||
|
--bidi-workaround Work around terminals that lack
|
||||||
|
bidirectional text support. Requires bidiv
|
||||||
|
or fribidi executable in PATH
|
||||||
|
|
||||||
## Video Format Options:
|
## Video Format Options:
|
||||||
-f, --format FORMAT video format code, specify the order of
|
-f, --format FORMAT video format code, specify the order of
|
||||||
preference using slashes: "-f 22/17/18".
|
preference using slashes: "-f 22/17/18".
|
||||||
@ -254,7 +270,7 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
128K (default 5)
|
128K (default 5)
|
||||||
--recode-video FORMAT Encode the video to another format if
|
--recode-video FORMAT Encode the video to another format if
|
||||||
necessary (currently supported:
|
necessary (currently supported:
|
||||||
mp4|flv|ogg|webm)
|
mp4|flv|ogg|webm|mkv)
|
||||||
-k, --keep-video keeps the video file on disk after the
|
-k, --keep-video keeps the video file on disk after the
|
||||||
post-processing; the video is erased by
|
post-processing; the video is erased by
|
||||||
default
|
default
|
||||||
@ -295,10 +311,12 @@ The current default template is `%(title)s-%(id)s.%(ext)s`.
|
|||||||
|
|
||||||
In some cases, you don't want special characters such as 中, spaces, or &, such as when transferring the downloaded filename to a Windows system or the filename through an 8bit-unsafe channel. In these cases, add the `--restrict-filenames` flag to get a shorter title:
|
In some cases, you don't want special characters such as 中, spaces, or &, such as when transferring the downloaded filename to a Windows system or the filename through an 8bit-unsafe channel. In these cases, add the `--restrict-filenames` flag to get a shorter title:
|
||||||
|
|
||||||
$ youtube-dl --get-filename -o "%(title)s.%(ext)s" BaW_jenozKc
|
```bash
|
||||||
youtube-dl test video ''_ä↭𝕐.mp4 # All kinds of weird characters
|
$ youtube-dl --get-filename -o "%(title)s.%(ext)s" BaW_jenozKc
|
||||||
$ youtube-dl --get-filename -o "%(title)s.%(ext)s" BaW_jenozKc --restrict-filenames
|
youtube-dl test video ''_ä↭𝕐.mp4 # All kinds of weird characters
|
||||||
youtube-dl_test_video_.mp4 # A simple file name
|
$ youtube-dl --get-filename -o "%(title)s.%(ext)s" BaW_jenozKc --restrict-filenames
|
||||||
|
youtube-dl_test_video_.mp4 # A simple file name
|
||||||
|
```
|
||||||
|
|
||||||
# VIDEO SELECTION
|
# VIDEO SELECTION
|
||||||
|
|
||||||
@ -309,14 +327,16 @@ Videos can be filtered by their upload date using the options `--date`, `--dateb
|
|||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
# Download only the videos uploaded in the last 6 months
|
```bash
|
||||||
$ youtube-dl --dateafter now-6months
|
# Download only the videos uploaded in the last 6 months
|
||||||
|
$ youtube-dl --dateafter now-6months
|
||||||
|
|
||||||
# Download only the videos uploaded on January 1, 1970
|
# Download only the videos uploaded on January 1, 1970
|
||||||
$ youtube-dl --date 19700101
|
$ youtube-dl --date 19700101
|
||||||
|
|
||||||
$ # will only download the videos uploaded in the 200x decade
|
$ # will only download the videos uploaded in the 200x decade
|
||||||
$ youtube-dl --dateafter 20000101 --datebefore 20091231
|
$ youtube-dl --dateafter 20000101 --datebefore 20091231
|
||||||
|
```
|
||||||
|
|
||||||
# FAQ
|
# FAQ
|
||||||
|
|
||||||
@ -391,49 +411,49 @@ If you want to add support for a new site, you can follow this quick list (assum
|
|||||||
2. Check out the source code with `git clone git@github.com:YOUR_GITHUB_USERNAME/youtube-dl.git`
|
2. Check out the source code with `git clone git@github.com:YOUR_GITHUB_USERNAME/youtube-dl.git`
|
||||||
3. Start a new git branch with `cd youtube-dl; git checkout -b yourextractor`
|
3. Start a new git branch with `cd youtube-dl; git checkout -b yourextractor`
|
||||||
4. Start with this simple template and save it to `youtube_dl/extractor/yourextractor.py`:
|
4. Start with this simple template and save it to `youtube_dl/extractor/yourextractor.py`:
|
||||||
|
```python
|
||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
# coding: utf-8
|
import re
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import re
|
from .common import InfoExtractor
|
||||||
|
|
||||||
from .common import InfoExtractor
|
|
||||||
|
|
||||||
|
|
||||||
class YourExtractorIE(InfoExtractor):
|
class YourExtractorIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?yourextractor\.com/watch/(?P<id>[0-9]+)'
|
_VALID_URL = r'https?://(?:www\.)?yourextractor\.com/watch/(?P<id>[0-9]+)'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://yourextractor.com/watch/42',
|
'url': 'http://yourextractor.com/watch/42',
|
||||||
'md5': 'TODO: md5 sum of the first 10KiB of the video file',
|
'md5': 'TODO: md5 sum of the first 10KiB of the video file',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '42',
|
'id': '42',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Video title goes here',
|
'title': 'Video title goes here',
|
||||||
# TODO more properties, either as:
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
# * A value
|
# TODO more properties, either as:
|
||||||
# * MD5 checksum; start the string with md5:
|
# * A value
|
||||||
# * A regular expression; start the string with re:
|
# * MD5 checksum; start the string with md5:
|
||||||
# * Any Python type (for example int or float)
|
# * A regular expression; start the string with re:
|
||||||
}
|
# * Any Python type (for example int or float)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
video_id = mobj.group('id')
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
# TODO more code goes here, for example ...
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
|
||||||
title = self._html_search_regex(r'<h1>(.*?)</h1>', webpage, 'title')
|
|
||||||
|
|
||||||
return {
|
|
||||||
'id': video_id,
|
|
||||||
'title': title,
|
|
||||||
# TODO more properties (see youtube_dl/extractor/common.py)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
# TODO more code goes here, for example ...
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
title = self._html_search_regex(r'<h1>(.*?)</h1>', webpage, 'title')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
# TODO more properties (see youtube_dl/extractor/common.py)
|
||||||
|
}
|
||||||
|
```
|
||||||
5. Add an import in [`youtube_dl/extractor/__init__.py`](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/__init__.py).
|
5. Add an import in [`youtube_dl/extractor/__init__.py`](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/__init__.py).
|
||||||
6. Run `python test/test_download.py TestDownload.test_YourExtractor`. This *should fail* at first, but you can continually re-run it until you're done.
|
6. Run `python test/test_download.py TestDownload.test_YourExtractor`. This *should fail* at first, but you can continually re-run it until you're done. If you decide to add more than one test, then rename ``_TEST`` to ``_TESTS`` and make it into a list of dictionaries. The tests will be then be named `TestDownload.test_YourExtractor`, `TestDownload.test_YourExtractor_1`, `TestDownload.test_YourExtractor_2`, etc.
|
||||||
7. Have a look at [`youtube_dl/common/extractor/common.py`](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/common.py) for possible helper methods and a [detailed description of what your extractor should return](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/common.py#L38). Add tests and code for as many as you want.
|
7. Have a look at [`youtube_dl/common/extractor/common.py`](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/common.py) for possible helper methods and a [detailed description of what your extractor should return](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/common.py#L38). Add tests and code for as many as you want.
|
||||||
8. If you can, check the code with [pyflakes](https://pypi.python.org/pypi/pyflakes) (a good idea) and [pep8](https://pypi.python.org/pypi/pep8) (optional, ignore E501).
|
8. If you can, check the code with [pyflakes](https://pypi.python.org/pypi/pyflakes) (a good idea) and [pep8](https://pypi.python.org/pypi/pep8) (optional, ignore E501).
|
||||||
9. When the tests pass, [add](https://www.kernel.org/pub/software/scm/git/docs/git-add.html) the new files and [commit](https://www.kernel.org/pub/software/scm/git/docs/git-commit.html) them and [push](https://www.kernel.org/pub/software/scm/git/docs/git-push.html) the result, like this:
|
9. When the tests pass, [add](https://www.kernel.org/pub/software/scm/git/docs/git-add.html) the new files and [commit](https://www.kernel.org/pub/software/scm/git/docs/git-commit.html) them and [push](https://www.kernel.org/pub/software/scm/git/docs/git-push.html) the result, like this:
|
||||||
|
@ -117,8 +117,9 @@ def expect_info_dict(self, expected_dict, got_dict):
|
|||||||
u'invalid value for field %s, expected %r, got %r' % (info_field, expected, got))
|
u'invalid value for field %s, expected %r, got %r' % (info_field, expected, got))
|
||||||
|
|
||||||
# Check for the presence of mandatory fields
|
# Check for the presence of mandatory fields
|
||||||
for key in ('id', 'url', 'title', 'ext'):
|
if got_dict.get('_type') != 'playlist':
|
||||||
self.assertTrue(got_dict.get(key), 'Missing mandatory field %s' % key)
|
for key in ('id', 'url', 'title', 'ext'):
|
||||||
|
self.assertTrue(got_dict.get(key), 'Missing mandatory field %s' % key)
|
||||||
# Check for mandatory fields that are automatically set by YoutubeDL
|
# Check for mandatory fields that are automatically set by YoutubeDL
|
||||||
for key in ['webpage_url', 'extractor', 'extractor_key']:
|
for key in ['webpage_url', 'extractor', 'extractor_key']:
|
||||||
self.assertTrue(got_dict.get(key), u'Missing field: %s' % key)
|
self.assertTrue(got_dict.get(key), u'Missing field: %s' % key)
|
||||||
@ -137,8 +138,8 @@ def expect_info_dict(self, expected_dict, got_dict):
|
|||||||
|
|
||||||
|
|
||||||
def assertRegexpMatches(self, text, regexp, msg=None):
|
def assertRegexpMatches(self, text, regexp, msg=None):
|
||||||
if hasattr(self, 'assertRegexpMatches'):
|
if hasattr(self, 'assertRegexp'):
|
||||||
return self.assertRegexpMatches(text, regexp, msg)
|
return self.assertRegexp(text, regexp, msg)
|
||||||
else:
|
else:
|
||||||
m = re.match(regexp, text)
|
m = re.match(regexp, text)
|
||||||
if not m:
|
if not m:
|
||||||
@ -148,3 +149,10 @@ def assertRegexpMatches(self, text, regexp, msg=None):
|
|||||||
else:
|
else:
|
||||||
msg = note + ', ' + msg
|
msg = note + ', ' + msg
|
||||||
self.assertTrue(m, msg)
|
self.assertTrue(m, msg)
|
||||||
|
|
||||||
|
|
||||||
|
def assertGreaterEqual(self, got, expected, msg=None):
|
||||||
|
if not (got >= expected):
|
||||||
|
if msg is None:
|
||||||
|
msg = '%r not greater than or equal to %r' % (got, expected)
|
||||||
|
self.assertTrue(got >= expected, msg)
|
||||||
|
1
test/swftests/.gitignore
vendored
Normal file
1
test/swftests/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
*.swf
|
19
test/swftests/ArrayAccess.as
Normal file
19
test/swftests/ArrayAccess.as
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// input: [["a", "b", "c", "d"]]
|
||||||
|
// output: ["c", "b", "a", "d"]
|
||||||
|
|
||||||
|
package {
|
||||||
|
public class ArrayAccess {
|
||||||
|
public static function main(ar:Array):Array {
|
||||||
|
var aa:ArrayAccess = new ArrayAccess();
|
||||||
|
return aa.f(ar, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function f(ar:Array, num:Number):Array{
|
||||||
|
var x:String = ar[0];
|
||||||
|
var y:String = ar[num % ar.length];
|
||||||
|
ar[0] = y;
|
||||||
|
ar[num] = x;
|
||||||
|
return ar;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
test/swftests/ClassCall.as
Normal file
17
test/swftests/ClassCall.as
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// input: []
|
||||||
|
// output: 121
|
||||||
|
|
||||||
|
package {
|
||||||
|
public class ClassCall {
|
||||||
|
public static function main():int{
|
||||||
|
var f:OtherClass = new OtherClass();
|
||||||
|
return f.func(100,20);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OtherClass {
|
||||||
|
public function func(x: int, y: int):int {
|
||||||
|
return x+y+1;
|
||||||
|
}
|
||||||
|
}
|
15
test/swftests/ClassConstruction.as
Normal file
15
test/swftests/ClassConstruction.as
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// input: []
|
||||||
|
// output: 0
|
||||||
|
|
||||||
|
package {
|
||||||
|
public class ClassConstruction {
|
||||||
|
public static function main():int{
|
||||||
|
var f:Foo = new Foo();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Foo {
|
||||||
|
|
||||||
|
}
|
13
test/swftests/LocalVars.as
Normal file
13
test/swftests/LocalVars.as
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// input: [1, 2]
|
||||||
|
// output: 3
|
||||||
|
|
||||||
|
package {
|
||||||
|
public class LocalVars {
|
||||||
|
public static function main(a:int, b:int):int{
|
||||||
|
var c:int = a + b + b;
|
||||||
|
var d:int = c - b;
|
||||||
|
var e:int = d;
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
test/swftests/PrivateCall.as
Normal file
21
test/swftests/PrivateCall.as
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// input: []
|
||||||
|
// output: 9
|
||||||
|
|
||||||
|
package {
|
||||||
|
public class PrivateCall {
|
||||||
|
public static function main():int{
|
||||||
|
var f:OtherClass = new OtherClass();
|
||||||
|
return f.func();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OtherClass {
|
||||||
|
private function pf():int {
|
||||||
|
return 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function func():int {
|
||||||
|
return this.pf();
|
||||||
|
}
|
||||||
|
}
|
13
test/swftests/StaticAssignment.as
Normal file
13
test/swftests/StaticAssignment.as
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// input: [1]
|
||||||
|
// output: 1
|
||||||
|
|
||||||
|
package {
|
||||||
|
public class StaticAssignment {
|
||||||
|
public static var v:int;
|
||||||
|
|
||||||
|
public static function main(a:int):int{
|
||||||
|
v = a;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
test/swftests/StaticRetrieval.as
Normal file
16
test/swftests/StaticRetrieval.as
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// input: []
|
||||||
|
// output: 1
|
||||||
|
|
||||||
|
package {
|
||||||
|
public class StaticRetrieval {
|
||||||
|
public static var v:int;
|
||||||
|
|
||||||
|
public static function main():int{
|
||||||
|
if (v) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -221,7 +221,7 @@ class TestFormatSelection(unittest.TestCase):
|
|||||||
'138', '137', '248', '136', '247', '135', '246',
|
'138', '137', '248', '136', '247', '135', '246',
|
||||||
'245', '244', '134', '243', '133', '242', '160',
|
'245', '244', '134', '243', '133', '242', '160',
|
||||||
# Dash audio
|
# Dash audio
|
||||||
'141', '172', '140', '139', '171',
|
'141', '172', '140', '171', '139',
|
||||||
]
|
]
|
||||||
|
|
||||||
for f1id, f2id in zip(order, order[1:]):
|
for f1id, f2id in zip(order, order[1:]):
|
||||||
|
@ -15,7 +15,6 @@ from youtube_dl.extractor import (
|
|||||||
FacebookIE,
|
FacebookIE,
|
||||||
gen_extractors,
|
gen_extractors,
|
||||||
JustinTVIE,
|
JustinTVIE,
|
||||||
PBSIE,
|
|
||||||
YoutubeIE,
|
YoutubeIE,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -69,9 +68,6 @@ class TestAllURLsMatching(unittest.TestCase):
|
|||||||
def test_youtube_show_matching(self):
|
def test_youtube_show_matching(self):
|
||||||
self.assertMatch('http://www.youtube.com/show/airdisasters', ['youtube:show'])
|
self.assertMatch('http://www.youtube.com/show/airdisasters', ['youtube:show'])
|
||||||
|
|
||||||
def test_youtube_truncated(self):
|
|
||||||
self.assertMatch('http://www.youtube.com/watch?', ['youtube:truncated_url'])
|
|
||||||
|
|
||||||
def test_youtube_search_matching(self):
|
def test_youtube_search_matching(self):
|
||||||
self.assertMatch('http://www.youtube.com/results?search_query=making+mustard', ['youtube:search_url'])
|
self.assertMatch('http://www.youtube.com/results?search_query=making+mustard', ['youtube:search_url'])
|
||||||
self.assertMatch('https://www.youtube.com/results?baz=bar&search_query=youtube-dl+test+video&filters=video&lclk=video', ['youtube:search_url'])
|
self.assertMatch('https://www.youtube.com/results?baz=bar&search_query=youtube-dl+test+video&filters=video&lclk=video', ['youtube:search_url'])
|
||||||
@ -103,6 +99,7 @@ class TestAllURLsMatching(unittest.TestCase):
|
|||||||
|
|
||||||
def test_facebook_matching(self):
|
def test_facebook_matching(self):
|
||||||
self.assertTrue(FacebookIE.suitable('https://www.facebook.com/Shiniknoh#!/photo.php?v=10153317450565268'))
|
self.assertTrue(FacebookIE.suitable('https://www.facebook.com/Shiniknoh#!/photo.php?v=10153317450565268'))
|
||||||
|
self.assertTrue(FacebookIE.suitable('https://www.facebook.com/cindyweather?fref=ts#!/photo.php?v=10152183998945793'))
|
||||||
|
|
||||||
def test_no_duplicates(self):
|
def test_no_duplicates(self):
|
||||||
ies = gen_extractors()
|
ies = gen_extractors()
|
||||||
|
@ -10,7 +10,6 @@ from test.helper import (
|
|||||||
get_params,
|
get_params,
|
||||||
gettestcases,
|
gettestcases,
|
||||||
expect_info_dict,
|
expect_info_dict,
|
||||||
md5,
|
|
||||||
try_rm,
|
try_rm,
|
||||||
report_warning,
|
report_warning,
|
||||||
)
|
)
|
||||||
@ -24,7 +23,6 @@ import socket
|
|||||||
import youtube_dl.YoutubeDL
|
import youtube_dl.YoutubeDL
|
||||||
from youtube_dl.utils import (
|
from youtube_dl.utils import (
|
||||||
compat_http_client,
|
compat_http_client,
|
||||||
compat_str,
|
|
||||||
compat_urllib_error,
|
compat_urllib_error,
|
||||||
compat_HTTPError,
|
compat_HTTPError,
|
||||||
DownloadError,
|
DownloadError,
|
||||||
@ -65,15 +63,21 @@ def generator(test_case):
|
|||||||
def test_template(self):
|
def test_template(self):
|
||||||
ie = youtube_dl.extractor.get_info_extractor(test_case['name'])
|
ie = youtube_dl.extractor.get_info_extractor(test_case['name'])
|
||||||
other_ies = [get_info_extractor(ie_key) for ie_key in test_case.get('add_ie', [])]
|
other_ies = [get_info_extractor(ie_key) for ie_key in test_case.get('add_ie', [])]
|
||||||
|
is_playlist = any(k.startswith('playlist') for k in test_case)
|
||||||
|
test_cases = test_case.get(
|
||||||
|
'playlist', [] if is_playlist else [test_case])
|
||||||
|
|
||||||
def print_skipping(reason):
|
def print_skipping(reason):
|
||||||
print('Skipping %s: %s' % (test_case['name'], reason))
|
print('Skipping %s: %s' % (test_case['name'], reason))
|
||||||
if not ie.working():
|
if not ie.working():
|
||||||
print_skipping('IE marked as not _WORKING')
|
print_skipping('IE marked as not _WORKING')
|
||||||
return
|
return
|
||||||
if 'playlist' not in test_case:
|
|
||||||
info_dict = test_case.get('info_dict', {})
|
for tc in test_cases:
|
||||||
if not test_case.get('file') and not (info_dict.get('id') and info_dict.get('ext')):
|
info_dict = tc.get('info_dict', {})
|
||||||
|
if not tc.get('file') and not (info_dict.get('id') and info_dict.get('ext')):
|
||||||
raise Exception('Test definition incorrect. The output file cannot be known. Are both \'id\' and \'ext\' keys present?')
|
raise Exception('Test definition incorrect. The output file cannot be known. Are both \'id\' and \'ext\' keys present?')
|
||||||
|
|
||||||
if 'skip' in test_case:
|
if 'skip' in test_case:
|
||||||
print_skipping(test_case['skip'])
|
print_skipping(test_case['skip'])
|
||||||
return
|
return
|
||||||
@ -83,6 +87,9 @@ def generator(test_case):
|
|||||||
return
|
return
|
||||||
|
|
||||||
params = get_params(test_case.get('params', {}))
|
params = get_params(test_case.get('params', {}))
|
||||||
|
if is_playlist and 'playlist' not in test_case:
|
||||||
|
params.setdefault('extract_flat', True)
|
||||||
|
params.setdefault('skip_download', True)
|
||||||
|
|
||||||
ydl = YoutubeDL(params)
|
ydl = YoutubeDL(params)
|
||||||
ydl.add_default_info_extractors()
|
ydl.add_default_info_extractors()
|
||||||
@ -95,7 +102,6 @@ def generator(test_case):
|
|||||||
def get_tc_filename(tc):
|
def get_tc_filename(tc):
|
||||||
return tc.get('file') or ydl.prepare_filename(tc.get('info_dict', {}))
|
return tc.get('file') or ydl.prepare_filename(tc.get('info_dict', {}))
|
||||||
|
|
||||||
test_cases = test_case.get('playlist', [test_case])
|
|
||||||
def try_rm_tcs_files():
|
def try_rm_tcs_files():
|
||||||
for tc in test_cases:
|
for tc in test_cases:
|
||||||
tc_filename = get_tc_filename(tc)
|
tc_filename = get_tc_filename(tc)
|
||||||
@ -107,7 +113,10 @@ def generator(test_case):
|
|||||||
try_num = 1
|
try_num = 1
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
ydl.download([test_case['url']])
|
# We're not using .download here sine that is just a shim
|
||||||
|
# for outside error handling, and returns the exit code
|
||||||
|
# instead of the result dict.
|
||||||
|
res_dict = ydl.extract_info(test_case['url'])
|
||||||
except (DownloadError, ExtractorError) as err:
|
except (DownloadError, ExtractorError) as err:
|
||||||
# Check if the exception is not a network related one
|
# Check if the exception is not a network related one
|
||||||
if not err.exc_info[0] in (compat_urllib_error.URLError, socket.timeout, UnavailableVideoError, compat_http_client.BadStatusLine) or (err.exc_info[0] == compat_HTTPError and err.exc_info[1].code == 503):
|
if not err.exc_info[0] in (compat_urllib_error.URLError, socket.timeout, UnavailableVideoError, compat_http_client.BadStatusLine) or (err.exc_info[0] == compat_HTTPError and err.exc_info[1].code == 503):
|
||||||
@ -123,6 +132,17 @@ def generator(test_case):
|
|||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
if is_playlist:
|
||||||
|
self.assertEqual(res_dict['_type'], 'playlist')
|
||||||
|
expect_info_dict(self, test_case.get('info_dict', {}), res_dict)
|
||||||
|
if 'playlist_mincount' in test_case:
|
||||||
|
self.assertGreaterEqual(
|
||||||
|
len(res_dict['entries']),
|
||||||
|
test_case['playlist_mincount'],
|
||||||
|
'Expected at least %d in playlist %s, but got only %d' % (
|
||||||
|
test_case['playlist_mincount'], test_case['url'],
|
||||||
|
len(res_dict['entries'])))
|
||||||
|
|
||||||
for tc in test_cases:
|
for tc in test_cases:
|
||||||
tc_filename = get_tc_filename(tc)
|
tc_filename = get_tc_filename(tc)
|
||||||
if not test_case.get('params', {}).get('skip_download', False):
|
if not test_case.get('params', {}).get('skip_download', False):
|
||||||
|
@ -1,6 +1,17 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
|
|
||||||
|
## DEPRECATED FILE!
|
||||||
|
# Add new tests to the extractors themselves, like this:
|
||||||
|
# _TEST = {
|
||||||
|
# 'url': 'http://example.com/playlist/42',
|
||||||
|
# 'playlist_mincount': 99,
|
||||||
|
# 'info_dict': {
|
||||||
|
# 'id': '42',
|
||||||
|
# 'title': 'Playlist number forty-two',
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
# Allow direct execution
|
# Allow direct execution
|
||||||
@ -11,6 +22,7 @@ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|||||||
|
|
||||||
from test.helper import (
|
from test.helper import (
|
||||||
assertRegexpMatches,
|
assertRegexpMatches,
|
||||||
|
assertGreaterEqual,
|
||||||
expect_info_dict,
|
expect_info_dict,
|
||||||
FakeYDL,
|
FakeYDL,
|
||||||
)
|
)
|
||||||
@ -28,8 +40,9 @@ from youtube_dl.extractor import (
|
|||||||
SoundcloudSetIE,
|
SoundcloudSetIE,
|
||||||
SoundcloudUserIE,
|
SoundcloudUserIE,
|
||||||
SoundcloudPlaylistIE,
|
SoundcloudPlaylistIE,
|
||||||
TeacherTubeClassroomIE,
|
TeacherTubeUserIE,
|
||||||
LivestreamIE,
|
LivestreamIE,
|
||||||
|
LivestreamOriginalIE,
|
||||||
NHLVideocenterIE,
|
NHLVideocenterIE,
|
||||||
BambuserChannelIE,
|
BambuserChannelIE,
|
||||||
BandcampAlbumIE,
|
BandcampAlbumIE,
|
||||||
@ -40,6 +53,7 @@ from youtube_dl.extractor import (
|
|||||||
KhanAcademyIE,
|
KhanAcademyIE,
|
||||||
EveryonesMixtapeIE,
|
EveryonesMixtapeIE,
|
||||||
RutubeChannelIE,
|
RutubeChannelIE,
|
||||||
|
RutubePersonIE,
|
||||||
GoogleSearchIE,
|
GoogleSearchIE,
|
||||||
GenericIE,
|
GenericIE,
|
||||||
TEDIE,
|
TEDIE,
|
||||||
@ -69,8 +83,8 @@ class TestPlaylists(unittest.TestCase):
|
|||||||
ie = DailymotionUserIE(dl)
|
ie = DailymotionUserIE(dl)
|
||||||
result = ie.extract('https://www.dailymotion.com/user/nqtv')
|
result = ie.extract('https://www.dailymotion.com/user/nqtv')
|
||||||
self.assertIsPlaylist(result)
|
self.assertIsPlaylist(result)
|
||||||
|
assertGreaterEqual(self, len(result['entries']), 100)
|
||||||
self.assertEqual(result['title'], 'Rémi Gaillard')
|
self.assertEqual(result['title'], 'Rémi Gaillard')
|
||||||
self.assertTrue(len(result['entries']) >= 100)
|
|
||||||
|
|
||||||
def test_vimeo_channel(self):
|
def test_vimeo_channel(self):
|
||||||
dl = FakeYDL()
|
dl = FakeYDL()
|
||||||
@ -109,7 +123,7 @@ class TestPlaylists(unittest.TestCase):
|
|||||||
ie = VineUserIE(dl)
|
ie = VineUserIE(dl)
|
||||||
result = ie.extract('https://vine.co/Visa')
|
result = ie.extract('https://vine.co/Visa')
|
||||||
self.assertIsPlaylist(result)
|
self.assertIsPlaylist(result)
|
||||||
self.assertTrue(len(result['entries']) >= 50)
|
assertGreaterEqual(self, len(result['entries']), 47)
|
||||||
|
|
||||||
def test_ustream_channel(self):
|
def test_ustream_channel(self):
|
||||||
dl = FakeYDL()
|
dl = FakeYDL()
|
||||||
@ -117,7 +131,7 @@ class TestPlaylists(unittest.TestCase):
|
|||||||
result = ie.extract('http://www.ustream.tv/channel/channeljapan')
|
result = ie.extract('http://www.ustream.tv/channel/channeljapan')
|
||||||
self.assertIsPlaylist(result)
|
self.assertIsPlaylist(result)
|
||||||
self.assertEqual(result['id'], '10874166')
|
self.assertEqual(result['id'], '10874166')
|
||||||
self.assertTrue(len(result['entries']) >= 54)
|
assertGreaterEqual(self, len(result['entries']), 54)
|
||||||
|
|
||||||
def test_soundcloud_set(self):
|
def test_soundcloud_set(self):
|
||||||
dl = FakeYDL()
|
dl = FakeYDL()
|
||||||
@ -125,7 +139,7 @@ class TestPlaylists(unittest.TestCase):
|
|||||||
result = ie.extract('https://soundcloud.com/the-concept-band/sets/the-royal-concept-ep')
|
result = ie.extract('https://soundcloud.com/the-concept-band/sets/the-royal-concept-ep')
|
||||||
self.assertIsPlaylist(result)
|
self.assertIsPlaylist(result)
|
||||||
self.assertEqual(result['title'], 'The Royal Concept EP')
|
self.assertEqual(result['title'], 'The Royal Concept EP')
|
||||||
self.assertTrue(len(result['entries']) >= 6)
|
assertGreaterEqual(self, len(result['entries']), 6)
|
||||||
|
|
||||||
def test_soundcloud_user(self):
|
def test_soundcloud_user(self):
|
||||||
dl = FakeYDL()
|
dl = FakeYDL()
|
||||||
@ -133,7 +147,15 @@ class TestPlaylists(unittest.TestCase):
|
|||||||
result = ie.extract('https://soundcloud.com/the-concept-band')
|
result = ie.extract('https://soundcloud.com/the-concept-band')
|
||||||
self.assertIsPlaylist(result)
|
self.assertIsPlaylist(result)
|
||||||
self.assertEqual(result['id'], '9615865')
|
self.assertEqual(result['id'], '9615865')
|
||||||
self.assertTrue(len(result['entries']) >= 12)
|
assertGreaterEqual(self, len(result['entries']), 12)
|
||||||
|
|
||||||
|
def test_soundcloud_likes(self):
|
||||||
|
dl = FakeYDL()
|
||||||
|
ie = SoundcloudUserIE(dl)
|
||||||
|
result = ie.extract('https://soundcloud.com/the-concept-band/likes')
|
||||||
|
self.assertIsPlaylist(result)
|
||||||
|
self.assertEqual(result['id'], '9615865')
|
||||||
|
assertGreaterEqual(self, len(result['entries']), 1)
|
||||||
|
|
||||||
def test_soundcloud_playlist(self):
|
def test_soundcloud_playlist(self):
|
||||||
dl = FakeYDL()
|
dl = FakeYDL()
|
||||||
@ -143,7 +165,7 @@ class TestPlaylists(unittest.TestCase):
|
|||||||
self.assertEqual(result['id'], '4110309')
|
self.assertEqual(result['id'], '4110309')
|
||||||
self.assertEqual(result['title'], 'TILT Brass - Bowery Poetry Club, August \'03 [Non-Site SCR 02]')
|
self.assertEqual(result['title'], 'TILT Brass - Bowery Poetry Club, August \'03 [Non-Site SCR 02]')
|
||||||
assertRegexpMatches(
|
assertRegexpMatches(
|
||||||
self, result['description'], r'TILT Brass - Bowery Poetry Club')
|
self, result['description'], r'.*?TILT Brass - Bowery Poetry Club')
|
||||||
self.assertEqual(len(result['entries']), 6)
|
self.assertEqual(len(result['entries']), 6)
|
||||||
|
|
||||||
def test_livestream_event(self):
|
def test_livestream_event(self):
|
||||||
@ -152,7 +174,15 @@ class TestPlaylists(unittest.TestCase):
|
|||||||
result = ie.extract('http://new.livestream.com/tedx/cityenglish')
|
result = ie.extract('http://new.livestream.com/tedx/cityenglish')
|
||||||
self.assertIsPlaylist(result)
|
self.assertIsPlaylist(result)
|
||||||
self.assertEqual(result['title'], 'TEDCity2.0 (English)')
|
self.assertEqual(result['title'], 'TEDCity2.0 (English)')
|
||||||
self.assertTrue(len(result['entries']) >= 4)
|
assertGreaterEqual(self, len(result['entries']), 4)
|
||||||
|
|
||||||
|
def test_livestreamoriginal_folder(self):
|
||||||
|
dl = FakeYDL()
|
||||||
|
ie = LivestreamOriginalIE(dl)
|
||||||
|
result = ie.extract('https://www.livestream.com/newplay/folder?dirId=a07bf706-d0e4-4e75-a747-b021d84f2fd3')
|
||||||
|
self.assertIsPlaylist(result)
|
||||||
|
self.assertEqual(result['id'], 'a07bf706-d0e4-4e75-a747-b021d84f2fd3')
|
||||||
|
assertGreaterEqual(self, len(result['entries']), 28)
|
||||||
|
|
||||||
def test_nhl_videocenter(self):
|
def test_nhl_videocenter(self):
|
||||||
dl = FakeYDL()
|
dl = FakeYDL()
|
||||||
@ -169,15 +199,15 @@ class TestPlaylists(unittest.TestCase):
|
|||||||
result = ie.extract('http://bambuser.com/channel/pixelversity')
|
result = ie.extract('http://bambuser.com/channel/pixelversity')
|
||||||
self.assertIsPlaylist(result)
|
self.assertIsPlaylist(result)
|
||||||
self.assertEqual(result['title'], 'pixelversity')
|
self.assertEqual(result['title'], 'pixelversity')
|
||||||
self.assertTrue(len(result['entries']) >= 60)
|
assertGreaterEqual(self, len(result['entries']), 60)
|
||||||
|
|
||||||
def test_bandcamp_album(self):
|
def test_bandcamp_album(self):
|
||||||
dl = FakeYDL()
|
dl = FakeYDL()
|
||||||
ie = BandcampAlbumIE(dl)
|
ie = BandcampAlbumIE(dl)
|
||||||
result = ie.extract('http://mpallante.bandcamp.com/album/nightmare-night-ep')
|
result = ie.extract('http://nightbringer.bandcamp.com/album/hierophany-of-the-open-grave')
|
||||||
self.assertIsPlaylist(result)
|
self.assertIsPlaylist(result)
|
||||||
self.assertEqual(result['title'], 'Nightmare Night EP')
|
self.assertEqual(result['title'], 'Hierophany of the Open Grave')
|
||||||
self.assertTrue(len(result['entries']) >= 4)
|
assertGreaterEqual(self, len(result['entries']), 9)
|
||||||
|
|
||||||
def test_smotri_community(self):
|
def test_smotri_community(self):
|
||||||
dl = FakeYDL()
|
dl = FakeYDL()
|
||||||
@ -186,7 +216,7 @@ class TestPlaylists(unittest.TestCase):
|
|||||||
self.assertIsPlaylist(result)
|
self.assertIsPlaylist(result)
|
||||||
self.assertEqual(result['id'], 'kommuna')
|
self.assertEqual(result['id'], 'kommuna')
|
||||||
self.assertEqual(result['title'], 'КПРФ')
|
self.assertEqual(result['title'], 'КПРФ')
|
||||||
self.assertTrue(len(result['entries']) >= 4)
|
assertGreaterEqual(self, len(result['entries']), 4)
|
||||||
|
|
||||||
def test_smotri_user(self):
|
def test_smotri_user(self):
|
||||||
dl = FakeYDL()
|
dl = FakeYDL()
|
||||||
@ -195,7 +225,7 @@ class TestPlaylists(unittest.TestCase):
|
|||||||
self.assertIsPlaylist(result)
|
self.assertIsPlaylist(result)
|
||||||
self.assertEqual(result['id'], 'inspector')
|
self.assertEqual(result['id'], 'inspector')
|
||||||
self.assertEqual(result['title'], 'Inspector')
|
self.assertEqual(result['title'], 'Inspector')
|
||||||
self.assertTrue(len(result['entries']) >= 9)
|
assertGreaterEqual(self, len(result['entries']), 9)
|
||||||
|
|
||||||
def test_AcademicEarthCourse(self):
|
def test_AcademicEarthCourse(self):
|
||||||
dl = FakeYDL()
|
dl = FakeYDL()
|
||||||
@ -214,7 +244,7 @@ class TestPlaylists(unittest.TestCase):
|
|||||||
self.assertIsPlaylist(result)
|
self.assertIsPlaylist(result)
|
||||||
self.assertEqual(result['id'], 'dvoe_iz_lartsa')
|
self.assertEqual(result['id'], 'dvoe_iz_lartsa')
|
||||||
self.assertEqual(result['title'], 'Двое из ларца (2006 - 2008)')
|
self.assertEqual(result['title'], 'Двое из ларца (2006 - 2008)')
|
||||||
self.assertTrue(len(result['entries']) >= 24)
|
assertGreaterEqual(self, len(result['entries']), 24)
|
||||||
|
|
||||||
def test_ivi_compilation_season(self):
|
def test_ivi_compilation_season(self):
|
||||||
dl = FakeYDL()
|
dl = FakeYDL()
|
||||||
@ -223,7 +253,7 @@ class TestPlaylists(unittest.TestCase):
|
|||||||
self.assertIsPlaylist(result)
|
self.assertIsPlaylist(result)
|
||||||
self.assertEqual(result['id'], 'dvoe_iz_lartsa/season1')
|
self.assertEqual(result['id'], 'dvoe_iz_lartsa/season1')
|
||||||
self.assertEqual(result['title'], 'Двое из ларца (2006 - 2008) 1 сезон')
|
self.assertEqual(result['title'], 'Двое из ларца (2006 - 2008) 1 сезон')
|
||||||
self.assertTrue(len(result['entries']) >= 12)
|
assertGreaterEqual(self, len(result['entries']), 12)
|
||||||
|
|
||||||
def test_imdb_list(self):
|
def test_imdb_list(self):
|
||||||
dl = FakeYDL()
|
dl = FakeYDL()
|
||||||
@ -242,7 +272,7 @@ class TestPlaylists(unittest.TestCase):
|
|||||||
self.assertEqual(result['id'], 'cryptography')
|
self.assertEqual(result['id'], 'cryptography')
|
||||||
self.assertEqual(result['title'], 'Journey into cryptography')
|
self.assertEqual(result['title'], 'Journey into cryptography')
|
||||||
self.assertEqual(result['description'], 'How have humans protected their secret messages through history? What has changed today?')
|
self.assertEqual(result['description'], 'How have humans protected their secret messages through history? What has changed today?')
|
||||||
self.assertTrue(len(result['entries']) >= 3)
|
assertGreaterEqual(self, len(result['entries']), 3)
|
||||||
|
|
||||||
def test_EveryonesMixtape(self):
|
def test_EveryonesMixtape(self):
|
||||||
dl = FakeYDL()
|
dl = FakeYDL()
|
||||||
@ -256,10 +286,18 @@ class TestPlaylists(unittest.TestCase):
|
|||||||
def test_rutube_channel(self):
|
def test_rutube_channel(self):
|
||||||
dl = FakeYDL()
|
dl = FakeYDL()
|
||||||
ie = RutubeChannelIE(dl)
|
ie = RutubeChannelIE(dl)
|
||||||
result = ie.extract('http://rutube.ru/tags/video/1409')
|
result = ie.extract('http://rutube.ru/tags/video/1800/')
|
||||||
self.assertIsPlaylist(result)
|
self.assertIsPlaylist(result)
|
||||||
self.assertEqual(result['id'], '1409')
|
self.assertEqual(result['id'], '1800')
|
||||||
self.assertTrue(len(result['entries']) >= 34)
|
assertGreaterEqual(self, len(result['entries']), 68)
|
||||||
|
|
||||||
|
def test_rutube_person(self):
|
||||||
|
dl = FakeYDL()
|
||||||
|
ie = RutubePersonIE(dl)
|
||||||
|
result = ie.extract('http://rutube.ru/video/person/313878/')
|
||||||
|
self.assertIsPlaylist(result)
|
||||||
|
self.assertEqual(result['id'], '313878')
|
||||||
|
assertGreaterEqual(self, len(result['entries']), 37)
|
||||||
|
|
||||||
def test_multiple_brightcove_videos(self):
|
def test_multiple_brightcove_videos(self):
|
||||||
# https://github.com/rg3/youtube-dl/issues/2283
|
# https://github.com/rg3/youtube-dl/issues/2283
|
||||||
@ -296,7 +334,7 @@ class TestPlaylists(unittest.TestCase):
|
|||||||
self.assertIsPlaylist(result)
|
self.assertIsPlaylist(result)
|
||||||
self.assertEqual(result['id'], '10')
|
self.assertEqual(result['id'], '10')
|
||||||
self.assertEqual(result['title'], 'Who are the hackers?')
|
self.assertEqual(result['title'], 'Who are the hackers?')
|
||||||
self.assertTrue(len(result['entries']) >= 6)
|
assertGreaterEqual(self, len(result['entries']), 6)
|
||||||
|
|
||||||
def test_toypics_user(self):
|
def test_toypics_user(self):
|
||||||
dl = FakeYDL()
|
dl = FakeYDL()
|
||||||
@ -304,7 +342,7 @@ class TestPlaylists(unittest.TestCase):
|
|||||||
result = ie.extract('http://videos.toypics.net/Mikey')
|
result = ie.extract('http://videos.toypics.net/Mikey')
|
||||||
self.assertIsPlaylist(result)
|
self.assertIsPlaylist(result)
|
||||||
self.assertEqual(result['id'], 'Mikey')
|
self.assertEqual(result['id'], 'Mikey')
|
||||||
self.assertTrue(len(result['entries']) >= 17)
|
assertGreaterEqual(self, len(result['entries']), 17)
|
||||||
|
|
||||||
def test_xtube_user(self):
|
def test_xtube_user(self):
|
||||||
dl = FakeYDL()
|
dl = FakeYDL()
|
||||||
@ -312,7 +350,7 @@ class TestPlaylists(unittest.TestCase):
|
|||||||
result = ie.extract('http://www.xtube.com/community/profile.php?user=greenshowers')
|
result = ie.extract('http://www.xtube.com/community/profile.php?user=greenshowers')
|
||||||
self.assertIsPlaylist(result)
|
self.assertIsPlaylist(result)
|
||||||
self.assertEqual(result['id'], 'greenshowers')
|
self.assertEqual(result['id'], 'greenshowers')
|
||||||
self.assertTrue(len(result['entries']) >= 155)
|
assertGreaterEqual(self, len(result['entries']), 155)
|
||||||
|
|
||||||
def test_InstagramUser(self):
|
def test_InstagramUser(self):
|
||||||
dl = FakeYDL()
|
dl = FakeYDL()
|
||||||
@ -320,7 +358,7 @@ class TestPlaylists(unittest.TestCase):
|
|||||||
result = ie.extract('http://instagram.com/porsche')
|
result = ie.extract('http://instagram.com/porsche')
|
||||||
self.assertIsPlaylist(result)
|
self.assertIsPlaylist(result)
|
||||||
self.assertEqual(result['id'], 'porsche')
|
self.assertEqual(result['id'], 'porsche')
|
||||||
self.assertTrue(len(result['entries']) >= 2)
|
assertGreaterEqual(self, len(result['entries']), 2)
|
||||||
test_video = next(
|
test_video = next(
|
||||||
e for e in result['entries']
|
e for e in result['entries']
|
||||||
if e['id'] == '614605558512799803_462752227')
|
if e['id'] == '614605558512799803_462752227')
|
||||||
@ -359,15 +397,15 @@ class TestPlaylists(unittest.TestCase):
|
|||||||
self.assertEqual(result['id'], '152147')
|
self.assertEqual(result['id'], '152147')
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
result['title'], 'Brace Yourself - Today\'s Weirdest News')
|
result['title'], 'Brace Yourself - Today\'s Weirdest News')
|
||||||
self.assertTrue(len(result['entries']) >= 10)
|
assertGreaterEqual(self, len(result['entries']), 10)
|
||||||
|
|
||||||
def test_TeacherTubeClassroom(self):
|
def test_TeacherTubeUser(self):
|
||||||
dl = FakeYDL()
|
dl = FakeYDL()
|
||||||
ie = TeacherTubeClassroomIE(dl)
|
ie = TeacherTubeUserIE(dl)
|
||||||
result = ie.extract('http://www.teachertube.com/view_classroom.php?user=rbhagwati2')
|
result = ie.extract('http://www.teachertube.com/user/profile/rbhagwati2')
|
||||||
self.assertIsPlaylist(result)
|
self.assertIsPlaylist(result)
|
||||||
self.assertEqual(result['id'], 'rbhagwati2')
|
self.assertEqual(result['id'], 'rbhagwati2')
|
||||||
self.assertTrue(len(result['entries']) >= 20)
|
assertGreaterEqual(self, len(result['entries']), 179)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -87,7 +87,7 @@ class TestYoutubeSubtitles(BaseTestSubtitles):
|
|||||||
|
|
||||||
def test_youtube_nosubtitles(self):
|
def test_youtube_nosubtitles(self):
|
||||||
self.DL.expect_warning(u'video doesn\'t have subtitles')
|
self.DL.expect_warning(u'video doesn\'t have subtitles')
|
||||||
self.url = 'sAjKT8FhjI8'
|
self.url = 'n5BB19UTcdA'
|
||||||
self.DL.params['writesubtitles'] = True
|
self.DL.params['writesubtitles'] = True
|
||||||
self.DL.params['allsubtitles'] = True
|
self.DL.params['allsubtitles'] = True
|
||||||
subtitles = self.getSubtitles()
|
subtitles = self.getSubtitles()
|
||||||
|
77
test/test_swfinterp.py
Normal file
77
test/test_swfinterp.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# Allow direct execution
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
|
||||||
|
import errno
|
||||||
|
import io
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from youtube_dl.swfinterp import SWFInterpreter
|
||||||
|
|
||||||
|
|
||||||
|
TEST_DIR = os.path.join(
|
||||||
|
os.path.dirname(os.path.abspath(__file__)), 'swftests')
|
||||||
|
|
||||||
|
|
||||||
|
class TestSWFInterpreter(unittest.TestCase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _make_testfunc(testfile):
|
||||||
|
m = re.match(r'^(.*)\.(as)$', testfile)
|
||||||
|
if not m:
|
||||||
|
return
|
||||||
|
test_id = m.group(1)
|
||||||
|
|
||||||
|
def test_func(self):
|
||||||
|
as_file = os.path.join(TEST_DIR, testfile)
|
||||||
|
swf_file = os.path.join(TEST_DIR, test_id + '.swf')
|
||||||
|
if ((not os.path.exists(swf_file))
|
||||||
|
or os.path.getmtime(swf_file) < os.path.getmtime(as_file)):
|
||||||
|
# Recompile
|
||||||
|
try:
|
||||||
|
subprocess.check_call(['mxmlc', '-output', swf_file, as_file])
|
||||||
|
except OSError as ose:
|
||||||
|
if ose.errno == errno.ENOENT:
|
||||||
|
print('mxmlc not found! Skipping test.')
|
||||||
|
return
|
||||||
|
raise
|
||||||
|
|
||||||
|
with open(swf_file, 'rb') as swf_f:
|
||||||
|
swf_content = swf_f.read()
|
||||||
|
swfi = SWFInterpreter(swf_content)
|
||||||
|
|
||||||
|
with io.open(as_file, 'r', encoding='utf-8') as as_f:
|
||||||
|
as_content = as_f.read()
|
||||||
|
|
||||||
|
def _find_spec(key):
|
||||||
|
m = re.search(
|
||||||
|
r'(?m)^//\s*%s:\s*(.*?)\n' % re.escape(key), as_content)
|
||||||
|
if not m:
|
||||||
|
raise ValueError('Cannot find %s in %s' % (key, testfile))
|
||||||
|
return json.loads(m.group(1))
|
||||||
|
|
||||||
|
input_args = _find_spec('input')
|
||||||
|
output = _find_spec('output')
|
||||||
|
|
||||||
|
swf_class = swfi.extract_class(test_id)
|
||||||
|
func = swfi.extract_function(swf_class, 'main')
|
||||||
|
res = func(input_args)
|
||||||
|
self.assertEqual(res, output)
|
||||||
|
|
||||||
|
test_func.__name__ = str('test_swf_' + test_id)
|
||||||
|
setattr(TestSWFInterpreter, test_func.__name__, test_func)
|
||||||
|
|
||||||
|
|
||||||
|
for testfile in os.listdir(TEST_DIR):
|
||||||
|
_make_testfunc(testfile)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
@ -280,7 +280,7 @@ class TestUtil(unittest.TestCase):
|
|||||||
d = json.loads(stripped)
|
d = json.loads(stripped)
|
||||||
self.assertEqual(d, [{"id": "532cb", "x": 3}])
|
self.assertEqual(d, [{"id": "532cb", "x": 3}])
|
||||||
|
|
||||||
def test_uppercase_escpae(self):
|
def test_uppercase_escape(self):
|
||||||
self.assertEqual(uppercase_escape(u'aä'), u'aä')
|
self.assertEqual(uppercase_escape(u'aä'), u'aä')
|
||||||
self.assertEqual(uppercase_escape(u'\\U0001d550'), u'𝕐')
|
self.assertEqual(uppercase_escape(u'\\U0001d550'), u'𝕐')
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
# Allow direct execution
|
# Allow direct execution
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
@ -16,23 +18,65 @@ from youtube_dl.utils import compat_str, compat_urlretrieve
|
|||||||
|
|
||||||
_TESTS = [
|
_TESTS = [
|
||||||
(
|
(
|
||||||
u'https://s.ytimg.com/yts/jsbin/html5player-vflHOr_nV.js',
|
'https://s.ytimg.com/yts/jsbin/html5player-vflHOr_nV.js',
|
||||||
u'js',
|
'js',
|
||||||
86,
|
86,
|
||||||
u'>=<;:/.-[+*)(\'&%$#"!ZYX0VUTSRQPONMLKJIHGFEDCBA\\yxwvutsrqponmlkjihgfedcba987654321',
|
'>=<;:/.-[+*)(\'&%$#"!ZYX0VUTSRQPONMLKJIHGFEDCBA\\yxwvutsrqponmlkjihgfedcba987654321',
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
u'https://s.ytimg.com/yts/jsbin/html5player-vfldJ8xgI.js',
|
'https://s.ytimg.com/yts/jsbin/html5player-vfldJ8xgI.js',
|
||||||
u'js',
|
'js',
|
||||||
85,
|
85,
|
||||||
u'3456789a0cdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRS[UVWXYZ!"#$%&\'()*+,-./:;<=>?@',
|
'3456789a0cdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRS[UVWXYZ!"#$%&\'()*+,-./:;<=>?@',
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
u'https://s.ytimg.com/yts/jsbin/html5player-vfle-mVwz.js',
|
'https://s.ytimg.com/yts/jsbin/html5player-vfle-mVwz.js',
|
||||||
u'js',
|
'js',
|
||||||
90,
|
90,
|
||||||
u']\\[@?>=<;:/.-,+*)(\'&%$#"hZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjiagfedcb39876',
|
']\\[@?>=<;:/.-,+*)(\'&%$#"hZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjiagfedcb39876',
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
'https://s.ytimg.com/yts/jsbin/html5player-en_US-vfl0Cbn9e.js',
|
||||||
|
'js',
|
||||||
|
84,
|
||||||
|
'O1I3456789abcde0ghijklmnopqrstuvwxyzABCDEFGHfJKLMN2PQRSTUVW@YZ!"#$%&\'()*+,-./:;<=',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'https://s.ytimg.com/yts/jsbin/html5player-en_US-vflXGBaUN.js',
|
||||||
|
'js',
|
||||||
|
'2ACFC7A61CA478CD21425E5A57EBD73DDC78E22A.2094302436B2D377D14A3BBA23022D023B8BC25AA',
|
||||||
|
'A52CB8B320D22032ABB3A41D773D2B6342034902.A22E87CDD37DBE75A5E52412DC874AC16A7CFCA2',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'http://s.ytimg.com/yts/swfbin/player-vfl5vIhK2/watch_as3.swf',
|
||||||
|
'swf',
|
||||||
|
86,
|
||||||
|
'O1I3456789abcde0ghijklmnopqrstuvwxyzABCDEFGHfJKLMN2PQRSTUVWXY\\!"#$%&\'()*+,-./:;<=>?'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'http://s.ytimg.com/yts/swfbin/player-vflmDyk47/watch_as3.swf',
|
||||||
|
'swf',
|
||||||
|
'F375F75BF2AFDAAF2666E43868D46816F83F13E81C46.3725A8218E446A0DECD33F79DC282994D6AA92C92C9',
|
||||||
|
'9C29AA6D499282CD97F33DCED0A644E8128A5273.64C18E31F38361864D86834E6662FAADFA2FB57F'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'https://s.ytimg.com/yts/jsbin/html5player-en_US-vflBb0OQx.js',
|
||||||
|
'js',
|
||||||
|
84,
|
||||||
|
'123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQ0STUVWXYZ!"#$%&\'()*+,@./:;<=>'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'https://s.ytimg.com/yts/jsbin/html5player-en_US-vfl9FYC6l.js',
|
||||||
|
'js',
|
||||||
|
83,
|
||||||
|
'123456789abcdefghijklmnopqr0tuvwxyzABCDETGHIJKLMNOPQRS>UVWXYZ!"#$%&\'()*+,-./:;<=F'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'https://s.ytimg.com/yts/jsbin/html5player-en_US-vflCGk6yw/html5player.js',
|
||||||
|
'js',
|
||||||
|
'4646B5181C6C3020DF1D9C7FCFEA.AD80ABF70C39BD369CCCAE780AFBB98FA6B6CB42766249D9488C288',
|
||||||
|
'82C8849D94266724DC6B6AF89BBFA087EACCD963.B93C07FBA084ACAEFCF7C9D1FD0203C6C1815B6B'
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -44,13 +88,13 @@ class TestSignature(unittest.TestCase):
|
|||||||
os.mkdir(self.TESTDATA_DIR)
|
os.mkdir(self.TESTDATA_DIR)
|
||||||
|
|
||||||
|
|
||||||
def make_tfunc(url, stype, sig_length, expected_sig):
|
def make_tfunc(url, stype, sig_input, expected_sig):
|
||||||
basename = url.rpartition('/')[2]
|
m = re.match(r'.*-([a-zA-Z0-9_-]+)(?:/watch_as3|/html5player)?\.[a-z]+$', url)
|
||||||
m = re.match(r'.*-([a-zA-Z0-9_-]+)\.[a-z]+$', basename)
|
assert m, '%r should follow URL format' % url
|
||||||
assert m, '%r should follow URL format' % basename
|
|
||||||
test_id = m.group(1)
|
test_id = m.group(1)
|
||||||
|
|
||||||
def test_func(self):
|
def test_func(self):
|
||||||
|
basename = 'player-%s.%s' % (test_id, stype)
|
||||||
fn = os.path.join(self.TESTDATA_DIR, basename)
|
fn = os.path.join(self.TESTDATA_DIR, basename)
|
||||||
|
|
||||||
if not os.path.exists(fn):
|
if not os.path.exists(fn):
|
||||||
@ -66,7 +110,9 @@ def make_tfunc(url, stype, sig_length, expected_sig):
|
|||||||
with open(fn, 'rb') as testf:
|
with open(fn, 'rb') as testf:
|
||||||
swfcode = testf.read()
|
swfcode = testf.read()
|
||||||
func = ie._parse_sig_swf(swfcode)
|
func = ie._parse_sig_swf(swfcode)
|
||||||
src_sig = compat_str(string.printable[:sig_length])
|
src_sig = (
|
||||||
|
compat_str(string.printable[:sig_input])
|
||||||
|
if isinstance(sig_input, int) else sig_input)
|
||||||
got_sig = func(src_sig)
|
got_sig = func(src_sig)
|
||||||
self.assertEqual(got_sig, expected_sig)
|
self.assertEqual(got_sig, expected_sig)
|
||||||
|
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
# Legacy file for backwards compatibility, use youtube_dl.downloader instead!
|
|
||||||
from .downloader import FileDownloader as RealFileDownloader
|
|
||||||
from .downloader import get_suitable_downloader
|
|
||||||
|
|
||||||
|
|
||||||
# This class reproduces the old behaviour of FileDownloader
|
|
||||||
class FileDownloader(RealFileDownloader):
|
|
||||||
def _do_download(self, filename, info_dict):
|
|
||||||
real_fd = get_suitable_downloader(info_dict)(self.ydl, self.params)
|
|
||||||
for ph in self._progress_hooks:
|
|
||||||
real_fd.add_progress_hook(ph)
|
|
||||||
return real_fd.download(filename, info_dict)
|
|
@ -162,6 +162,7 @@ class YoutubeDL(object):
|
|||||||
default_search: Prepend this string if an input url is not valid.
|
default_search: Prepend this string if an input url is not valid.
|
||||||
'auto' for elaborate guessing
|
'auto' for elaborate guessing
|
||||||
encoding: Use this encoding instead of the system-specified.
|
encoding: Use this encoding instead of the system-specified.
|
||||||
|
extract_flat: Do not resolve URLs, return the immediate result.
|
||||||
|
|
||||||
The following parameters are not used by YoutubeDL itself, they are used by
|
The following parameters are not used by YoutubeDL itself, they are used by
|
||||||
the FileDownloader:
|
the FileDownloader:
|
||||||
@ -275,7 +276,7 @@ class YoutubeDL(object):
|
|||||||
return message
|
return message
|
||||||
|
|
||||||
assert hasattr(self, '_output_process')
|
assert hasattr(self, '_output_process')
|
||||||
assert type(message) == type('')
|
assert isinstance(message, compat_str)
|
||||||
line_count = message.count('\n') + 1
|
line_count = message.count('\n') + 1
|
||||||
self._output_process.stdin.write((message + '\n').encode('utf-8'))
|
self._output_process.stdin.write((message + '\n').encode('utf-8'))
|
||||||
self._output_process.stdin.flush()
|
self._output_process.stdin.flush()
|
||||||
@ -303,7 +304,7 @@ class YoutubeDL(object):
|
|||||||
|
|
||||||
def to_stderr(self, message):
|
def to_stderr(self, message):
|
||||||
"""Print message to stderr."""
|
"""Print message to stderr."""
|
||||||
assert type(message) == type('')
|
assert isinstance(message, compat_str)
|
||||||
if self.params.get('logger'):
|
if self.params.get('logger'):
|
||||||
self.params['logger'].error(message)
|
self.params['logger'].error(message)
|
||||||
else:
|
else:
|
||||||
@ -479,7 +480,10 @@ class YoutubeDL(object):
|
|||||||
return 'Skipping %s, because it has exceeded the maximum view count (%d/%d)' % (video_title, view_count, max_views)
|
return 'Skipping %s, because it has exceeded the maximum view count (%d/%d)' % (video_title, view_count, max_views)
|
||||||
age_limit = self.params.get('age_limit')
|
age_limit = self.params.get('age_limit')
|
||||||
if age_limit is not None:
|
if age_limit is not None:
|
||||||
if age_limit < info_dict.get('age_limit', 0):
|
actual_age_limit = info_dict.get('age_limit')
|
||||||
|
if actual_age_limit is None:
|
||||||
|
actual_age_limit = 0
|
||||||
|
if age_limit < actual_age_limit:
|
||||||
return 'Skipping "' + title + '" because it is age restricted'
|
return 'Skipping "' + title + '" because it is age restricted'
|
||||||
if self.in_download_archive(info_dict):
|
if self.in_download_archive(info_dict):
|
||||||
return '%s has already been recorded in archive' % video_title
|
return '%s has already been recorded in archive' % video_title
|
||||||
@ -558,7 +562,12 @@ class YoutubeDL(object):
|
|||||||
Returns the resolved ie_result.
|
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
|
result_type = ie_result.get('_type', 'video')
|
||||||
|
|
||||||
|
if self.params.get('extract_flat', False):
|
||||||
|
if result_type in ('url', 'url_transparent'):
|
||||||
|
return ie_result
|
||||||
|
|
||||||
if result_type == 'video':
|
if result_type == 'video':
|
||||||
self.add_extra_info(ie_result, extra_info)
|
self.add_extra_info(ie_result, extra_info)
|
||||||
return self.process_video_result(ie_result, download=download)
|
return self.process_video_result(ie_result, download=download)
|
||||||
@ -849,7 +858,7 @@ class YoutubeDL(object):
|
|||||||
# Keep for backwards compatibility
|
# Keep for backwards compatibility
|
||||||
info_dict['stitle'] = info_dict['title']
|
info_dict['stitle'] = info_dict['title']
|
||||||
|
|
||||||
if not 'format' in info_dict:
|
if 'format' not in info_dict:
|
||||||
info_dict['format'] = info_dict['ext']
|
info_dict['format'] = info_dict['ext']
|
||||||
|
|
||||||
reason = self._match_entry(info_dict)
|
reason = self._match_entry(info_dict)
|
||||||
@ -993,11 +1002,13 @@ class YoutubeDL(object):
|
|||||||
fd = get_suitable_downloader(info)(self, self.params)
|
fd = get_suitable_downloader(info)(self, self.params)
|
||||||
for ph in self._progress_hooks:
|
for ph in self._progress_hooks:
|
||||||
fd.add_progress_hook(ph)
|
fd.add_progress_hook(ph)
|
||||||
|
if self.params.get('verbose'):
|
||||||
|
self.to_stdout('[debug] Invoking downloader on %r' % info.get('url'))
|
||||||
return fd.download(name, info)
|
return fd.download(name, info)
|
||||||
if info_dict.get('requested_formats') is not None:
|
if info_dict.get('requested_formats') is not None:
|
||||||
downloaded = []
|
downloaded = []
|
||||||
success = True
|
success = True
|
||||||
merger = FFmpegMergerPP(self)
|
merger = FFmpegMergerPP(self, not self.params.get('keepvideo'))
|
||||||
if not merger._get_executable():
|
if not merger._get_executable():
|
||||||
postprocessors = []
|
postprocessors = []
|
||||||
self.report_warning('You have requested multiple '
|
self.report_warning('You have requested multiple '
|
||||||
@ -1195,6 +1206,10 @@ class YoutubeDL(object):
|
|||||||
if res:
|
if res:
|
||||||
res += ', '
|
res += ', '
|
||||||
res += format_bytes(fdict['filesize'])
|
res += format_bytes(fdict['filesize'])
|
||||||
|
elif fdict.get('filesize_approx') is not None:
|
||||||
|
if res:
|
||||||
|
res += ', '
|
||||||
|
res += '~' + format_bytes(fdict['filesize_approx'])
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def list_formats(self, info_dict):
|
def list_formats(self, info_dict):
|
||||||
@ -1228,14 +1243,18 @@ class YoutubeDL(object):
|
|||||||
if not self.params.get('verbose'):
|
if not self.params.get('verbose'):
|
||||||
return
|
return
|
||||||
|
|
||||||
write_string(
|
if type('') is not compat_str:
|
||||||
|
# Python 2.6 on SLES11 SP1 (https://github.com/rg3/youtube-dl/issues/3326)
|
||||||
|
self.report_warning(
|
||||||
|
'Your Python is broken! Update to a newer and supported version')
|
||||||
|
|
||||||
|
encoding_str = (
|
||||||
'[debug] Encodings: locale %s, fs %s, out %s, pref %s\n' % (
|
'[debug] Encodings: locale %s, fs %s, out %s, pref %s\n' % (
|
||||||
locale.getpreferredencoding(),
|
locale.getpreferredencoding(),
|
||||||
sys.getfilesystemencoding(),
|
sys.getfilesystemencoding(),
|
||||||
sys.stdout.encoding,
|
sys.stdout.encoding,
|
||||||
self.get_encoding()),
|
self.get_encoding()))
|
||||||
encoding=None
|
write_string(encoding_str, encoding=None)
|
||||||
)
|
|
||||||
|
|
||||||
self._write_string('[debug] youtube-dl version ' + __version__ + '\n')
|
self._write_string('[debug] youtube-dl version ' + __version__ + '\n')
|
||||||
try:
|
try:
|
||||||
|
@ -59,18 +59,29 @@ __authors__ = (
|
|||||||
'Adam Thalhammer',
|
'Adam Thalhammer',
|
||||||
'Georg Jähnig',
|
'Georg Jähnig',
|
||||||
'Ralf Haring',
|
'Ralf Haring',
|
||||||
|
'Koki Takahashi',
|
||||||
|
'Ariset Llerena',
|
||||||
|
'Adam Malcontenti-Wilson',
|
||||||
|
'Tobias Bell',
|
||||||
|
'Naglis Jonaitis',
|
||||||
|
'Charles Chen',
|
||||||
|
'Hassaan Ali',
|
||||||
|
'Dobrosław Żybort',
|
||||||
|
'David Fabijan',
|
||||||
|
'Sebastian Haas',
|
||||||
|
'Alexander Kirk',
|
||||||
|
'Erik Johnson',
|
||||||
)
|
)
|
||||||
|
|
||||||
__license__ = 'Public Domain'
|
__license__ = 'Public Domain'
|
||||||
|
|
||||||
import codecs
|
import codecs
|
||||||
import io
|
import io
|
||||||
import locale
|
|
||||||
import optparse
|
import optparse
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
import re
|
|
||||||
import shlex
|
import shlex
|
||||||
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
||||||
@ -92,7 +103,7 @@ from .utils import (
|
|||||||
write_string,
|
write_string,
|
||||||
)
|
)
|
||||||
from .update import update_self
|
from .update import update_self
|
||||||
from .FileDownloader import (
|
from .downloader import (
|
||||||
FileDownloader,
|
FileDownloader,
|
||||||
)
|
)
|
||||||
from .extractor import gen_extractors
|
from .extractor import gen_extractors
|
||||||
@ -217,6 +228,7 @@ def parseOpts(overrideArguments=None):
|
|||||||
downloader = optparse.OptionGroup(parser, 'Download Options')
|
downloader = optparse.OptionGroup(parser, 'Download Options')
|
||||||
postproc = optparse.OptionGroup(parser, 'Post-processing Options')
|
postproc = optparse.OptionGroup(parser, 'Post-processing Options')
|
||||||
filesystem = optparse.OptionGroup(parser, 'Filesystem Options')
|
filesystem = optparse.OptionGroup(parser, 'Filesystem Options')
|
||||||
|
workarounds = optparse.OptionGroup(parser, 'Workarounds')
|
||||||
verbosity = optparse.OptionGroup(parser, 'Verbosity / Simulation Options')
|
verbosity = optparse.OptionGroup(parser, 'Verbosity / Simulation Options')
|
||||||
|
|
||||||
general.add_option('-h', '--help',
|
general.add_option('-h', '--help',
|
||||||
@ -233,14 +245,6 @@ def parseOpts(overrideArguments=None):
|
|||||||
general.add_option('--dump-user-agent',
|
general.add_option('--dump-user-agent',
|
||||||
action='store_true', dest='dump_user_agent',
|
action='store_true', dest='dump_user_agent',
|
||||||
help='display the current browser identification', default=False)
|
help='display the current browser identification', default=False)
|
||||||
general.add_option('--user-agent',
|
|
||||||
dest='user_agent', help='specify a custom user agent', metavar='UA')
|
|
||||||
general.add_option('--referer',
|
|
||||||
dest='referer', help='specify a custom referer, use if the video access is restricted to one domain',
|
|
||||||
metavar='REF', default=None)
|
|
||||||
general.add_option('--add-header',
|
|
||||||
dest='headers', help='specify a custom HTTP header and its value, separated by a colon \':\'. You can use this option multiple times', action="append",
|
|
||||||
metavar='FIELD:VALUE')
|
|
||||||
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)
|
||||||
@ -250,33 +254,17 @@ def parseOpts(overrideArguments=None):
|
|||||||
general.add_option(
|
general.add_option(
|
||||||
'--proxy', dest='proxy', default=None, metavar='URL',
|
'--proxy', dest='proxy', default=None, metavar='URL',
|
||||||
help='Use the specified HTTP/HTTPS proxy. Pass in an empty string (--proxy "") for direct connection')
|
help='Use the specified HTTP/HTTPS proxy. Pass in an empty string (--proxy "") for direct connection')
|
||||||
general.add_option('--no-check-certificate', action='store_true', dest='no_check_certificate', default=False, help='Suppress HTTPS certificate validation.')
|
|
||||||
general.add_option(
|
|
||||||
'--prefer-insecure', '--prefer-unsecure', action='store_true', dest='prefer_insecure',
|
|
||||||
help='Use an unencrypted connection to retrieve information about the video. (Currently supported only for YouTube)')
|
|
||||||
general.add_option(
|
|
||||||
'--cache-dir', dest='cachedir', default=get_cachedir(), metavar='DIR',
|
|
||||||
help='Location in the filesystem where youtube-dl can store some downloaded information permanently. By default $XDG_CACHE_HOME/youtube-dl or ~/.cache/youtube-dl . At the moment, only YouTube player files (for videos with obfuscated signatures) are cached, but that may change.')
|
|
||||||
general.add_option(
|
|
||||||
'--no-cache-dir', action='store_const', const=None, dest='cachedir',
|
|
||||||
help='Disable filesystem caching')
|
|
||||||
general.add_option(
|
general.add_option(
|
||||||
'--socket-timeout', dest='socket_timeout',
|
'--socket-timeout', dest='socket_timeout',
|
||||||
type=float, default=None, help=u'Time to wait before giving up, in seconds')
|
type=float, default=None, help=u'Time to wait before giving up, in seconds')
|
||||||
general.add_option(
|
|
||||||
'--bidi-workaround', dest='bidi_workaround', action='store_true',
|
|
||||||
help=u'Work around terminals that lack bidirectional text support. Requires bidiv or fribidi executable in PATH')
|
|
||||||
general.add_option(
|
general.add_option(
|
||||||
'--default-search',
|
'--default-search',
|
||||||
dest='default_search', metavar='PREFIX',
|
dest='default_search', metavar='PREFIX',
|
||||||
help='Use this prefix for unqualified URLs. For example "gvsearch2:" downloads two videos from google videos for youtube-dl "large apple". By default (with value "auto") youtube-dl guesses.')
|
help='Use this prefix for unqualified URLs. For example "gvsearch2:" downloads two videos from google videos for youtube-dl "large apple". Use the value "auto" to let youtube-dl guess ("auto_warning" to emit a warning when guessing). "error" just throws an error. The default value "fixup_error" repairs broken URLs, but emits an error if this is not possible instead of searching.')
|
||||||
general.add_option(
|
general.add_option(
|
||||||
'--ignore-config',
|
'--ignore-config',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help='Do not read configuration files. When given in the global configuration file /etc/youtube-dl.conf: do not read the user configuration in ~/.config/youtube-dl.conf (%APPDATA%/youtube-dl/config.txt on Windows)')
|
help='Do not read configuration files. When given in the global configuration file /etc/youtube-dl.conf: do not read the user configuration in ~/.config/youtube-dl.conf (%APPDATA%/youtube-dl/config.txt on Windows)')
|
||||||
general.add_option(
|
|
||||||
'--encoding', dest='encoding', metavar='ENCODING',
|
|
||||||
help='Force the specified encoding (experimental)')
|
|
||||||
|
|
||||||
selection.add_option(
|
selection.add_option(
|
||||||
'--playlist-start',
|
'--playlist-start',
|
||||||
@ -377,6 +365,33 @@ def parseOpts(overrideArguments=None):
|
|||||||
help='do not automatically adjust the buffer size. By default, the buffer size is automatically resized from an initial value of SIZE.', default=False)
|
help='do not automatically adjust the buffer size. By default, the buffer size is automatically resized from an initial value of SIZE.', default=False)
|
||||||
downloader.add_option('--test', action='store_true', dest='test', default=False, help=optparse.SUPPRESS_HELP)
|
downloader.add_option('--test', action='store_true', dest='test', default=False, help=optparse.SUPPRESS_HELP)
|
||||||
|
|
||||||
|
workarounds.add_option(
|
||||||
|
'--encoding', dest='encoding', metavar='ENCODING',
|
||||||
|
help='Force the specified encoding (experimental)')
|
||||||
|
workarounds.add_option(
|
||||||
|
'--no-check-certificate', action='store_true',
|
||||||
|
dest='no_check_certificate', default=False,
|
||||||
|
help='Suppress HTTPS certificate validation.')
|
||||||
|
workarounds.add_option(
|
||||||
|
'--prefer-insecure', '--prefer-unsecure', action='store_true', dest='prefer_insecure',
|
||||||
|
help='Use an unencrypted connection to retrieve information about the video. (Currently supported only for YouTube)')
|
||||||
|
workarounds.add_option(
|
||||||
|
'--user-agent', metavar='UA',
|
||||||
|
dest='user_agent', help='specify a custom user agent')
|
||||||
|
workarounds.add_option(
|
||||||
|
'--referer', metavar='REF',
|
||||||
|
dest='referer', default=None,
|
||||||
|
help='specify a custom referer, use if the video access is restricted to one domain',
|
||||||
|
)
|
||||||
|
workarounds.add_option(
|
||||||
|
'--add-header', metavar='FIELD:VALUE',
|
||||||
|
dest='headers', action='append',
|
||||||
|
help='specify a custom HTTP header and its value, separated by a colon \':\'. You can use this option multiple times',
|
||||||
|
)
|
||||||
|
workarounds.add_option(
|
||||||
|
'--bidi-workaround', dest='bidi_workaround', action='store_true',
|
||||||
|
help=u'Work around terminals that lack bidirectional text support. Requires bidiv or fribidi executable in PATH')
|
||||||
|
|
||||||
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)
|
||||||
verbosity.add_option(
|
verbosity.add_option(
|
||||||
@ -434,12 +449,10 @@ def parseOpts(overrideArguments=None):
|
|||||||
help='Display sent and read HTTP traffic')
|
help='Display sent and read HTTP traffic')
|
||||||
|
|
||||||
|
|
||||||
filesystem.add_option('-t', '--title',
|
filesystem.add_option('-a', '--batch-file',
|
||||||
action='store_true', dest='usetitle', help='use title in file name (default)', default=False)
|
dest='batchfile', metavar='FILE', help='file containing URLs to download (\'-\' for stdin)')
|
||||||
filesystem.add_option('--id',
|
filesystem.add_option('--id',
|
||||||
action='store_true', dest='useid', help='use only video ID in file name', default=False)
|
action='store_true', dest='useid', help='use only video ID in file name', default=False)
|
||||||
filesystem.add_option('-l', '--literal',
|
|
||||||
action='store_true', dest='usetitle', help='[deprecated] alias of --title', default=False)
|
|
||||||
filesystem.add_option('-A', '--auto-number',
|
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)
|
||||||
@ -465,11 +478,10 @@ def parseOpts(overrideArguments=None):
|
|||||||
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)
|
||||||
filesystem.add_option('-a', '--batch-file',
|
filesystem.add_option('-t', '--title',
|
||||||
dest='batchfile', metavar='FILE', help='file containing URLs to download (\'-\' for stdin)')
|
action='store_true', dest='usetitle', help='[deprecated] use title in file name (default)', default=False)
|
||||||
filesystem.add_option('--load-info',
|
filesystem.add_option('-l', '--literal',
|
||||||
dest='load_info_filename', metavar='FILE',
|
action='store_true', dest='usetitle', help='[deprecated] alias of --title', default=False)
|
||||||
help='json file containing the video information (created with the "--write-json" option)')
|
|
||||||
filesystem.add_option('-w', '--no-overwrites',
|
filesystem.add_option('-w', '--no-overwrites',
|
||||||
action='store_true', dest='nooverwrites', help='do not overwrite files', default=False)
|
action='store_true', dest='nooverwrites', help='do not overwrite files', default=False)
|
||||||
filesystem.add_option('-c', '--continue',
|
filesystem.add_option('-c', '--continue',
|
||||||
@ -477,8 +489,6 @@ def parseOpts(overrideArguments=None):
|
|||||||
filesystem.add_option('--no-continue',
|
filesystem.add_option('--no-continue',
|
||||||
action='store_false', dest='continue_dl',
|
action='store_false', dest='continue_dl',
|
||||||
help='do not resume partially downloaded files (restart from beginning)')
|
help='do not resume partially downloaded files (restart from beginning)')
|
||||||
filesystem.add_option('--cookies',
|
|
||||||
dest='cookiefile', metavar='FILE', help='file to read cookies from and dump cookie jar in')
|
|
||||||
filesystem.add_option('--no-part',
|
filesystem.add_option('--no-part',
|
||||||
action='store_true', dest='nopart', help='do not use .part files', default=False)
|
action='store_true', dest='nopart', help='do not use .part files', default=False)
|
||||||
filesystem.add_option('--no-mtime',
|
filesystem.add_option('--no-mtime',
|
||||||
@ -496,6 +506,20 @@ def parseOpts(overrideArguments=None):
|
|||||||
filesystem.add_option('--write-thumbnail',
|
filesystem.add_option('--write-thumbnail',
|
||||||
action='store_true', dest='writethumbnail',
|
action='store_true', dest='writethumbnail',
|
||||||
help='write thumbnail image to disk', default=False)
|
help='write thumbnail image to disk', default=False)
|
||||||
|
filesystem.add_option('--load-info',
|
||||||
|
dest='load_info_filename', metavar='FILE',
|
||||||
|
help='json file containing the video information (created with the "--write-json" option)')
|
||||||
|
filesystem.add_option('--cookies',
|
||||||
|
dest='cookiefile', metavar='FILE', help='file to read cookies from and dump cookie jar in')
|
||||||
|
filesystem.add_option(
|
||||||
|
'--cache-dir', dest='cachedir', default=get_cachedir(), metavar='DIR',
|
||||||
|
help='Location in the filesystem where youtube-dl can store some downloaded information permanently. By default $XDG_CACHE_HOME/youtube-dl or ~/.cache/youtube-dl . At the moment, only YouTube player files (for videos with obfuscated signatures) are cached, but that may change.')
|
||||||
|
filesystem.add_option(
|
||||||
|
'--no-cache-dir', action='store_const', const=None, dest='cachedir',
|
||||||
|
help='Disable filesystem caching')
|
||||||
|
filesystem.add_option(
|
||||||
|
'--rm-cache-dir', action='store_true', dest='rm_cachedir',
|
||||||
|
help='Delete all filesystem cache files')
|
||||||
|
|
||||||
|
|
||||||
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,
|
||||||
@ -505,7 +529,7 @@ def parseOpts(overrideArguments=None):
|
|||||||
postproc.add_option('--audio-quality', metavar='QUALITY', dest='audioquality', default='5',
|
postproc.add_option('--audio-quality', metavar='QUALITY', dest='audioquality', default='5',
|
||||||
help='ffmpeg/avconv audio quality specification, insert a value between 0 (better) and 9 (worse) for VBR or a specific bitrate like 128K (default 5)')
|
help='ffmpeg/avconv audio quality specification, insert a value between 0 (better) and 9 (worse) for VBR or a specific bitrate like 128K (default 5)')
|
||||||
postproc.add_option('--recode-video', metavar='FORMAT', dest='recodevideo', default=None,
|
postproc.add_option('--recode-video', metavar='FORMAT', dest='recodevideo', default=None,
|
||||||
help='Encode the video to another format if necessary (currently supported: mp4|flv|ogg|webm)')
|
help='Encode the video to another format if necessary (currently supported: mp4|flv|ogg|webm|mkv)')
|
||||||
postproc.add_option('-k', '--keep-video', action='store_true', dest='keepvideo', default=False,
|
postproc.add_option('-k', '--keep-video', action='store_true', dest='keepvideo', default=False,
|
||||||
help='keeps the video file on disk after the post-processing; the video is erased by default')
|
help='keeps the video file on disk after the post-processing; the video is erased by default')
|
||||||
postproc.add_option('--no-post-overwrites', action='store_true', dest='nopostoverwrites', default=False,
|
postproc.add_option('--no-post-overwrites', action='store_true', dest='nopostoverwrites', default=False,
|
||||||
@ -529,6 +553,7 @@ def parseOpts(overrideArguments=None):
|
|||||||
parser.add_option_group(downloader)
|
parser.add_option_group(downloader)
|
||||||
parser.add_option_group(filesystem)
|
parser.add_option_group(filesystem)
|
||||||
parser.add_option_group(verbosity)
|
parser.add_option_group(verbosity)
|
||||||
|
parser.add_option_group(workarounds)
|
||||||
parser.add_option_group(video_format)
|
parser.add_option_group(video_format)
|
||||||
parser.add_option_group(subtitles)
|
parser.add_option_group(subtitles)
|
||||||
parser.add_option_group(authentication)
|
parser.add_option_group(authentication)
|
||||||
@ -628,7 +653,7 @@ def _real_main(argv=None):
|
|||||||
if desc is False:
|
if desc is False:
|
||||||
continue
|
continue
|
||||||
if hasattr(ie, 'SEARCH_KEY'):
|
if hasattr(ie, 'SEARCH_KEY'):
|
||||||
_SEARCHES = (u'cute kittens', u'slithering pythons', u'falling cat', u'angry poodle', u'purple fish', u'running tortoise')
|
_SEARCHES = (u'cute kittens', u'slithering pythons', u'falling cat', u'angry poodle', u'purple fish', u'running tortoise', u'sleeping bunny')
|
||||||
_COUNTS = (u'', u'5', u'10', u'all')
|
_COUNTS = (u'', u'5', u'10', u'all')
|
||||||
desc += u' (Example: "%s%s:%s" )' % (ie.SEARCH_KEY, random.choice(_COUNTS), random.choice(_SEARCHES))
|
desc += u' (Example: "%s%s:%s" )' % (ie.SEARCH_KEY, random.choice(_COUNTS), random.choice(_SEARCHES))
|
||||||
compat_print(desc)
|
compat_print(desc)
|
||||||
@ -689,7 +714,7 @@ def _real_main(argv=None):
|
|||||||
date = DateRange.day(opts.date)
|
date = DateRange.day(opts.date)
|
||||||
else:
|
else:
|
||||||
date = DateRange(opts.dateafter, opts.datebefore)
|
date = DateRange(opts.dateafter, opts.datebefore)
|
||||||
if opts.default_search not in ('auto', 'auto_warning', None) and ':' not in opts.default_search:
|
if opts.default_search not in ('auto', 'auto_warning', 'error', 'fixup_error', None) and ':' not in opts.default_search:
|
||||||
parser.error(u'--default-search invalid; did you forget a colon (:) at the end?')
|
parser.error(u'--default-search invalid; did you forget a colon (:) at the end?')
|
||||||
|
|
||||||
# Do not download videos when there are audio-only formats
|
# Do not download videos when there are audio-only formats
|
||||||
@ -828,9 +853,26 @@ def _real_main(argv=None):
|
|||||||
if opts.update_self:
|
if opts.update_self:
|
||||||
update_self(ydl.to_screen, opts.verbose)
|
update_self(ydl.to_screen, opts.verbose)
|
||||||
|
|
||||||
|
# Remove cache dir
|
||||||
|
if opts.rm_cachedir:
|
||||||
|
if opts.cachedir is None:
|
||||||
|
ydl.to_screen(u'No cache dir specified (Did you combine --no-cache-dir and --rm-cache-dir?)')
|
||||||
|
else:
|
||||||
|
if ('.cache' not in opts.cachedir) or ('youtube-dl' not in opts.cachedir):
|
||||||
|
ydl.to_screen(u'Not removing directory %s - this does not look like a cache dir')
|
||||||
|
retcode = 141
|
||||||
|
else:
|
||||||
|
ydl.to_screen(
|
||||||
|
u'Removing cache dir %s .' % opts.cachedir,
|
||||||
|
skip_eol=True)
|
||||||
|
if os.path.exists(opts.cachedir):
|
||||||
|
ydl.to_screen(u'.', skip_eol=True)
|
||||||
|
shutil.rmtree(opts.cachedir)
|
||||||
|
ydl.to_screen(u'.')
|
||||||
|
|
||||||
# Maybe do nothing
|
# Maybe do nothing
|
||||||
if (len(all_urls) < 1) and (opts.load_info_filename is None):
|
if (len(all_urls) < 1) and (opts.load_info_filename is None):
|
||||||
if not opts.update_self:
|
if not (opts.update_self or opts.rm_cachedir):
|
||||||
parser.error(u'you must provide at least one URL')
|
parser.error(u'you must provide at least one URL')
|
||||||
else:
|
else:
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
@ -292,7 +292,7 @@ class FileDownloader(object):
|
|||||||
|
|
||||||
def real_download(self, filename, info_dict):
|
def real_download(self, filename, info_dict):
|
||||||
"""Real download process. Redefine in subclasses."""
|
"""Real download process. Redefine in subclasses."""
|
||||||
raise NotImplementedError(u'This method must be implemented by sublcasses')
|
raise NotImplementedError(u'This method must be implemented by subclasses')
|
||||||
|
|
||||||
def _hook_progress(self, status):
|
def _hook_progress(self, status):
|
||||||
for ph in self._progress_hooks:
|
for ph in self._progress_hooks:
|
||||||
|
@ -220,6 +220,7 @@ class F4mFD(FileDownloader):
|
|||||||
|
|
||||||
def real_download(self, filename, info_dict):
|
def real_download(self, filename, info_dict):
|
||||||
man_url = info_dict['url']
|
man_url = info_dict['url']
|
||||||
|
requested_bitrate = info_dict.get('tbr')
|
||||||
self.to_screen('[download] Downloading f4m manifest')
|
self.to_screen('[download] Downloading f4m manifest')
|
||||||
manifest = self.ydl.urlopen(man_url).read()
|
manifest = self.ydl.urlopen(man_url).read()
|
||||||
self.report_destination(filename)
|
self.report_destination(filename)
|
||||||
@ -233,8 +234,14 @@ class F4mFD(FileDownloader):
|
|||||||
|
|
||||||
doc = etree.fromstring(manifest)
|
doc = etree.fromstring(manifest)
|
||||||
formats = [(int(f.attrib.get('bitrate', -1)), f) for f in doc.findall(_add_ns('media'))]
|
formats = [(int(f.attrib.get('bitrate', -1)), f) for f in doc.findall(_add_ns('media'))]
|
||||||
formats = sorted(formats, key=lambda f: f[0])
|
if requested_bitrate is None:
|
||||||
rate, media = formats[-1]
|
# get the best format
|
||||||
|
formats = sorted(formats, key=lambda f: f[0])
|
||||||
|
rate, media = formats[-1]
|
||||||
|
else:
|
||||||
|
rate, media = list(filter(
|
||||||
|
lambda f: int(f[0]) == requested_bitrate, formats))[0]
|
||||||
|
|
||||||
base_url = compat_urlparse.urljoin(man_url, media.attrib['url'])
|
base_url = compat_urlparse.urljoin(man_url, media.attrib['url'])
|
||||||
bootstrap = base64.b64decode(doc.find(_add_ns('bootstrapInfo')).text)
|
bootstrap = base64.b64decode(doc.find(_add_ns('bootstrapInfo')).text)
|
||||||
metadata = base64.b64decode(media.find(_add_ns('metadata')).text)
|
metadata = base64.b64decode(media.find(_add_ns('metadata')).text)
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
|
from .abc import ABCIE
|
||||||
from .academicearth import AcademicEarthCourseIE
|
from .academicearth import AcademicEarthCourseIE
|
||||||
from .addanime import AddAnimeIE
|
from .addanime import AddAnimeIE
|
||||||
|
from .adultswim import AdultSwimIE
|
||||||
from .aftonbladet import AftonbladetIE
|
from .aftonbladet import AftonbladetIE
|
||||||
from .anitube import AnitubeIE
|
from .anitube import AnitubeIE
|
||||||
from .aol import AolIE
|
from .aol import AolIE
|
||||||
|
from .allocine import AllocineIE
|
||||||
from .aparat import AparatIE
|
from .aparat import AparatIE
|
||||||
from .appletrailers import AppleTrailersIE
|
from .appletrailers import AppleTrailersIE
|
||||||
from .archiveorg import ArchiveOrgIE
|
from .archiveorg import ArchiveOrgIE
|
||||||
@ -51,6 +54,7 @@ from .cnn import (
|
|||||||
from .collegehumor import CollegeHumorIE
|
from .collegehumor import CollegeHumorIE
|
||||||
from .comedycentral import ComedyCentralIE, ComedyCentralShowsIE
|
from .comedycentral import ComedyCentralIE, ComedyCentralShowsIE
|
||||||
from .condenast import CondeNastIE
|
from .condenast import CondeNastIE
|
||||||
|
from .cracked import CrackedIE
|
||||||
from .criterion import CriterionIE
|
from .criterion import CriterionIE
|
||||||
from .crunchyroll import CrunchyrollIE
|
from .crunchyroll import CrunchyrollIE
|
||||||
from .cspan import CSpanIE
|
from .cspan import CSpanIE
|
||||||
@ -61,8 +65,11 @@ from .dailymotion import (
|
|||||||
DailymotionUserIE,
|
DailymotionUserIE,
|
||||||
)
|
)
|
||||||
from .daum import DaumIE
|
from .daum import DaumIE
|
||||||
|
from .dfb import DFBIE
|
||||||
from .dotsub import DotsubIE
|
from .dotsub import DotsubIE
|
||||||
from .dreisat import DreiSatIE
|
from .dreisat import DreiSatIE
|
||||||
|
from .drtv import DRTVIE
|
||||||
|
from .dump import DumpIE
|
||||||
from .defense import DefenseGouvFrIE
|
from .defense import DefenseGouvFrIE
|
||||||
from .discovery import DiscoveryIE
|
from .discovery import DiscoveryIE
|
||||||
from .divxstage import DivxStageIE
|
from .divxstage import DivxStageIE
|
||||||
@ -71,6 +78,10 @@ from .ebaumsworld import EbaumsWorldIE
|
|||||||
from .ehow import EHowIE
|
from .ehow import EHowIE
|
||||||
from .eighttracks import EightTracksIE
|
from .eighttracks import EightTracksIE
|
||||||
from .eitb import EitbIE
|
from .eitb import EitbIE
|
||||||
|
from .ellentv import (
|
||||||
|
EllenTVIE,
|
||||||
|
EllenTVClipsIE,
|
||||||
|
)
|
||||||
from .elpais import ElPaisIE
|
from .elpais import ElPaisIE
|
||||||
from .empflix import EmpflixIE
|
from .empflix import EmpflixIE
|
||||||
from .engadget import EngadgetIE
|
from .engadget import EngadgetIE
|
||||||
@ -81,6 +92,7 @@ from .extremetube import ExtremeTubeIE
|
|||||||
from .facebook import FacebookIE
|
from .facebook import FacebookIE
|
||||||
from .faz import FazIE
|
from .faz import FazIE
|
||||||
from .fc2 import FC2IE
|
from .fc2 import FC2IE
|
||||||
|
from .firedrive import FiredriveIE
|
||||||
from .firstpost import FirstpostIE
|
from .firstpost import FirstpostIE
|
||||||
from .firsttv import FirstTVIE
|
from .firsttv import FirstTVIE
|
||||||
from .fivemin import FiveMinIE
|
from .fivemin import FiveMinIE
|
||||||
@ -103,18 +115,23 @@ from .freesound import FreesoundIE
|
|||||||
from .freespeech import FreespeechIE
|
from .freespeech import FreespeechIE
|
||||||
from .funnyordie import FunnyOrDieIE
|
from .funnyordie import FunnyOrDieIE
|
||||||
from .gamekings import GamekingsIE
|
from .gamekings import GamekingsIE
|
||||||
|
from .gameone import GameOneIE
|
||||||
from .gamespot import GameSpotIE
|
from .gamespot import GameSpotIE
|
||||||
|
from .gamestar import GameStarIE
|
||||||
from .gametrailers import GametrailersIE
|
from .gametrailers import GametrailersIE
|
||||||
from .gdcvault import GDCVaultIE
|
from .gdcvault import GDCVaultIE
|
||||||
from .generic import GenericIE
|
from .generic import GenericIE
|
||||||
|
from .godtube import GodTubeIE
|
||||||
from .googleplus import GooglePlusIE
|
from .googleplus import GooglePlusIE
|
||||||
from .googlesearch import GoogleSearchIE
|
from .googlesearch import GoogleSearchIE
|
||||||
from .gorillavid import GorillaVidIE
|
from .gorillavid import GorillaVidIE
|
||||||
|
from .goshgay import GoshgayIE
|
||||||
from .hark import HarkIE
|
from .hark import HarkIE
|
||||||
from .helsinki import HelsinkiIE
|
from .helsinki import HelsinkiIE
|
||||||
from .hentaistigma import HentaiStigmaIE
|
from .hentaistigma import HentaiStigmaIE
|
||||||
from .hotnewhiphop import HotNewHipHopIE
|
from .hotnewhiphop import HotNewHipHopIE
|
||||||
from .howcast import HowcastIE
|
from .howcast import HowcastIE
|
||||||
|
from .howstuffworks import HowStuffWorksIE
|
||||||
from .huffpost import HuffPostIE
|
from .huffpost import HuffPostIE
|
||||||
from .hypem import HypemIE
|
from .hypem import HypemIE
|
||||||
from .iconosquare import IconosquareIE
|
from .iconosquare import IconosquareIE
|
||||||
@ -132,8 +149,10 @@ from .ivi import (
|
|||||||
IviIE,
|
IviIE,
|
||||||
IviCompilationIE
|
IviCompilationIE
|
||||||
)
|
)
|
||||||
|
from .izlesene import IzleseneIE
|
||||||
from .jadorecettepub import JadoreCettePubIE
|
from .jadorecettepub import JadoreCettePubIE
|
||||||
from .jeuxvideo import JeuxVideoIE
|
from .jeuxvideo import JeuxVideoIE
|
||||||
|
from .jove import JoveIE
|
||||||
from .jukebox import JukeboxIE
|
from .jukebox import JukeboxIE
|
||||||
from .justintv import JustinTVIE
|
from .justintv import JustinTVIE
|
||||||
from .jpopsukitv import JpopsukiIE
|
from .jpopsukitv import JpopsukiIE
|
||||||
@ -143,11 +162,16 @@ from .khanacademy import KhanAcademyIE
|
|||||||
from .kickstarter import KickStarterIE
|
from .kickstarter import KickStarterIE
|
||||||
from .keek import KeekIE
|
from .keek import KeekIE
|
||||||
from .kontrtube import KontrTubeIE
|
from .kontrtube import KontrTubeIE
|
||||||
|
from .krasview import KrasViewIE
|
||||||
from .ku6 import Ku6IE
|
from .ku6 import Ku6IE
|
||||||
from .la7 import LA7IE
|
from .la7 import LA7IE
|
||||||
from .lifenews import LifeNewsIE
|
from .lifenews import LifeNewsIE
|
||||||
from .liveleak import LiveLeakIE
|
from .liveleak import LiveLeakIE
|
||||||
from .livestream import LivestreamIE, LivestreamOriginalIE
|
from .livestream import (
|
||||||
|
LivestreamIE,
|
||||||
|
LivestreamOriginalIE,
|
||||||
|
LivestreamShortenerIE,
|
||||||
|
)
|
||||||
from .lynda import (
|
from .lynda import (
|
||||||
LyndaIE,
|
LyndaIE,
|
||||||
LyndaCourseIE
|
LyndaCourseIE
|
||||||
@ -160,12 +184,17 @@ from .mdr import MDRIE
|
|||||||
from .metacafe import MetacafeIE
|
from .metacafe import MetacafeIE
|
||||||
from .metacritic import MetacriticIE
|
from .metacritic import MetacriticIE
|
||||||
from .mit import TechTVMITIE, MITIE, OCWMITIE
|
from .mit import TechTVMITIE, MITIE, OCWMITIE
|
||||||
|
from .mitele import MiTeleIE
|
||||||
from .mixcloud import MixcloudIE
|
from .mixcloud import MixcloudIE
|
||||||
|
from .mlb import MLBIE
|
||||||
from .mpora import MporaIE
|
from .mpora import MporaIE
|
||||||
from .mofosex import MofosexIE
|
from .mofosex import MofosexIE
|
||||||
|
from .mojvideo import MojvideoIE
|
||||||
from .mooshare import MooshareIE
|
from .mooshare import MooshareIE
|
||||||
from .morningstar import MorningstarIE
|
from .morningstar import MorningstarIE
|
||||||
|
from .motherless import MotherlessIE
|
||||||
from .motorsport import MotorsportIE
|
from .motorsport import MotorsportIE
|
||||||
|
from .movieclips import MovieClipsIE
|
||||||
from .moviezine import MoviezineIE
|
from .moviezine import MoviezineIE
|
||||||
from .movshare import MovShareIE
|
from .movshare import MovShareIE
|
||||||
from .mtv import (
|
from .mtv import (
|
||||||
@ -197,6 +226,7 @@ from .normalboots import NormalbootsIE
|
|||||||
from .novamov import NovaMovIE
|
from .novamov import NovaMovIE
|
||||||
from .nowness import NownessIE
|
from .nowness import NownessIE
|
||||||
from .nowvideo import NowVideoIE
|
from .nowvideo import NowVideoIE
|
||||||
|
from .npo import NPOIE
|
||||||
from .nrk import (
|
from .nrk import (
|
||||||
NRKIE,
|
NRKIE,
|
||||||
NRKTVIE,
|
NRKTVIE,
|
||||||
@ -204,12 +234,17 @@ from .nrk import (
|
|||||||
from .ntv import NTVIE
|
from .ntv import NTVIE
|
||||||
from .nytimes import NYTimesIE
|
from .nytimes import NYTimesIE
|
||||||
from .nuvid import NuvidIE
|
from .nuvid import NuvidIE
|
||||||
from .oe1 import OE1IE
|
|
||||||
from .ooyala import OoyalaIE
|
from .ooyala import OoyalaIE
|
||||||
from .orf import ORFIE
|
from .orf import (
|
||||||
|
ORFTVthekIE,
|
||||||
|
ORFOE1IE,
|
||||||
|
ORFFM4IE,
|
||||||
|
)
|
||||||
from .parliamentliveuk import ParliamentLiveUKIE
|
from .parliamentliveuk import ParliamentLiveUKIE
|
||||||
|
from .patreon import PatreonIE
|
||||||
from .pbs import PBSIE
|
from .pbs import PBSIE
|
||||||
from .photobucket import PhotobucketIE
|
from .photobucket import PhotobucketIE
|
||||||
|
from .playfm import PlayFMIE
|
||||||
from .playvid import PlayvidIE
|
from .playvid import PlayvidIE
|
||||||
from .podomatic import PodomaticIE
|
from .podomatic import PodomaticIE
|
||||||
from .pornhd import PornHdIE
|
from .pornhd import PornHdIE
|
||||||
@ -221,14 +256,17 @@ from .radiofrance import RadioFranceIE
|
|||||||
from .rai import RaiIE
|
from .rai import RaiIE
|
||||||
from .rbmaradio import RBMARadioIE
|
from .rbmaradio import RBMARadioIE
|
||||||
from .redtube import RedTubeIE
|
from .redtube import RedTubeIE
|
||||||
|
from .reverbnation import ReverbNationIE
|
||||||
from .ringtv import RingTVIE
|
from .ringtv import RingTVIE
|
||||||
from .ro220 import Ro220IE
|
from .ro220 import Ro220IE
|
||||||
from .rottentomatoes import RottenTomatoesIE
|
from .rottentomatoes import RottenTomatoesIE
|
||||||
from .roxwel import RoxwelIE
|
from .roxwel import RoxwelIE
|
||||||
from .rtbf import RTBFIE
|
from .rtbf import RTBFIE
|
||||||
|
from .rtlnl import RtlXlIE
|
||||||
from .rtlnow import RTLnowIE
|
from .rtlnow import RTLnowIE
|
||||||
from .rts import RTSIE
|
from .rts import RTSIE
|
||||||
from .rtve import RTVEALaCartaIE
|
from .rtve import RTVEALaCartaIE, RTVELiveIE
|
||||||
|
from .ruhd import RUHDIE
|
||||||
from .rutube import (
|
from .rutube import (
|
||||||
RutubeIE,
|
RutubeIE,
|
||||||
RutubeChannelIE,
|
RutubeChannelIE,
|
||||||
@ -236,9 +274,13 @@ from .rutube import (
|
|||||||
RutubePersonIE,
|
RutubePersonIE,
|
||||||
)
|
)
|
||||||
from .rutv import RUTVIE
|
from .rutv import RUTVIE
|
||||||
|
from .sapo import SapoIE
|
||||||
from .savefrom import SaveFromIE
|
from .savefrom import SaveFromIE
|
||||||
|
from .sbs import SBSIE
|
||||||
from .scivee import SciVeeIE
|
from .scivee import SciVeeIE
|
||||||
|
from .screencast import ScreencastIE
|
||||||
from .servingsys import ServingSysIE
|
from .servingsys import ServingSysIE
|
||||||
|
from .shared import SharedIE
|
||||||
from .sina import SinaIE
|
from .sina import SinaIE
|
||||||
from .slideshare import SlideshareIE
|
from .slideshare import SlideshareIE
|
||||||
from .slutload import SlutloadIE
|
from .slutload import SlutloadIE
|
||||||
@ -248,6 +290,8 @@ from .smotri import (
|
|||||||
SmotriUserIE,
|
SmotriUserIE,
|
||||||
SmotriBroadcastIE,
|
SmotriBroadcastIE,
|
||||||
)
|
)
|
||||||
|
from .snotr import SnotrIE
|
||||||
|
from .sockshare import SockshareIE
|
||||||
from .sohu import SohuIE
|
from .sohu import SohuIE
|
||||||
from .soundcloud import (
|
from .soundcloud import (
|
||||||
SoundcloudIE,
|
SoundcloudIE,
|
||||||
@ -255,8 +299,9 @@ from .soundcloud import (
|
|||||||
SoundcloudUserIE,
|
SoundcloudUserIE,
|
||||||
SoundcloudPlaylistIE
|
SoundcloudPlaylistIE
|
||||||
)
|
)
|
||||||
from .southparkstudios import (
|
from .soundgasm import SoundgasmIE
|
||||||
SouthParkStudiosIE,
|
from .southpark import (
|
||||||
|
SouthParkIE,
|
||||||
SouthparkDeIE,
|
SouthparkDeIE,
|
||||||
)
|
)
|
||||||
from .space import SpaceIE
|
from .space import SpaceIE
|
||||||
@ -274,12 +319,13 @@ from .sztvhu import SztvHuIE
|
|||||||
from .tagesschau import TagesschauIE
|
from .tagesschau import TagesschauIE
|
||||||
from .teachertube import (
|
from .teachertube import (
|
||||||
TeacherTubeIE,
|
TeacherTubeIE,
|
||||||
TeacherTubeClassroomIE,
|
TeacherTubeUserIE,
|
||||||
)
|
)
|
||||||
from .teachingchannel import TeachingChannelIE
|
from .teachingchannel import TeachingChannelIE
|
||||||
from .teamcoco import TeamcocoIE
|
from .teamcoco import TeamcocoIE
|
||||||
from .techtalks import TechTalksIE
|
from .techtalks import TechTalksIE
|
||||||
from .ted import TEDIE
|
from .ted import TEDIE
|
||||||
|
from .tenplay import TenPlayIE
|
||||||
from .testurl import TestURLIE
|
from .testurl import TestURLIE
|
||||||
from .tf1 import TF1IE
|
from .tf1 import TF1IE
|
||||||
from .theplatform import ThePlatformIE
|
from .theplatform import ThePlatformIE
|
||||||
@ -297,6 +343,8 @@ from .tumblr import TumblrIE
|
|||||||
from .tutv import TutvIE
|
from .tutv import TutvIE
|
||||||
from .tvigle import TvigleIE
|
from .tvigle import TvigleIE
|
||||||
from .tvp import TvpIE
|
from .tvp import TvpIE
|
||||||
|
from .tvplay import TVPlayIE
|
||||||
|
from .ubu import UbuIE
|
||||||
from .udemy import (
|
from .udemy import (
|
||||||
UdemyIE,
|
UdemyIE,
|
||||||
UdemyCourseIE
|
UdemyCourseIE
|
||||||
@ -318,6 +366,7 @@ from .videofyme import VideofyMeIE
|
|||||||
from .videopremium import VideoPremiumIE
|
from .videopremium import VideoPremiumIE
|
||||||
from .videott import VideoTtIE
|
from .videott import VideoTtIE
|
||||||
from .videoweed import VideoWeedIE
|
from .videoweed import VideoWeedIE
|
||||||
|
from .vidme import VidmeIE
|
||||||
from .vimeo import (
|
from .vimeo import (
|
||||||
VimeoIE,
|
VimeoIE,
|
||||||
VimeoChannelIE,
|
VimeoChannelIE,
|
||||||
@ -327,12 +376,14 @@ from .vimeo import (
|
|||||||
VimeoReviewIE,
|
VimeoReviewIE,
|
||||||
VimeoWatchLaterIE,
|
VimeoWatchLaterIE,
|
||||||
)
|
)
|
||||||
|
from .vimple import VimpleIE
|
||||||
from .vine import (
|
from .vine import (
|
||||||
VineIE,
|
VineIE,
|
||||||
VineUserIE,
|
VineUserIE,
|
||||||
)
|
)
|
||||||
from .viki import VikiIE
|
from .viki import VikiIE
|
||||||
from .vk import VKIE
|
from .vk import VKIE
|
||||||
|
from .vodlocker import VodlockerIE
|
||||||
from .vube import VubeIE
|
from .vube import VubeIE
|
||||||
from .vuclip import VuClipIE
|
from .vuclip import VuClipIE
|
||||||
from .vulture import VultureIE
|
from .vulture import VultureIE
|
||||||
@ -349,6 +400,7 @@ from .wistia import WistiaIE
|
|||||||
from .worldstarhiphop import WorldStarHipHopIE
|
from .worldstarhiphop import WorldStarHipHopIE
|
||||||
from .wrzuta import WrzutaIE
|
from .wrzuta import WrzutaIE
|
||||||
from .xbef import XBefIE
|
from .xbef import XBefIE
|
||||||
|
from .xboxclips import XboxClipsIE
|
||||||
from .xhamster import XHamsterIE
|
from .xhamster import XHamsterIE
|
||||||
from .xnxx import XNXXIE
|
from .xnxx import XNXXIE
|
||||||
from .xvideos import XVideosIE
|
from .xvideos import XVideosIE
|
||||||
@ -378,6 +430,7 @@ from .youtube import (
|
|||||||
YoutubeUserIE,
|
YoutubeUserIE,
|
||||||
YoutubeWatchLaterIE,
|
YoutubeWatchLaterIE,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .zdf import ZDFIE
|
from .zdf import ZDFIE
|
||||||
|
|
||||||
|
|
||||||
|
48
youtube_dl/extractor/abc.py
Normal file
48
youtube_dl/extractor/abc.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
|
class ABCIE(InfoExtractor):
|
||||||
|
IE_NAME = 'abc.net.au'
|
||||||
|
_VALID_URL = r'http://www\.abc\.net\.au/news/[^/]+/[^/]+/(?P<id>\d+)'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.abc.net.au/news/2014-07-25/bringing-asylum-seekers-to-australia-would-give/5624716',
|
||||||
|
'md5': 'dad6f8ad011a70d9ddf887ce6d5d0742',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '5624716',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Bringing asylum seekers to Australia would give them right to asylum claims: professor',
|
||||||
|
'description': 'md5:ba36fa5e27e5c9251fd929d339aea4af',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
urls_info_json = self._search_regex(
|
||||||
|
r'inlineVideoData\.push\((.*?)\);', webpage, 'video urls',
|
||||||
|
flags=re.DOTALL)
|
||||||
|
urls_info = json.loads(urls_info_json.replace('\'', '"'))
|
||||||
|
formats = [{
|
||||||
|
'url': url_info['url'],
|
||||||
|
'width': int(url_info['width']),
|
||||||
|
'height': int(url_info['height']),
|
||||||
|
'tbr': int(url_info['bitrate']),
|
||||||
|
'filesize': int(url_info['filesize']),
|
||||||
|
} for url_info in urls_info]
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': self._og_search_title(webpage),
|
||||||
|
'formats': formats,
|
||||||
|
'description': self._og_search_description(webpage),
|
||||||
|
'thumbnail': self._og_search_thumbnail(webpage),
|
||||||
|
}
|
139
youtube_dl/extractor/adultswim.py
Normal file
139
youtube_dl/extractor/adultswim.py
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
class AdultSwimIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://video\.adultswim\.com/(?P<path>.+?)(?:\.html)?(?:\?.*)?(?:#.*)?$'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://video.adultswim.com/rick-and-morty/close-rick-counters-of-the-rick-kind.html?x=y#title',
|
||||||
|
'playlist': [
|
||||||
|
{
|
||||||
|
'md5': '4da359ec73b58df4575cd01a610ba5dc',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '8a250ba1450996e901453d7f02ca02f5',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Rick and Morty Close Rick-Counters of the Rick Kind part 1',
|
||||||
|
'description': 'Rick has a run in with some old associates, resulting in a fallout with Morty. You got any chips, broh?',
|
||||||
|
'uploader': 'Rick and Morty',
|
||||||
|
'thumbnail': 'http://i.cdn.turner.com/asfix/repository/8a250ba13f865824013fc9db8b6b0400/thumbnail_267549017116827057.jpg'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'md5': 'ffbdf55af9331c509d95350bd0cc1819',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '8a250ba1450996e901453d7f4bd102f6',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Rick and Morty Close Rick-Counters of the Rick Kind part 2',
|
||||||
|
'description': 'Rick has a run in with some old associates, resulting in a fallout with Morty. You got any chips, broh?',
|
||||||
|
'uploader': 'Rick and Morty',
|
||||||
|
'thumbnail': 'http://i.cdn.turner.com/asfix/repository/8a250ba13f865824013fc9db8b6b0400/thumbnail_267549017116827057.jpg'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'md5': 'b92409635540304280b4b6c36bd14a0a',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '8a250ba1450996e901453d7fa73c02f7',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Rick and Morty Close Rick-Counters of the Rick Kind part 3',
|
||||||
|
'description': 'Rick has a run in with some old associates, resulting in a fallout with Morty. You got any chips, broh?',
|
||||||
|
'uploader': 'Rick and Morty',
|
||||||
|
'thumbnail': 'http://i.cdn.turner.com/asfix/repository/8a250ba13f865824013fc9db8b6b0400/thumbnail_267549017116827057.jpg'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'md5': 'e8818891d60e47b29cd89d7b0278156d',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '8a250ba1450996e901453d7fc8ba02f8',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Rick and Morty Close Rick-Counters of the Rick Kind part 4',
|
||||||
|
'description': 'Rick has a run in with some old associates, resulting in a fallout with Morty. You got any chips, broh?',
|
||||||
|
'uploader': 'Rick and Morty',
|
||||||
|
'thumbnail': 'http://i.cdn.turner.com/asfix/repository/8a250ba13f865824013fc9db8b6b0400/thumbnail_267549017116827057.jpg'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
_video_extensions = {
|
||||||
|
'3500': 'flv',
|
||||||
|
'640': 'mp4',
|
||||||
|
'150': 'mp4',
|
||||||
|
'ipad': 'm3u8',
|
||||||
|
'iphone': 'm3u8'
|
||||||
|
}
|
||||||
|
_video_dimensions = {
|
||||||
|
'3500': (1280, 720),
|
||||||
|
'640': (480, 270),
|
||||||
|
'150': (320, 180)
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_path = mobj.group('path')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_path)
|
||||||
|
episode_id = self._html_search_regex(r'<link rel="video_src" href="http://i\.adultswim\.com/adultswim/adultswimtv/tools/swf/viralplayer.swf\?id=([0-9a-f]+?)"\s*/?\s*>', webpage, 'episode_id')
|
||||||
|
title = self._og_search_title(webpage)
|
||||||
|
|
||||||
|
index_url = 'http://asfix.adultswim.com/asfix-svc/episodeSearch/getEpisodesByIDs?networkName=AS&ids=%s' % episode_id
|
||||||
|
idoc = self._download_xml(index_url, title, 'Downloading episode index', 'Unable to download episode index')
|
||||||
|
|
||||||
|
episode_el = idoc.find('.//episode')
|
||||||
|
show_title = episode_el.attrib.get('collectionTitle')
|
||||||
|
episode_title = episode_el.attrib.get('title')
|
||||||
|
thumbnail = episode_el.attrib.get('thumbnailUrl')
|
||||||
|
description = episode_el.find('./description').text.strip()
|
||||||
|
|
||||||
|
entries = []
|
||||||
|
segment_els = episode_el.findall('./segments/segment')
|
||||||
|
|
||||||
|
for part_num, segment_el in enumerate(segment_els):
|
||||||
|
segment_id = segment_el.attrib.get('id')
|
||||||
|
segment_title = '%s %s part %d' % (show_title, episode_title, part_num + 1)
|
||||||
|
thumbnail = segment_el.attrib.get('thumbnailUrl')
|
||||||
|
duration = segment_el.attrib.get('duration')
|
||||||
|
|
||||||
|
segment_url = 'http://asfix.adultswim.com/asfix-svc/episodeservices/getCvpPlaylist?networkName=AS&id=%s' % segment_id
|
||||||
|
idoc = self._download_xml(segment_url, segment_title, 'Downloading segment information', 'Unable to download segment information')
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
file_els = idoc.findall('.//files/file')
|
||||||
|
|
||||||
|
for file_el in file_els:
|
||||||
|
bitrate = file_el.attrib.get('bitrate')
|
||||||
|
type = file_el.attrib.get('type')
|
||||||
|
width, height = self._video_dimensions.get(bitrate, (None, None))
|
||||||
|
formats.append({
|
||||||
|
'format_id': '%s-%s' % (bitrate, type),
|
||||||
|
'url': file_el.text,
|
||||||
|
'ext': self._video_extensions.get(bitrate, 'mp4'),
|
||||||
|
# The bitrate may not be a number (for example: 'iphone')
|
||||||
|
'tbr': int(bitrate) if bitrate.isdigit() else None,
|
||||||
|
'height': height,
|
||||||
|
'width': width
|
||||||
|
})
|
||||||
|
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
entries.append({
|
||||||
|
'id': segment_id,
|
||||||
|
'title': segment_title,
|
||||||
|
'formats': formats,
|
||||||
|
'uploader': show_title,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'duration': duration,
|
||||||
|
'description': description
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
'_type': 'playlist',
|
||||||
|
'id': episode_id,
|
||||||
|
'display_id': video_path,
|
||||||
|
'entries': entries,
|
||||||
|
'title': '%s %s' % (show_title, episode_title),
|
||||||
|
'description': description,
|
||||||
|
'thumbnail': thumbnail
|
||||||
|
}
|
89
youtube_dl/extractor/allocine.py
Normal file
89
youtube_dl/extractor/allocine.py
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
compat_str,
|
||||||
|
qualities,
|
||||||
|
determine_ext,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AllocineIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?allocine\.fr/(?P<typ>article|video|film)/(fichearticle_gen_carticle=|player_gen_cmedia=|fichefilm_gen_cfilm=)(?P<id>[0-9]+)(?:\.html)?'
|
||||||
|
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://www.allocine.fr/article/fichearticle_gen_carticle=18635087.html',
|
||||||
|
'md5': '0c9fcf59a841f65635fa300ac43d8269',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '19546517',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Astérix - Le Domaine des Dieux Teaser VF',
|
||||||
|
'description': 'md5:4a754271d9c6f16c72629a8a993ee884',
|
||||||
|
'thumbnail': 're:http://.*\.jpg',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.allocine.fr/video/player_gen_cmedia=19540403&cfilm=222257.html',
|
||||||
|
'md5': 'd0cdce5d2b9522ce279fdfec07ff16e0',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '19540403',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Planes 2 Bande-annonce VF',
|
||||||
|
'description': 'md5:eeaffe7c2d634525e21159b93acf3b1e',
|
||||||
|
'thumbnail': 're:http://.*\.jpg',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.allocine.fr/film/fichefilm_gen_cfilm=181290.html',
|
||||||
|
'md5': '101250fb127ef9ca3d73186ff22a47ce',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '19544709',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Dragons 2 - Bande annonce finale VF',
|
||||||
|
'description': 'md5:71742e3a74b0d692c7fce0dd2017a4ac',
|
||||||
|
'thumbnail': 're:http://.*\.jpg',
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
typ = mobj.group('typ')
|
||||||
|
display_id = mobj.group('id')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
|
||||||
|
if typ == 'film':
|
||||||
|
video_id = self._search_regex(r'href="/video/player_gen_cmedia=([0-9]+).+"', webpage, 'video id')
|
||||||
|
else:
|
||||||
|
player = self._search_regex(r'data-player=\'([^\']+)\'>', webpage, 'data player')
|
||||||
|
|
||||||
|
player_data = json.loads(player)
|
||||||
|
video_id = compat_str(player_data['refMedia'])
|
||||||
|
|
||||||
|
xml = self._download_xml('http://www.allocine.fr/ws/AcVisiondataV4.ashx?media=%s' % video_id, display_id)
|
||||||
|
|
||||||
|
video = xml.find('.//AcVisionVideo').attrib
|
||||||
|
quality = qualities(['ld', 'md', 'hd'])
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
for k, v in video.items():
|
||||||
|
if re.match(r'.+_path', k):
|
||||||
|
format_id = k.split('_')[0]
|
||||||
|
formats.append({
|
||||||
|
'format_id': format_id,
|
||||||
|
'quality': quality(format_id),
|
||||||
|
'url': v,
|
||||||
|
'ext': determine_ext(v),
|
||||||
|
})
|
||||||
|
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': video['videoTitle'],
|
||||||
|
'thumbnail': self._og_search_thumbnail(webpage),
|
||||||
|
'formats': formats,
|
||||||
|
'description': self._og_search_description(webpage),
|
||||||
|
}
|
@ -1,22 +1,24 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
class AnitubeIE(InfoExtractor):
|
class AnitubeIE(InfoExtractor):
|
||||||
IE_NAME = u'anitube.se'
|
IE_NAME = 'anitube.se'
|
||||||
_VALID_URL = r'https?://(?:www\.)?anitube\.se/video/(?P<id>\d+)'
|
_VALID_URL = r'https?://(?:www\.)?anitube\.se/video/(?P<id>\d+)'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
u'url': u'http://www.anitube.se/video/36621',
|
'url': 'http://www.anitube.se/video/36621',
|
||||||
u'md5': u'59d0eeae28ea0bc8c05e7af429998d43',
|
'md5': '59d0eeae28ea0bc8c05e7af429998d43',
|
||||||
u'file': u'36621.mp4',
|
'info_dict': {
|
||||||
u'info_dict': {
|
'id': '36621',
|
||||||
u'id': u'36621',
|
'ext': 'mp4',
|
||||||
u'ext': u'mp4',
|
'title': 'Recorder to Randoseru 01',
|
||||||
u'title': u'Recorder to Randoseru 01',
|
'duration': 180.19,
|
||||||
},
|
},
|
||||||
u'skip': u'Blocked in the US',
|
'skip': 'Blocked in the US',
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
@ -24,13 +26,15 @@ class AnitubeIE(InfoExtractor):
|
|||||||
video_id = mobj.group('id')
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
key = self._html_search_regex(r'http://www\.anitube\.se/embed/([A-Za-z0-9_-]*)',
|
key = self._html_search_regex(
|
||||||
webpage, u'key')
|
r'http://www\.anitube\.se/embed/([A-Za-z0-9_-]*)', webpage, 'key')
|
||||||
|
|
||||||
config_xml = self._download_xml('http://www.anitube.se/nuevo/econfig.php?key=%s' % key,
|
config_xml = self._download_xml(
|
||||||
key)
|
'http://www.anitube.se/nuevo/econfig.php?key=%s' % key, key)
|
||||||
|
|
||||||
video_title = config_xml.find('title').text
|
video_title = config_xml.find('title').text
|
||||||
|
thumbnail = config_xml.find('image').text
|
||||||
|
duration = float(config_xml.find('duration').text)
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
video_url = config_xml.find('file')
|
video_url = config_xml.find('file')
|
||||||
@ -49,5 +53,7 @@ class AnitubeIE(InfoExtractor):
|
|||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': video_title,
|
'title': video_title,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'duration': duration,
|
||||||
'formats': formats
|
'formats': formats
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
#coding: utf-8
|
#coding: utf-8
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
@ -13,13 +15,14 @@ class AparatIE(InfoExtractor):
|
|||||||
_VALID_URL = r'^https?://(?:www\.)?aparat\.com/(?:v/|video/video/embed/videohash/)(?P<id>[a-zA-Z0-9]+)'
|
_VALID_URL = r'^https?://(?:www\.)?aparat\.com/(?:v/|video/video/embed/videohash/)(?P<id>[a-zA-Z0-9]+)'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
u'url': u'http://www.aparat.com/v/wP8On',
|
'url': 'http://www.aparat.com/v/wP8On',
|
||||||
u'file': u'wP8On.mp4',
|
'md5': '6714e0af7e0d875c5a39c4dc4ab46ad1',
|
||||||
u'md5': u'6714e0af7e0d875c5a39c4dc4ab46ad1',
|
'info_dict': {
|
||||||
u'info_dict': {
|
'id': 'wP8On',
|
||||||
u"title": u"تیم گلکسی 11 - زومیت",
|
'ext': 'mp4',
|
||||||
|
'title': 'تیم گلکسی 11 - زومیت',
|
||||||
},
|
},
|
||||||
#u'skip': u'Extremely unreliable',
|
# 'skip': 'Extremely unreliable',
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
@ -29,8 +32,8 @@ class AparatIE(InfoExtractor):
|
|||||||
# Note: There is an easier-to-parse configuration at
|
# Note: There is an easier-to-parse configuration at
|
||||||
# http://www.aparat.com/video/video/config/videohash/%video_id
|
# http://www.aparat.com/video/video/config/videohash/%video_id
|
||||||
# but the URL in there does not work
|
# but the URL in there does not work
|
||||||
embed_url = (u'http://www.aparat.com/video/video/embed/videohash/' +
|
embed_url = ('http://www.aparat.com/video/video/embed/videohash/' +
|
||||||
video_id + u'/vt/frame')
|
video_id + '/vt/frame')
|
||||||
webpage = self._download_webpage(embed_url, video_id)
|
webpage = self._download_webpage(embed_url, video_id)
|
||||||
|
|
||||||
video_urls = re.findall(r'fileList\[[0-9]+\]\s*=\s*"([^"]+)"', webpage)
|
video_urls = re.findall(r'fileList\[[0-9]+\]\s*=\s*"([^"]+)"', webpage)
|
||||||
|
@ -6,6 +6,7 @@ import json
|
|||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
compat_urlparse,
|
compat_urlparse,
|
||||||
|
int_or_none,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -110,8 +111,8 @@ class AppleTrailersIE(InfoExtractor):
|
|||||||
formats.append({
|
formats.append({
|
||||||
'url': format_url,
|
'url': format_url,
|
||||||
'format': format['type'],
|
'format': format['type'],
|
||||||
'width': format['width'],
|
'width': int_or_none(format['width']),
|
||||||
'height': int(format['height']),
|
'height': int_or_none(format['height']),
|
||||||
})
|
})
|
||||||
|
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
@ -7,23 +7,34 @@ from .common import InfoExtractor
|
|||||||
from ..utils import (
|
from ..utils import (
|
||||||
determine_ext,
|
determine_ext,
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
|
qualities,
|
||||||
|
compat_urllib_parse_urlparse,
|
||||||
|
compat_urllib_parse,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ARDIE(InfoExtractor):
|
class ARDIE(InfoExtractor):
|
||||||
_VALID_URL = r'^https?://(?:(?:www\.)?ardmediathek\.de|mediathek\.daserste\.de)/(?:.*/)(?P<video_id>[^/\?]+)(?:\?.*)?'
|
_VALID_URL = r'^https?://(?:(?:www\.)?ardmediathek\.de|mediathek\.daserste\.de)/(?:.*/)(?P<video_id>[0-9]+|[^0-9][^/\?]+)[^/\?]*(?:\?.*)?'
|
||||||
|
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
'url': 'http://www.ardmediathek.de/das-erste/guenther-jauch/edward-snowden-im-interview-held-oder-verraeter?documentId=19288786',
|
'url': 'http://mediathek.daserste.de/sendungen_a-z/328454_anne-will/22429276_vertrauen-ist-gut-spionieren-ist-besser-geht',
|
||||||
'file': '19288786.mp4',
|
'file': '22429276.mp4',
|
||||||
'md5': '515bf47ce209fb3f5a61b7aad364634c',
|
'md5': '469751912f1de0816a9fc9df8336476c',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'title': 'Edward Snowden im Interview - Held oder Verräter?',
|
'title': 'Vertrauen ist gut, Spionieren ist besser - Geht so deutsch-amerikanische Freundschaft?',
|
||||||
'description': 'Edward Snowden hat alles aufs Spiel gesetzt, um die weltweite \xdcberwachung durch die Geheimdienste zu enttarnen. Nun stellt sich der ehemalige NSA-Mitarbeiter erstmals weltweit in einem TV-Interview den Fragen eines NDR-Journalisten. Die Sendung vom Sonntagabend.',
|
'description': 'Das Erste Mediathek [ARD]: Vertrauen ist gut, Spionieren ist besser - Geht so deutsch-amerikanische Freundschaft?, Anne Will, Über die Spionage-Affäre diskutieren Clemens Binninger, Katrin Göring-Eckardt, Georg Mascolo, Andrew B. Denison und Constanze Kurz.. Das Video zur Sendung Anne Will am Mittwoch, 16.07.2014',
|
||||||
'thumbnail': 'http://www.ardmediathek.de/ard/servlet/contentblob/19/28/87/90/19288790/bild/2250037',
|
|
||||||
},
|
},
|
||||||
'skip': 'Blocked outside of Germany',
|
'skip': 'Blocked outside of Germany',
|
||||||
}
|
}, {
|
||||||
|
'url': 'http://www.ardmediathek.de/tv/Tatort/Das-Wunder-von-Wolbeck-Video-tgl-ab-20/Das-Erste/Video?documentId=22490580&bcastId=602916',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '22490580',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Das Wunder von Wolbeck (Video tgl. ab 20 Uhr)',
|
||||||
|
'description': 'Auf einem restaurierten Hof bei Wolbeck wird der Heilpraktiker Raffael Lembeck eines morgens von seiner Frau Stella tot aufgefunden. Das Opfer war offensichtlich in seiner Praxis zu Fall gekommen und ist dann verblutet, erklärt Prof. Boerne am Tatort.',
|
||||||
|
},
|
||||||
|
'skip': 'Blocked outside of Germany',
|
||||||
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
# determine video id from url
|
# determine video id from url
|
||||||
@ -35,48 +46,78 @@ class ARDIE(InfoExtractor):
|
|||||||
else:
|
else:
|
||||||
video_id = m.group('video_id')
|
video_id = m.group('video_id')
|
||||||
|
|
||||||
|
urlp = compat_urllib_parse_urlparse(url)
|
||||||
|
url = urlp._replace(path=compat_urllib_parse.quote(urlp.path.encode('utf-8'))).geturl()
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
if '>Der gewünschte Beitrag ist nicht mehr verfügbar.<' in webpage:
|
||||||
|
raise ExtractorError('Video %s is no longer available' % video_id, expected=True)
|
||||||
|
|
||||||
title = self._html_search_regex(
|
title = self._html_search_regex(
|
||||||
[r'<h1(?:\s+class="boxTopHeadline")?>(.*?)</h1>',
|
[r'<h1(?:\s+class="boxTopHeadline")?>(.*?)</h1>',
|
||||||
r'<meta name="dcterms.title" content="(.*?)"/>',
|
r'<meta name="dcterms.title" content="(.*?)"/>',
|
||||||
r'<h4 class="headline">(.*?)</h4>'],
|
r'<h4 class="headline">(.*?)</h4>'],
|
||||||
webpage, 'title')
|
webpage, 'title')
|
||||||
description = self._html_search_meta(
|
description = self._html_search_meta(
|
||||||
'dcterms.abstract', webpage, 'description')
|
'dcterms.abstract', webpage, 'description', default=None)
|
||||||
thumbnail = self._og_search_thumbnail(webpage)
|
if description is None:
|
||||||
|
description = self._html_search_meta(
|
||||||
|
'description', webpage, 'meta description')
|
||||||
|
|
||||||
|
# Thumbnail is sometimes not present.
|
||||||
|
# It is in the mobile version, but that seems to use a different URL
|
||||||
|
# structure altogether.
|
||||||
|
thumbnail = self._og_search_thumbnail(webpage, default=None)
|
||||||
|
|
||||||
media_info = self._download_json(
|
media_streams = re.findall(r'''(?x)
|
||||||
'http://www.ardmediathek.de/play/media/%s' % video_id, video_id)
|
mediaCollection\.addMediaStream\([0-9]+,\s*[0-9]+,\s*"[^"]*",\s*
|
||||||
# The second element of the _mediaArray contains the standard http urls
|
"([^"]+)"''', webpage)
|
||||||
streams = media_info['_mediaArray'][1]['_mediaStreamArray']
|
|
||||||
if not streams:
|
|
||||||
if '"fsk"' in webpage:
|
|
||||||
raise ExtractorError('This video is only available after 20:00')
|
|
||||||
|
|
||||||
formats = []
|
if media_streams:
|
||||||
|
QUALITIES = qualities(['lo', 'hi', 'hq'])
|
||||||
|
formats = []
|
||||||
|
for furl in set(media_streams):
|
||||||
|
if furl.endswith('.f4m'):
|
||||||
|
fid = 'f4m'
|
||||||
|
else:
|
||||||
|
fid_m = re.match(r'.*\.([^.]+)\.[^.]+$', furl)
|
||||||
|
fid = fid_m.group(1) if fid_m else None
|
||||||
|
formats.append({
|
||||||
|
'quality': QUALITIES(fid),
|
||||||
|
'format_id': fid,
|
||||||
|
'url': furl,
|
||||||
|
})
|
||||||
|
else: # request JSON file
|
||||||
|
media_info = self._download_json(
|
||||||
|
'http://www.ardmediathek.de/play/media/%s' % video_id, video_id)
|
||||||
|
# The second element of the _mediaArray contains the standard http urls
|
||||||
|
streams = media_info['_mediaArray'][1]['_mediaStreamArray']
|
||||||
|
if not streams:
|
||||||
|
if '"fsk"' in webpage:
|
||||||
|
raise ExtractorError('This video is only available after 20:00')
|
||||||
|
|
||||||
for s in streams:
|
formats = []
|
||||||
if type(s['_stream']) == list:
|
for s in streams:
|
||||||
for index, url in enumerate(s['_stream'][::-1]):
|
if type(s['_stream']) == list:
|
||||||
quality = s['_quality'] + index
|
for index, url in enumerate(s['_stream'][::-1]):
|
||||||
formats.append({
|
quality = s['_quality'] + index
|
||||||
'quality': quality,
|
formats.append({
|
||||||
'url': url,
|
'quality': quality,
|
||||||
'format_id': '%s-%s' % (determine_ext(url), quality)
|
'url': url,
|
||||||
|
'format_id': '%s-%s' % (determine_ext(url), quality)
|
||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
|
|
||||||
format = {
|
format = {
|
||||||
'quality': s['_quality'],
|
'quality': s['_quality'],
|
||||||
'url': s['_stream'],
|
'url': s['_stream'],
|
||||||
}
|
}
|
||||||
|
|
||||||
format['format_id'] = '%s-%s' % (
|
format['format_id'] = '%s-%s' % (
|
||||||
determine_ext(format['url']), format['quality'])
|
determine_ext(format['url']), format['quality'])
|
||||||
|
|
||||||
formats.append(format)
|
formats.append(format)
|
||||||
|
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
@ -39,7 +39,10 @@ class ArteTvIE(InfoExtractor):
|
|||||||
|
|
||||||
formats = [{
|
formats = [{
|
||||||
'forma_id': q.attrib['quality'],
|
'forma_id': q.attrib['quality'],
|
||||||
'url': q.text,
|
# The playpath starts at 'mp4:', if we don't manually
|
||||||
|
# split the url, rtmpdump will incorrectly parse them
|
||||||
|
'url': q.text.split('mp4:', 1)[0],
|
||||||
|
'play_path': 'mp4:' + q.text.split('mp4:', 1)[1],
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'quality': 2 if q.attrib['quality'] == 'hd' else 1,
|
'quality': 2 if q.attrib['quality'] == 'hd' else 1,
|
||||||
} for q in config.findall('./urls/url')]
|
} for q in config.findall('./urls/url')]
|
||||||
@ -106,15 +109,19 @@ class ArteTVPlus7IE(InfoExtractor):
|
|||||||
regexes = [r'VO?%s' % l, r'VO?.-ST%s' % l]
|
regexes = [r'VO?%s' % l, r'VO?.-ST%s' % l]
|
||||||
return any(re.match(r, f['versionCode']) for r in regexes)
|
return any(re.match(r, f['versionCode']) for r in regexes)
|
||||||
# Some formats may not be in the same language as the url
|
# Some formats may not be in the same language as the url
|
||||||
|
# TODO: Might want not to drop videos that does not match requested language
|
||||||
|
# but to process those formats with lower precedence
|
||||||
formats = filter(_match_lang, all_formats)
|
formats = filter(_match_lang, all_formats)
|
||||||
formats = list(formats) # in python3 filter returns an iterator
|
formats = list(formats) # in python3 filter returns an iterator
|
||||||
if not formats:
|
if not formats:
|
||||||
# Some videos are only available in the 'Originalversion'
|
# Some videos are only available in the 'Originalversion'
|
||||||
# they aren't tagged as being in French or German
|
# they aren't tagged as being in French or German
|
||||||
if all(f['versionCode'] == 'VO' for f in all_formats):
|
# Sometimes there are neither videos of requested lang code
|
||||||
formats = all_formats
|
# nor original version videos available
|
||||||
else:
|
# For such cases we just take all_formats as is
|
||||||
raise ExtractorError(u'The formats list is empty')
|
formats = all_formats
|
||||||
|
if not formats:
|
||||||
|
raise ExtractorError('The formats list is empty')
|
||||||
|
|
||||||
if re.match(r'[A-Z]Q', formats[0]['quality']) is not None:
|
if re.match(r'[A-Z]Q', formats[0]['quality']) is not None:
|
||||||
def sort_key(f):
|
def sort_key(f):
|
||||||
@ -189,9 +196,10 @@ class ArteTVFutureIE(ArteTVPlus7IE):
|
|||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://future.arte.tv/fr/sujet/info-sciences#article-anchor-7081',
|
'url': 'http://future.arte.tv/fr/sujet/info-sciences#article-anchor-7081',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '050940-003',
|
'id': '5201',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Les champignons au secours de la planète',
|
'title': 'Les champignons au secours de la planète',
|
||||||
|
'upload_date': '20131101',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ class BlinkxIE(InfoExtractor):
|
|||||||
'height': int(m['h']),
|
'height': int(m['h']),
|
||||||
})
|
})
|
||||||
elif m['type'] == 'original':
|
elif m['type'] == 'original':
|
||||||
duration = m['d']
|
duration = float(m['d'])
|
||||||
elif m['type'] == 'youtube':
|
elif m['type'] == 'youtube':
|
||||||
yt_id = m['link']
|
yt_id = m['link']
|
||||||
self.to_screen('Youtube video detected: %s' % yt_id)
|
self.to_screen('Youtube video detected: %s' % yt_id)
|
||||||
|
@ -15,7 +15,7 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class BlipTVIE(SubtitlesInfoExtractor):
|
class BlipTVIE(SubtitlesInfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:\w+\.)?blip\.tv/(?:(?:.+-|rss/flash/)(?P<id>\d+)|((?:play/|api\.swf#)(?P<lookup_id>[\da-zA-Z]+)))'
|
_VALID_URL = r'https?://(?:\w+\.)?blip\.tv/(?:(?:.+-|rss/flash/)(?P<id>\d+)|((?:play/|api\.swf#)(?P<lookup_id>[\da-zA-Z+]+)))'
|
||||||
|
|
||||||
_TESTS = [
|
_TESTS = [
|
||||||
{
|
{
|
||||||
|
@ -10,7 +10,7 @@ class BloombergIE(InfoExtractor):
|
|||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.bloomberg.com/video/shah-s-presentation-on-foreign-exchange-strategies-qurhIVlJSB6hzkVi229d8g.html',
|
'url': 'http://www.bloomberg.com/video/shah-s-presentation-on-foreign-exchange-strategies-qurhIVlJSB6hzkVi229d8g.html',
|
||||||
'md5': '7bf08858ff7c203c870e8a6190e221e5',
|
# The md5 checksum changes
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'qurhIVlJSB6hzkVi229d8g',
|
'id': 'qurhIVlJSB6hzkVi229d8g',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
@ -31,8 +31,7 @@ class BloombergIE(InfoExtractor):
|
|||||||
return {
|
return {
|
||||||
'id': name.split('-')[-1],
|
'id': name.split('-')[-1],
|
||||||
'title': title,
|
'title': title,
|
||||||
'url': f4m_url,
|
'formats': self._extract_f4m_formats(f4m_url, name),
|
||||||
'ext': 'flv',
|
|
||||||
'description': self._og_search_description(webpage),
|
'description': self._og_search_description(webpage),
|
||||||
'thumbnail': self._og_search_thumbnail(webpage),
|
'thumbnail': self._og_search_thumbnail(webpage),
|
||||||
}
|
}
|
||||||
|
@ -7,12 +7,13 @@ from .common import InfoExtractor
|
|||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
|
parse_duration,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class BRIE(InfoExtractor):
|
class BRIE(InfoExtractor):
|
||||||
IE_DESC = 'Bayerischer Rundfunk Mediathek'
|
IE_DESC = 'Bayerischer Rundfunk Mediathek'
|
||||||
_VALID_URL = r'https?://(?:www\.)?br\.de/(?:[a-z0-9\-]+/)+(?P<id>[a-z0-9\-]+)\.html'
|
_VALID_URL = r'https?://(?:www\.)?br\.de/(?:[a-z0-9\-_]+/)+(?P<id>[a-z0-9\-_]+)\.html'
|
||||||
_BASE_URL = 'http://www.br.de'
|
_BASE_URL = 'http://www.br.de'
|
||||||
|
|
||||||
_TESTS = [
|
_TESTS = [
|
||||||
@ -22,8 +23,9 @@ class BRIE(InfoExtractor):
|
|||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '25e279aa-1ffd-40fd-9955-5325bd48a53a',
|
'id': '25e279aa-1ffd-40fd-9955-5325bd48a53a',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Am 1. und 2. August in Oberammergau',
|
'title': 'Wenn das Traditions-Theater wackelt',
|
||||||
'description': 'md5:dfd224e5aa6819bc1fcbb7826a932021',
|
'description': 'Heimatsound-Festival 2014: Wenn das Traditions-Theater wackelt',
|
||||||
|
'duration': 34,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -34,6 +36,7 @@ class BRIE(InfoExtractor):
|
|||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Über den Pass',
|
'title': 'Über den Pass',
|
||||||
'description': 'Die Eroberung der Alpen: Über den Pass',
|
'description': 'Die Eroberung der Alpen: Über den Pass',
|
||||||
|
'duration': 2588,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -44,6 +47,7 @@ class BRIE(InfoExtractor):
|
|||||||
'ext': 'aac',
|
'ext': 'aac',
|
||||||
'title': '"Keine neuen Schulden im nächsten Jahr"',
|
'title': '"Keine neuen Schulden im nächsten Jahr"',
|
||||||
'description': 'Haushaltsentwurf: "Keine neuen Schulden im nächsten Jahr"',
|
'description': 'Haushaltsentwurf: "Keine neuen Schulden im nächsten Jahr"',
|
||||||
|
'duration': 64,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -54,6 +58,7 @@ class BRIE(InfoExtractor):
|
|||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Umweltbewusster Häuslebauer',
|
'title': 'Umweltbewusster Häuslebauer',
|
||||||
'description': 'Uwe Erdelt: Umweltbewusster Häuslebauer',
|
'description': 'Uwe Erdelt: Umweltbewusster Häuslebauer',
|
||||||
|
'duration': 116,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -64,6 +69,7 @@ class BRIE(InfoExtractor):
|
|||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Folge 1 - Metaphysik',
|
'title': 'Folge 1 - Metaphysik',
|
||||||
'description': 'Kant für Anfänger: Folge 1 - Metaphysik',
|
'description': 'Kant für Anfänger: Folge 1 - Metaphysik',
|
||||||
|
'duration': 893,
|
||||||
'uploader': 'Eva Maria Steimle',
|
'uploader': 'Eva Maria Steimle',
|
||||||
'upload_date': '20140117',
|
'upload_date': '20140117',
|
||||||
}
|
}
|
||||||
@ -84,6 +90,7 @@ class BRIE(InfoExtractor):
|
|||||||
media = {
|
media = {
|
||||||
'id': xml_media.get('externalId'),
|
'id': xml_media.get('externalId'),
|
||||||
'title': xml_media.find('title').text,
|
'title': xml_media.find('title').text,
|
||||||
|
'duration': parse_duration(xml_media.find('duration').text),
|
||||||
'formats': self._extract_formats(xml_media.find('assets')),
|
'formats': self._extract_formats(xml_media.find('assets')),
|
||||||
'thumbnails': self._extract_thumbnails(xml_media.find('teaserImage/variants')),
|
'thumbnails': self._extract_thumbnails(xml_media.find('teaserImage/variants')),
|
||||||
'description': ' '.join(xml_media.find('shareTitle').text.splitlines()),
|
'description': ' '.join(xml_media.find('shareTitle').text.splitlines()),
|
||||||
|
@ -1,24 +1,42 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
class CBSIE(InfoExtractor):
|
class CBSIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?cbs\.com/shows/[^/]+/video/(?P<id>[^/]+)/.*'
|
_VALID_URL = r'https?://(?:www\.)?cbs\.com/shows/[^/]+/(?:video|artist)/(?P<id>[^/]+)/.*'
|
||||||
|
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
u'url': u'http://www.cbs.com/shows/garth-brooks/video/_u7W953k6la293J7EPTd9oHkSPs6Xn6_/connect-chat-feat-garth-brooks/',
|
'url': 'http://www.cbs.com/shows/garth-brooks/video/_u7W953k6la293J7EPTd9oHkSPs6Xn6_/connect-chat-feat-garth-brooks/',
|
||||||
u'file': u'4JUVEwq3wUT7.flv',
|
'info_dict': {
|
||||||
u'info_dict': {
|
'id': '4JUVEwq3wUT7',
|
||||||
u'title': u'Connect Chat feat. Garth Brooks',
|
'ext': 'flv',
|
||||||
u'description': u'Connect with country music singer Garth Brooks, as he chats with fans on Wednesday November 27, 2013. Be sure to tune in to Garth Brooks: Live from Las Vegas, Friday November 29, at 9/8c on CBS!',
|
'title': 'Connect Chat feat. Garth Brooks',
|
||||||
u'duration': 1495,
|
'description': 'Connect with country music singer Garth Brooks, as he chats with fans on Wednesday November 27, 2013. Be sure to tune in to Garth Brooks: Live from Las Vegas, Friday November 29, at 9/8c on CBS!',
|
||||||
|
'duration': 1495,
|
||||||
},
|
},
|
||||||
u'params': {
|
'params': {
|
||||||
# rtmp download
|
# rtmp download
|
||||||
u'skip_download': True,
|
'skip_download': True,
|
||||||
},
|
},
|
||||||
}
|
'_skip': 'Blocked outside the US',
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.cbs.com/shows/liveonletterman/artist/221752/st-vincent/',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'P9gjWjelt6iP',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Live on Letterman - St. Vincent',
|
||||||
|
'description': 'Live On Letterman: St. Vincent in concert from New York\'s Ed Sullivan Theater on Tuesday, July 16, 2014.',
|
||||||
|
'duration': 3221,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# rtmp download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
'_skip': 'Blocked outside the US',
|
||||||
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
@ -26,5 +44,5 @@ class CBSIE(InfoExtractor):
|
|||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
real_id = self._search_regex(
|
real_id = self._search_regex(
|
||||||
r"video\.settings\.pid\s*=\s*'([^']+)';",
|
r"video\.settings\.pid\s*=\s*'([^']+)';",
|
||||||
webpage, u'real video ID')
|
webpage, 'real video ID')
|
||||||
return self.url_result(u'theplatform:%s' % real_id)
|
return self.url_result(u'theplatform:%s' % real_id)
|
||||||
|
@ -42,7 +42,7 @@ class ChilloutzoneIE(InfoExtractor):
|
|||||||
'id': '85523671',
|
'id': '85523671',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'The Sunday Times - Icons',
|
'title': 'The Sunday Times - Icons',
|
||||||
'description': 'md5:3e1c0dc6047498d6728dcdaad0891762',
|
'description': 'md5:a5f7ff82e2f7a9ed77473fe666954e84',
|
||||||
'uploader': 'Us',
|
'uploader': 'Us',
|
||||||
'uploader_id': 'usfilms',
|
'uploader_id': 'usfilms',
|
||||||
'upload_date': '20140131'
|
'upload_date': '20140131'
|
||||||
|
@ -43,7 +43,11 @@ class CNETIE(InfoExtractor):
|
|||||||
raise ExtractorError('Cannot find video data')
|
raise ExtractorError('Cannot find video data')
|
||||||
|
|
||||||
video_id = vdata['id']
|
video_id = vdata['id']
|
||||||
title = vdata['headline']
|
title = vdata.get('headline')
|
||||||
|
if title is None:
|
||||||
|
title = vdata.get('title')
|
||||||
|
if title is None:
|
||||||
|
raise ExtractorError('Cannot find title!')
|
||||||
description = vdata.get('dek')
|
description = vdata.get('dek')
|
||||||
thumbnail = vdata.get('image', {}).get('path')
|
thumbnail = vdata.get('image', {}).get('path')
|
||||||
author = vdata.get('author')
|
author = vdata.get('author')
|
||||||
|
@ -14,13 +14,13 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class ComedyCentralIE(MTVServicesInfoExtractor):
|
class ComedyCentralIE(MTVServicesInfoExtractor):
|
||||||
_VALID_URL = r'''(?x)https?://(?:www\.)?(comedycentral|cc)\.com/
|
_VALID_URL = r'''(?x)https?://(?:www\.)?cc\.com/
|
||||||
(video-clips|episodes|cc-studios|video-collections)
|
(video-clips|episodes|cc-studios|video-collections|full-episodes)
|
||||||
/(?P<title>.*)'''
|
/(?P<title>.*)'''
|
||||||
_FEED_URL = 'http://comedycentral.com/feeds/mrss/'
|
_FEED_URL = 'http://comedycentral.com/feeds/mrss/'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.comedycentral.com/video-clips/kllhuv/stand-up-greg-fitzsimmons--uncensored---too-good-of-a-mother',
|
'url': 'http://www.cc.com/video-clips/kllhuv/stand-up-greg-fitzsimmons--uncensored---too-good-of-a-mother',
|
||||||
'md5': 'c4f48e9eda1b16dd10add0744344b6d8',
|
'md5': 'c4f48e9eda1b16dd10add0744344b6d8',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'cef0cbb3-e776-4bc9-b62e-8016deccb354',
|
'id': 'cef0cbb3-e776-4bc9-b62e-8016deccb354',
|
||||||
@ -130,7 +130,7 @@ class ComedyCentralShowsIE(InfoExtractor):
|
|||||||
raise ExtractorError('Invalid redirected URL: ' + url)
|
raise ExtractorError('Invalid redirected URL: ' + url)
|
||||||
if mobj.group('episode') == '':
|
if mobj.group('episode') == '':
|
||||||
raise ExtractorError('Redirected URL is still not specific: ' + url)
|
raise ExtractorError('Redirected URL is still not specific: ' + url)
|
||||||
epTitle = mobj.group('episode').rpartition('/')[-1]
|
epTitle = (mobj.group('episode') or mobj.group('videotitle')).rpartition('/')[-1]
|
||||||
|
|
||||||
mMovieParams = re.findall('(?:<param name="movie" value="|var url = ")(http://media.mtvnservices.com/([^"]*(?:episode|video).*?:.*?))"', webpage)
|
mMovieParams = re.findall('(?:<param name="movie" value="|var url = ")(http://media.mtvnservices.com/([^"]*(?:episode|video).*?:.*?))"', webpage)
|
||||||
if len(mMovieParams) == 0:
|
if len(mMovieParams) == 0:
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import base64
|
import base64
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
|
import netrc
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
import netrc
|
import time
|
||||||
import xml.etree.ElementTree
|
import xml.etree.ElementTree
|
||||||
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
@ -17,6 +18,7 @@ from ..utils import (
|
|||||||
clean_html,
|
clean_html,
|
||||||
compiled_regex_type,
|
compiled_regex_type,
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
|
int_or_none,
|
||||||
RegexNotFoundError,
|
RegexNotFoundError,
|
||||||
sanitize_filename,
|
sanitize_filename,
|
||||||
unescapeHTML,
|
unescapeHTML,
|
||||||
@ -68,6 +70,7 @@ class InfoExtractor(object):
|
|||||||
* vcodec Name of the video codec in use
|
* vcodec Name of the video codec in use
|
||||||
* container Name of the container format
|
* container Name of the container format
|
||||||
* filesize The number of bytes, if known in advance
|
* filesize The number of bytes, if known in advance
|
||||||
|
* filesize_approx An estimate for the number of bytes
|
||||||
* player_url SWF Player URL (used for rtmpdump).
|
* player_url SWF Player URL (used for rtmpdump).
|
||||||
* protocol The protocol that will be used for the actual
|
* protocol The protocol that will be used for the actual
|
||||||
download, lower-case.
|
download, lower-case.
|
||||||
@ -299,8 +302,12 @@ class InfoExtractor(object):
|
|||||||
def _download_json(self, url_or_request, video_id,
|
def _download_json(self, url_or_request, video_id,
|
||||||
note=u'Downloading JSON metadata',
|
note=u'Downloading JSON metadata',
|
||||||
errnote=u'Unable to download JSON metadata',
|
errnote=u'Unable to download JSON metadata',
|
||||||
transform_source=None):
|
transform_source=None,
|
||||||
json_string = self._download_webpage(url_or_request, video_id, note, errnote)
|
fatal=True):
|
||||||
|
json_string = self._download_webpage(
|
||||||
|
url_or_request, video_id, note, errnote, fatal=fatal)
|
||||||
|
if (not fatal) and json_string is False:
|
||||||
|
return None
|
||||||
if transform_source:
|
if transform_source:
|
||||||
json_string = transform_source(json_string)
|
json_string = transform_source(json_string)
|
||||||
try:
|
try:
|
||||||
@ -367,7 +374,8 @@ class InfoExtractor(object):
|
|||||||
else:
|
else:
|
||||||
for p in pattern:
|
for p in pattern:
|
||||||
mobj = re.search(p, string, flags)
|
mobj = re.search(p, string, flags)
|
||||||
if mobj: break
|
if mobj:
|
||||||
|
break
|
||||||
|
|
||||||
if os.name != 'nt' and sys.stderr.isatty():
|
if os.name != 'nt' and sys.stderr.isatty():
|
||||||
_name = u'\033[0;34m%s\033[0m' % name
|
_name = u'\033[0;34m%s\033[0m' % name
|
||||||
@ -455,18 +463,22 @@ class InfoExtractor(object):
|
|||||||
return self._og_search_property('title', html, **kargs)
|
return self._og_search_property('title', html, **kargs)
|
||||||
|
|
||||||
def _og_search_video_url(self, html, name='video url', secure=True, **kargs):
|
def _og_search_video_url(self, html, name='video url', secure=True, **kargs):
|
||||||
regexes = self._og_regexes('video')
|
regexes = self._og_regexes('video') + self._og_regexes('video:url')
|
||||||
if secure: regexes = self._og_regexes('video:secure_url') + regexes
|
if secure:
|
||||||
|
regexes = self._og_regexes('video:secure_url') + regexes
|
||||||
return self._html_search_regex(regexes, html, name, **kargs)
|
return self._html_search_regex(regexes, html, name, **kargs)
|
||||||
|
|
||||||
def _html_search_meta(self, name, html, display_name=None, fatal=False):
|
def _og_search_url(self, html, **kargs):
|
||||||
|
return self._og_search_property('url', html, **kargs)
|
||||||
|
|
||||||
|
def _html_search_meta(self, name, html, display_name=None, fatal=False, **kwargs):
|
||||||
if display_name is None:
|
if display_name is None:
|
||||||
display_name = name
|
display_name = name
|
||||||
return self._html_search_regex(
|
return self._html_search_regex(
|
||||||
r'''(?ix)<meta
|
r'''(?ix)<meta
|
||||||
(?=[^>]+(?:itemprop|name|property)=["\']%s["\'])
|
(?=[^>]+(?:itemprop|name|property)=["\']?%s["\']?)
|
||||||
[^>]+content=["\']([^"\']+)["\']''' % re.escape(name),
|
[^>]+content=["\']([^"\']+)["\']''' % re.escape(name),
|
||||||
html, display_name, fatal=fatal)
|
html, display_name, fatal=fatal, **kwargs)
|
||||||
|
|
||||||
def _dc_search_uploader(self, html):
|
def _dc_search_uploader(self, html):
|
||||||
return self._html_search_meta('dc.creator', html, 'uploader')
|
return self._html_search_meta('dc.creator', html, 'uploader')
|
||||||
@ -551,6 +563,7 @@ class InfoExtractor(object):
|
|||||||
f.get('abr') if f.get('abr') is not None else -1,
|
f.get('abr') if f.get('abr') is not None else -1,
|
||||||
audio_ext_preference,
|
audio_ext_preference,
|
||||||
f.get('filesize') if f.get('filesize') is not None else -1,
|
f.get('filesize') if f.get('filesize') is not None else -1,
|
||||||
|
f.get('filesize_approx') if f.get('filesize_approx') is not None else -1,
|
||||||
f.get('format_id'),
|
f.get('format_id'),
|
||||||
)
|
)
|
||||||
formats.sort(key=_formats_key)
|
formats.sort(key=_formats_key)
|
||||||
@ -572,6 +585,31 @@ class InfoExtractor(object):
|
|||||||
else:
|
else:
|
||||||
return url
|
return url
|
||||||
|
|
||||||
|
def _sleep(self, timeout, video_id, msg_template=None):
|
||||||
|
if msg_template is None:
|
||||||
|
msg_template = u'%(video_id)s: Waiting for %(timeout)s seconds'
|
||||||
|
msg = msg_template % {'video_id': video_id, 'timeout': timeout}
|
||||||
|
self.to_screen(msg)
|
||||||
|
time.sleep(timeout)
|
||||||
|
|
||||||
|
def _extract_f4m_formats(self, manifest_url, video_id):
|
||||||
|
manifest = self._download_xml(
|
||||||
|
manifest_url, video_id, 'Downloading f4m manifest',
|
||||||
|
'Unable to download f4m manifest')
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
for media_el in manifest.findall('{http://ns.adobe.com/f4m/1.0}media'):
|
||||||
|
formats.append({
|
||||||
|
'url': manifest_url,
|
||||||
|
'ext': 'flv',
|
||||||
|
'tbr': int_or_none(media_el.attrib.get('bitrate')),
|
||||||
|
'width': int_or_none(media_el.attrib.get('width')),
|
||||||
|
'height': int_or_none(media_el.attrib.get('height')),
|
||||||
|
})
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
return formats
|
||||||
|
|
||||||
|
|
||||||
class SearchInfoExtractor(InfoExtractor):
|
class SearchInfoExtractor(InfoExtractor):
|
||||||
"""
|
"""
|
||||||
@ -615,4 +653,3 @@ class SearchInfoExtractor(InfoExtractor):
|
|||||||
@property
|
@property
|
||||||
def SEARCH_KEY(self):
|
def SEARCH_KEY(self):
|
||||||
return self._SEARCH_KEY
|
return self._SEARCH_KEY
|
||||||
|
|
||||||
|
65
youtube_dl/extractor/cracked.py
Normal file
65
youtube_dl/extractor/cracked.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
parse_iso8601,
|
||||||
|
str_to_int,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CrackedIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?cracked\.com/video_(?P<id>\d+)_[\da-z-]+\.html'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.cracked.com/video_19006_4-plot-holes-you-didnt-notice-in-your-favorite-movies.html',
|
||||||
|
'md5': '4b29a5eeec292cd5eca6388c7558db9e',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '19006',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': '4 Plot Holes You Didn\'t Notice in Your Favorite Movies',
|
||||||
|
'description': 'md5:3b909e752661db86007d10e5ec2df769',
|
||||||
|
'timestamp': 1405659600,
|
||||||
|
'upload_date': '20140718',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
video_url = self._html_search_regex(
|
||||||
|
[r'var\s+CK_vidSrc\s*=\s*"([^"]+)"', r'<video\s+src="([^"]+)"'], webpage, 'video URL')
|
||||||
|
|
||||||
|
title = self._og_search_title(webpage)
|
||||||
|
description = self._og_search_description(webpage)
|
||||||
|
|
||||||
|
timestamp = self._html_search_regex(r'<time datetime="([^"]+)"', webpage, 'upload date', fatal=False)
|
||||||
|
if timestamp:
|
||||||
|
timestamp = parse_iso8601(timestamp[:-6])
|
||||||
|
|
||||||
|
view_count = str_to_int(self._html_search_regex(
|
||||||
|
r'<span class="views" id="viewCounts">([\d,\.]+) Views</span>', webpage, 'view count', fatal=False))
|
||||||
|
comment_count = str_to_int(self._html_search_regex(
|
||||||
|
r'<span id="commentCounts">([\d,\.]+)</span>', webpage, 'comment count', fatal=False))
|
||||||
|
|
||||||
|
m = re.search(r'_(?P<width>\d+)X(?P<height>\d+)\.mp4$', video_url)
|
||||||
|
if m:
|
||||||
|
width = int(m.group('width'))
|
||||||
|
height = int(m.group('height'))
|
||||||
|
else:
|
||||||
|
width = height = None
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'url':video_url,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'timestamp': timestamp,
|
||||||
|
'view_count': view_count,
|
||||||
|
'comment_count': comment_count,
|
||||||
|
'height': height,
|
||||||
|
'width': width,
|
||||||
|
}
|
@ -1,40 +1,43 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import determine_ext
|
|
||||||
|
|
||||||
class CriterionIE(InfoExtractor):
|
class CriterionIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://www\.criterion\.com/films/(\d*)-.+'
|
_VALID_URL = r'https?://www\.criterion\.com/films/(?P<id>[0-9]+)-.+'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
u'url': u'http://www.criterion.com/films/184-le-samourai',
|
'url': 'http://www.criterion.com/films/184-le-samourai',
|
||||||
u'file': u'184.mp4',
|
'md5': 'bc51beba55685509883a9a7830919ec3',
|
||||||
u'md5': u'bc51beba55685509883a9a7830919ec3',
|
'info_dict': {
|
||||||
u'info_dict': {
|
'id': '184',
|
||||||
u"title": u"Le Samouraï",
|
'ext': 'mp4',
|
||||||
u"description" : u'md5:a2b4b116326558149bef81f76dcbb93f',
|
'title': 'Le Samouraï',
|
||||||
|
'description': 'md5:a2b4b116326558149bef81f76dcbb93f',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
video_id = mobj.group(1)
|
video_id = mobj.group('id')
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
final_url = self._search_regex(r'so.addVariable\("videoURL", "(.+?)"\)\;',
|
final_url = self._search_regex(
|
||||||
webpage, 'video url')
|
r'so.addVariable\("videoURL", "(.+?)"\)\;', webpage, 'video url')
|
||||||
title = self._html_search_regex(r'<meta content="(.+?)" property="og:title" />',
|
title = self._og_search_title(webpage)
|
||||||
webpage, 'video title')
|
description = self._html_search_regex(
|
||||||
description = self._html_search_regex(r'<meta name="description" content="(.+?)" />',
|
r'<meta name="description" content="(.+?)" />',
|
||||||
webpage, 'video description')
|
webpage, 'video description')
|
||||||
thumbnail = self._search_regex(r'so.addVariable\("thumbnailURL", "(.+?)"\)\;',
|
thumbnail = self._search_regex(
|
||||||
webpage, 'thumbnail url')
|
r'so.addVariable\("thumbnailURL", "(.+?)"\)\;',
|
||||||
|
webpage, 'thumbnail url')
|
||||||
|
|
||||||
return {'id': video_id,
|
return {
|
||||||
'url' : final_url,
|
'id': video_id,
|
||||||
'title': title,
|
'url': final_url,
|
||||||
'ext': determine_ext(final_url),
|
'title': title,
|
||||||
'description': description,
|
'description': description,
|
||||||
'thumbnail': thumbnail,
|
'thumbnail': thumbnail,
|
||||||
}
|
}
|
||||||
|
@ -150,7 +150,7 @@ class DailymotionIE(DailymotionBaseInfoExtractor, SubtitlesInfoExtractor):
|
|||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
'uploader': info['owner_screenname'],
|
'uploader': info['owner.screenname'],
|
||||||
'upload_date': video_upload_date,
|
'upload_date': video_upload_date,
|
||||||
'title': self._og_search_title(webpage),
|
'title': self._og_search_title(webpage),
|
||||||
'subtitles': video_subtitles,
|
'subtitles': video_subtitles,
|
||||||
|
44
youtube_dl/extractor/dfb.py
Normal file
44
youtube_dl/extractor/dfb.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
|
class DFBIE(InfoExtractor):
|
||||||
|
IE_NAME = 'tv.dfb.de'
|
||||||
|
_VALID_URL = r'https?://tv\.dfb\.de/video/[^/]+/(?P<id>\d+)'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://tv.dfb.de/video/highlights-des-empfangs-in-berlin/9070/',
|
||||||
|
# The md5 is different each time
|
||||||
|
'info_dict': {
|
||||||
|
'id': '9070',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Highlights des Empfangs in Berlin',
|
||||||
|
'upload_date': '20140716',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
player_info = self._download_xml(
|
||||||
|
'http://tv.dfb.de/server/hd_video.php?play=%s' % video_id,
|
||||||
|
video_id)
|
||||||
|
video_info = player_info.find('video')
|
||||||
|
|
||||||
|
f4m_info = self._download_xml(self._proto_relative_url(video_info.find('url').text.strip()), video_id)
|
||||||
|
token_el = f4m_info.find('token')
|
||||||
|
manifest_url = token_el.attrib['url'] + '?' + 'hdnea=' + token_el.attrib['auth'] + '&hdcore=3.2.0'
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': video_info.find('title').text,
|
||||||
|
'url': manifest_url,
|
||||||
|
'ext': 'flv',
|
||||||
|
'thumbnail': self._og_search_thumbnail(webpage),
|
||||||
|
'upload_date': ''.join(video_info.find('time_date').text.split('.')[::-1]),
|
||||||
|
}
|
@ -7,9 +7,9 @@ from .common import InfoExtractor
|
|||||||
|
|
||||||
|
|
||||||
class DiscoveryIE(InfoExtractor):
|
class DiscoveryIE(InfoExtractor):
|
||||||
_VALID_URL = r'http://dsc\.discovery\.com\/[a-zA-Z0-9\-]*/[a-zA-Z0-9\-]*/videos/(?P<id>[a-zA-Z0-9\-]*)(.htm)?'
|
_VALID_URL = r'http://www\.discovery\.com\/[a-zA-Z0-9\-]*/[a-zA-Z0-9\-]*/videos/(?P<id>[a-zA-Z0-9\-]*)(.htm)?'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://dsc.discovery.com/tv-shows/mythbusters/videos/mission-impossible-outtakes.htm',
|
'url': 'http://www.discovery.com/tv-shows/mythbusters/videos/mission-impossible-outtakes.htm',
|
||||||
'md5': 'e12614f9ee303a6ccef415cb0793eba2',
|
'md5': 'e12614f9ee303a6ccef415cb0793eba2',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '614784',
|
'id': '614784',
|
||||||
|
@ -5,24 +5,26 @@ import os.path
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from ..utils import compat_urllib_parse_unquote
|
||||||
|
|
||||||
|
|
||||||
class DropboxIE(InfoExtractor):
|
class DropboxIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?dropbox[.]com/s/(?P<id>[a-zA-Z0-9]{15})/(?P<title>[^?#]*)'
|
_VALID_URL = r'https?://(?:www\.)?dropbox[.]com/s/(?P<id>[a-zA-Z0-9]{15})/(?P<title>[^?#]*)'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'https://www.dropbox.com/s/0qr9sai2veej4f8/THE_DOCTOR_GAMES.mp4',
|
'url': 'https://www.dropbox.com/s/nelirfsxnmcfbfh/youtube-dl%20test%20video%20%27%C3%A4%22BaW_jenozKc.mp4',
|
||||||
'md5': '8ae17c51172fb7f93bdd6a214cc8c896',
|
'md5': '8a3d905427a6951ccb9eb292f154530b',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '0qr9sai2veej4f8',
|
'id': 'nelirfsxnmcfbfh',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'THE_DOCTOR_GAMES'
|
'title': 'youtube-dl test video \'ä"BaW_jenozKc'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
video_id = mobj.group('id')
|
video_id = mobj.group('id')
|
||||||
title = os.path.splitext(mobj.group('title'))[0]
|
fn = compat_urllib_parse_unquote(mobj.group('title'))
|
||||||
|
title = os.path.splitext(fn)[0]
|
||||||
video_url = url + '?dl=1'
|
video_url = url + '?dl=1'
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
91
youtube_dl/extractor/drtv.py
Normal file
91
youtube_dl/extractor/drtv.py
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .subtitles import SubtitlesInfoExtractor
|
||||||
|
from .common import ExtractorError
|
||||||
|
from ..utils import parse_iso8601
|
||||||
|
|
||||||
|
|
||||||
|
class DRTVIE(SubtitlesInfoExtractor):
|
||||||
|
_VALID_URL = r'http://(?:www\.)?dr\.dk/tv/se/[^/]+/(?P<id>[\da-z-]+)'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.dr.dk/tv/se/partiets-mand/partiets-mand-7-8',
|
||||||
|
'md5': '4a7e1dd65cdb2643500a3f753c942f25',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'partiets-mand-7-8',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Partiets mand (7:8)',
|
||||||
|
'description': 'md5:a684b90a8f9336cd4aab94b7647d7862',
|
||||||
|
'timestamp': 1403047940,
|
||||||
|
'upload_date': '20140617',
|
||||||
|
'duration': 1299.040,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
programcard = self._download_json(
|
||||||
|
'http://www.dr.dk/mu/programcard/expanded/%s' % video_id, video_id, 'Downloading video JSON')
|
||||||
|
|
||||||
|
data = programcard['Data'][0]
|
||||||
|
|
||||||
|
title = data['Title']
|
||||||
|
description = data['Description']
|
||||||
|
timestamp = parse_iso8601(data['CreatedTime'][:-5])
|
||||||
|
|
||||||
|
thumbnail = None
|
||||||
|
duration = None
|
||||||
|
|
||||||
|
restricted_to_denmark = False
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
subtitles = {}
|
||||||
|
|
||||||
|
for asset in data['Assets']:
|
||||||
|
if asset['Kind'] == 'Image':
|
||||||
|
thumbnail = asset['Uri']
|
||||||
|
elif asset['Kind'] == 'VideoResource':
|
||||||
|
duration = asset['DurationInMilliseconds'] / 1000.0
|
||||||
|
restricted_to_denmark = asset['RestrictedToDenmark']
|
||||||
|
for link in asset['Links']:
|
||||||
|
target = link['Target']
|
||||||
|
uri = link['Uri']
|
||||||
|
formats.append({
|
||||||
|
'url': uri + '?hdcore=3.3.0&plugin=aasp-3.3.0.99.43' if target == 'HDS' else uri,
|
||||||
|
'format_id': target,
|
||||||
|
'ext': link['FileFormat'],
|
||||||
|
'preference': -1 if target == 'HDS' else -2,
|
||||||
|
})
|
||||||
|
subtitles_list = asset.get('SubtitlesList')
|
||||||
|
if isinstance(subtitles_list, list):
|
||||||
|
LANGS = {
|
||||||
|
'Danish': 'dk',
|
||||||
|
}
|
||||||
|
for subs in subtitles_list:
|
||||||
|
lang = subs['Language']
|
||||||
|
subtitles[LANGS.get(lang, lang)] = subs['Uri']
|
||||||
|
|
||||||
|
if not formats and restricted_to_denmark:
|
||||||
|
raise ExtractorError(
|
||||||
|
'Unfortunately, DR is not allowed to show this program outside Denmark.', expected=True)
|
||||||
|
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
if self._downloader.params.get('listsubtitles', False):
|
||||||
|
self._list_available_subtitles(video_id, subtitles)
|
||||||
|
return
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'timestamp': timestamp,
|
||||||
|
'duration': duration,
|
||||||
|
'formats': formats,
|
||||||
|
'subtitles': self.extract_subtitles(video_id, subtitles),
|
||||||
|
}
|
39
youtube_dl/extractor/dump.py
Normal file
39
youtube_dl/extractor/dump.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# encoding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
|
class DumpIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'^https?://(?:www\.)?dump\.com/(?P<id>[a-zA-Z0-9]+)/'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.dump.com/oneus/',
|
||||||
|
'md5': 'ad71704d1e67dfd9e81e3e8b42d69d99',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'oneus',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': "He's one of us.",
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
m = re.match(self._VALID_URL, url)
|
||||||
|
video_id = m.group('id')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
video_url = self._search_regex(
|
||||||
|
r's1.addVariable\("file",\s*"([^"]+)"', webpage, 'video URL')
|
||||||
|
|
||||||
|
thumb = self._og_search_thumbnail(webpage)
|
||||||
|
title = self._search_regex(r'<b>([^"]+)</b>', webpage, 'title')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'url': video_url,
|
||||||
|
'thumbnail': thumb,
|
||||||
|
}
|
79
youtube_dl/extractor/ellentv.py
Normal file
79
youtube_dl/extractor/ellentv.py
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
ExtractorError,
|
||||||
|
parse_iso8601,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class EllenTVIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?ellentv\.com/videos/(?P<id>[a-z0-9_-]+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.ellentv.com/videos/0-7jqrsr18/',
|
||||||
|
'md5': 'e4af06f3bf0d5f471921a18db5764642',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '0-7jqrsr18',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'What\'s Wrong with These Photos? A Whole Lot',
|
||||||
|
'timestamp': 1406876400,
|
||||||
|
'upload_date': '20140801',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
timestamp = parse_iso8601(self._search_regex(
|
||||||
|
r'<span class="publish-date"><time datetime="([^"]+)">',
|
||||||
|
webpage, 'timestamp'))
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': self._og_search_title(webpage),
|
||||||
|
'url': self._html_search_meta('VideoURL', webpage, 'url'),
|
||||||
|
'timestamp': timestamp,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class EllenTVClipsIE(InfoExtractor):
|
||||||
|
IE_NAME = 'EllenTV:clips'
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?ellentv\.com/episodes/(?P<id>[a-z0-9_-]+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.ellentv.com/episodes/meryl-streep-vanessa-hudgens/',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'meryl-streep-vanessa-hudgens',
|
||||||
|
'title': 'Meryl Streep, Vanessa Hudgens',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 9,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
playlist_id = mobj.group('id')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, playlist_id)
|
||||||
|
playlist = self._extract_playlist(webpage)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'_type': 'playlist',
|
||||||
|
'id': playlist_id,
|
||||||
|
'title': self._og_search_title(webpage),
|
||||||
|
'entries': self._extract_entries(playlist)
|
||||||
|
}
|
||||||
|
|
||||||
|
def _extract_playlist(self, webpage):
|
||||||
|
json_string = self._search_regex(r'playerView.addClips\(\[\{(.*?)\}\]\);', webpage, 'json')
|
||||||
|
try:
|
||||||
|
return json.loads("[{" + json_string + "}]")
|
||||||
|
except ValueError as ve:
|
||||||
|
raise ExtractorError('Failed to download JSON', cause=ve)
|
||||||
|
|
||||||
|
def _extract_entries(self, playlist):
|
||||||
|
return [self.url_result(item['url'], 'EllenTV') for item in playlist]
|
@ -36,7 +36,7 @@ class EscapistIE(InfoExtractor):
|
|||||||
r'<meta name="description" content="([^"]*)"',
|
r'<meta name="description" content="([^"]*)"',
|
||||||
webpage, 'description', fatal=False)
|
webpage, 'description', fatal=False)
|
||||||
|
|
||||||
playerUrl = self._og_search_video_url(webpage, name=u'player URL')
|
playerUrl = self._og_search_video_url(webpage, name='player URL')
|
||||||
|
|
||||||
title = self._html_search_regex(
|
title = self._html_search_regex(
|
||||||
r'<meta name="title" content="([^"]*)"',
|
r'<meta name="title" content="([^"]*)"',
|
||||||
|
@ -20,7 +20,7 @@ from ..utils import (
|
|||||||
class FacebookIE(InfoExtractor):
|
class FacebookIE(InfoExtractor):
|
||||||
_VALID_URL = r'''(?x)
|
_VALID_URL = r'''(?x)
|
||||||
https?://(?:\w+\.)?facebook\.com/
|
https?://(?:\w+\.)?facebook\.com/
|
||||||
(?:[^#?]*\#!/)?
|
(?:[^#]*?\#!/)?
|
||||||
(?:video/video\.php|photo\.php|video/embed)\?(?:.*?)
|
(?:video/video\.php|photo\.php|video/embed)\?(?:.*?)
|
||||||
(?:v|video_id)=(?P<id>[0-9]+)
|
(?:v|video_id)=(?P<id>[0-9]+)
|
||||||
(?:.*)'''
|
(?:.*)'''
|
||||||
|
81
youtube_dl/extractor/firedrive.py
Normal file
81
youtube_dl/extractor/firedrive.py
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
ExtractorError,
|
||||||
|
compat_urllib_parse,
|
||||||
|
compat_urllib_request,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class FiredriveIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?firedrive\.com/' + \
|
||||||
|
'(?:file|embed)/(?P<id>[0-9a-zA-Z]+)'
|
||||||
|
_FILE_DELETED_REGEX = r'<div class="removed_file_image">'
|
||||||
|
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://www.firedrive.com/file/FEB892FA160EBD01',
|
||||||
|
'md5': 'd5d4252f80ebeab4dc2d5ceaed1b7970',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'FEB892FA160EBD01',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'bbb_theora_486kbit.flv',
|
||||||
|
'thumbnail': 're:^http://.*\.jpg$',
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
url = 'http://firedrive.com/file/%s' % video_id
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
if re.search(self._FILE_DELETED_REGEX, webpage) is not None:
|
||||||
|
raise ExtractorError('Video %s does not exist' % video_id,
|
||||||
|
expected=True)
|
||||||
|
|
||||||
|
fields = dict(re.findall(r'''(?x)<input\s+
|
||||||
|
type="hidden"\s+
|
||||||
|
name="([^"]+)"\s+
|
||||||
|
value="([^"]*)"
|
||||||
|
''', webpage))
|
||||||
|
|
||||||
|
post = compat_urllib_parse.urlencode(fields)
|
||||||
|
req = compat_urllib_request.Request(url, post)
|
||||||
|
req.add_header('Content-type', 'application/x-www-form-urlencoded')
|
||||||
|
|
||||||
|
# Apparently, this header is required for confirmation to work.
|
||||||
|
req.add_header('Host', 'www.firedrive.com')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(req, video_id,
|
||||||
|
'Downloading video page')
|
||||||
|
|
||||||
|
title = self._search_regex(r'class="external_title_left">(.+)</div>',
|
||||||
|
webpage, 'title')
|
||||||
|
thumbnail = self._search_regex(r'image:\s?"(//[^\"]+)', webpage,
|
||||||
|
'thumbnail', fatal=False)
|
||||||
|
if thumbnail is not None:
|
||||||
|
thumbnail = 'http:' + thumbnail
|
||||||
|
|
||||||
|
ext = self._search_regex(r'type:\s?\'([^\']+)\',',
|
||||||
|
webpage, 'extension', fatal=False)
|
||||||
|
video_url = self._search_regex(
|
||||||
|
r'file:\s?loadURL\(\'(http[^\']+)\'\),', webpage, 'file url')
|
||||||
|
|
||||||
|
formats = [{
|
||||||
|
'format_id': 'sd',
|
||||||
|
'url': video_url,
|
||||||
|
'ext': ext,
|
||||||
|
}]
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'formats': formats,
|
||||||
|
}
|
@ -19,17 +19,35 @@ class FranceTVBaseInfoExtractor(InfoExtractor):
|
|||||||
+ video_id, video_id, 'Downloading XML config')
|
+ video_id, video_id, 'Downloading XML config')
|
||||||
|
|
||||||
manifest_url = info.find('videos/video/url').text
|
manifest_url = info.find('videos/video/url').text
|
||||||
video_url = manifest_url.replace('manifest.f4m', 'index_2_av.m3u8')
|
manifest_url = manifest_url.replace('/z/', '/i/')
|
||||||
video_url = video_url.replace('/z/', '/i/')
|
|
||||||
|
if manifest_url.startswith('rtmp'):
|
||||||
|
formats = [{'url': manifest_url, 'ext': 'flv'}]
|
||||||
|
else:
|
||||||
|
formats = []
|
||||||
|
available_formats = self._search_regex(r'/[^,]*,(.*?),k\.mp4', manifest_url, 'available formats')
|
||||||
|
for index, format_descr in enumerate(available_formats.split(',')):
|
||||||
|
format_info = {
|
||||||
|
'url': manifest_url.replace('manifest.f4m', 'index_%d_av.m3u8' % index),
|
||||||
|
'ext': 'mp4',
|
||||||
|
}
|
||||||
|
m_resolution = re.search(r'(?P<width>\d+)x(?P<height>\d+)', format_descr)
|
||||||
|
if m_resolution is not None:
|
||||||
|
format_info.update({
|
||||||
|
'width': int(m_resolution.group('width')),
|
||||||
|
'height': int(m_resolution.group('height')),
|
||||||
|
})
|
||||||
|
formats.append(format_info)
|
||||||
|
|
||||||
thumbnail_path = info.find('image').text
|
thumbnail_path = info.find('image').text
|
||||||
|
|
||||||
return {'id': video_id,
|
return {
|
||||||
'ext': 'flv' if video_url.startswith('rtmp') else 'mp4',
|
'id': video_id,
|
||||||
'url': video_url,
|
'title': info.find('titre').text,
|
||||||
'title': info.find('titre').text,
|
'formats': formats,
|
||||||
'thumbnail': compat_urlparse.urljoin('http://pluzz.francetv.fr', thumbnail_path),
|
'thumbnail': compat_urlparse.urljoin('http://pluzz.francetv.fr', thumbnail_path),
|
||||||
'description': info.find('synopsis').text,
|
'description': info.find('synopsis').text,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PluzzIE(FranceTVBaseInfoExtractor):
|
class PluzzIE(FranceTVBaseInfoExtractor):
|
||||||
@ -48,7 +66,7 @@ class PluzzIE(FranceTVBaseInfoExtractor):
|
|||||||
|
|
||||||
class FranceTvInfoIE(FranceTVBaseInfoExtractor):
|
class FranceTvInfoIE(FranceTVBaseInfoExtractor):
|
||||||
IE_NAME = 'francetvinfo.fr'
|
IE_NAME = 'francetvinfo.fr'
|
||||||
_VALID_URL = r'https?://www\.francetvinfo\.fr/.*/(?P<title>.+)\.html'
|
_VALID_URL = r'https?://(?:www|mobile)\.francetvinfo\.fr/.*/(?P<title>.+)\.html'
|
||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.francetvinfo.fr/replay-jt/france-3/soir-3/jt-grand-soir-3-lundi-26-aout-2013_393427.html',
|
'url': 'http://www.francetvinfo.fr/replay-jt/france-3/soir-3/jt-grand-soir-3-lundi-26-aout-2013_393427.html',
|
||||||
@ -211,7 +229,7 @@ class GenerationQuoiIE(InfoExtractor):
|
|||||||
|
|
||||||
class CultureboxIE(FranceTVBaseInfoExtractor):
|
class CultureboxIE(FranceTVBaseInfoExtractor):
|
||||||
IE_NAME = 'culturebox.francetvinfo.fr'
|
IE_NAME = 'culturebox.francetvinfo.fr'
|
||||||
_VALID_URL = r'https?://culturebox\.francetvinfo\.fr/(?P<name>.*?)(\?|$)'
|
_VALID_URL = r'https?://(?:m\.)?culturebox\.francetvinfo\.fr/(?P<name>.*?)(\?|$)'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://culturebox.francetvinfo.fr/einstein-on-the-beach-au-theatre-du-chatelet-146813',
|
'url': 'http://culturebox.francetvinfo.fr/einstein-on-the-beach-au-theatre-du-chatelet-146813',
|
||||||
|
@ -26,7 +26,7 @@ class FunnyOrDieIE(InfoExtractor):
|
|||||||
'id': 'e402820827',
|
'id': 'e402820827',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Please Use This Song (Jon Lajoie)',
|
'title': 'Please Use This Song (Jon Lajoie)',
|
||||||
'description': 'md5:2ed27d364f5a805a6dba199faaf6681d',
|
'description': 'Please use this to sell something. www.jonlajoie.com',
|
||||||
'thumbnail': 're:^http:.*\.jpg$',
|
'thumbnail': 're:^http:.*\.jpg$',
|
||||||
},
|
},
|
||||||
}]
|
}]
|
||||||
|
90
youtube_dl/extractor/gameone.py
Normal file
90
youtube_dl/extractor/gameone.py
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
xpath_with_ns,
|
||||||
|
parse_iso8601
|
||||||
|
)
|
||||||
|
|
||||||
|
NAMESPACE_MAP = {
|
||||||
|
'media': 'http://search.yahoo.com/mrss/',
|
||||||
|
}
|
||||||
|
|
||||||
|
# URL prefix to download the mp4 files directly instead of streaming via rtmp
|
||||||
|
# Credits go to XBox-Maniac
|
||||||
|
# http://board.jdownloader.org/showpost.php?p=185835&postcount=31
|
||||||
|
RAW_MP4_URL = 'http://cdn.riptide-mtvn.com/'
|
||||||
|
|
||||||
|
|
||||||
|
class GameOneIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?gameone\.de/tv/(?P<id>\d+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.gameone.de/tv/288',
|
||||||
|
'md5': '136656b7fb4c9cb4a8e2d500651c499b',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '288',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Game One - Folge 288',
|
||||||
|
'duration': 1238,
|
||||||
|
'thumbnail': 'http://s3.gameone.de/gameone/assets/video_metas/teaser_images/000/643/636/big/640x360.jpg',
|
||||||
|
'description': 'FIFA-Pressepokal 2014, Star Citizen, Kingdom Come: Deliverance, Project Cars, Schöner Trants Nerdquiz Folge 2 Runde 1',
|
||||||
|
'age_limit': 16,
|
||||||
|
'upload_date': '20140513',
|
||||||
|
'timestamp': 1399980122,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
og_video = self._og_search_video_url(webpage, secure=False)
|
||||||
|
description = self._html_search_meta('description', webpage)
|
||||||
|
age_limit = int(
|
||||||
|
self._search_regex(
|
||||||
|
r'age=(\d+)',
|
||||||
|
self._html_search_meta(
|
||||||
|
'age-de-meta-label',
|
||||||
|
webpage),
|
||||||
|
'age_limit',
|
||||||
|
'0'))
|
||||||
|
mrss_url = self._search_regex(r'mrss=([^&]+)', og_video, 'mrss')
|
||||||
|
|
||||||
|
mrss = self._download_xml(mrss_url, video_id, 'Downloading mrss')
|
||||||
|
title = mrss.find('.//item/title').text
|
||||||
|
thumbnail = mrss.find('.//item/image').get('url')
|
||||||
|
timestamp = parse_iso8601(mrss.find('.//pubDate').text, delimiter=' ')
|
||||||
|
content = mrss.find(xpath_with_ns('.//media:content', NAMESPACE_MAP))
|
||||||
|
content_url = content.get('url')
|
||||||
|
|
||||||
|
content = self._download_xml(
|
||||||
|
content_url,
|
||||||
|
video_id,
|
||||||
|
'Downloading media:content')
|
||||||
|
rendition_items = content.findall('.//rendition')
|
||||||
|
duration = int(rendition_items[0].get('duration'))
|
||||||
|
formats = [
|
||||||
|
{
|
||||||
|
'url': re.sub(r'.*/(r2)', RAW_MP4_URL + r'\1', r.find('./src').text),
|
||||||
|
'width': int(r.get('width')),
|
||||||
|
'height': int(r.get('height')),
|
||||||
|
'tbr': int(r.get('bitrate')),
|
||||||
|
}
|
||||||
|
for r in rendition_items
|
||||||
|
]
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'duration': duration,
|
||||||
|
'formats': formats,
|
||||||
|
'description': description,
|
||||||
|
'age_limit': age_limit,
|
||||||
|
'timestamp': timestamp,
|
||||||
|
}
|
74
youtube_dl/extractor/gamestar.py
Normal file
74
youtube_dl/extractor/gamestar.py
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
int_or_none,
|
||||||
|
parse_duration,
|
||||||
|
str_to_int,
|
||||||
|
unified_strdate,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class GameStarIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'http://www\.gamestar\.de/videos/.*,(?P<id>[0-9]+)\.html'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.gamestar.de/videos/trailer,3/hobbit-3-die-schlacht-der-fuenf-heere,76110.html',
|
||||||
|
'md5': '96974ecbb7fd8d0d20fca5a00810cea7',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '76110',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Hobbit 3: Die Schlacht der Fünf Heere - Teaser-Trailer zum dritten Teil',
|
||||||
|
'description': 'Der Teaser-Trailer zu Hobbit 3: Die Schlacht der Fünf Heere zeigt einige Szenen aus dem dritten Teil der Saga und kündigt den vollständigen Trailer an.',
|
||||||
|
'thumbnail': 'http://images.gamestar.de/images/idgwpgsgp/bdb/2494525/600x.jpg',
|
||||||
|
'upload_date': '20140728',
|
||||||
|
'duration': 17
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
og_title = self._og_search_title(webpage)
|
||||||
|
title = og_title.replace(' - Video bei GameStar.de', '').strip()
|
||||||
|
|
||||||
|
url = 'http://gamestar.de/_misc/videos/portal/getVideoUrl.cfm?premium=0&videoId=' + video_id
|
||||||
|
|
||||||
|
description = self._og_search_description(webpage).strip()
|
||||||
|
|
||||||
|
thumbnail = self._proto_relative_url(
|
||||||
|
self._og_search_thumbnail(webpage), scheme='http:')
|
||||||
|
|
||||||
|
upload_date = unified_strdate(self._html_search_regex(
|
||||||
|
r'<span style="float:left;font-size:11px;">Datum: ([0-9]+\.[0-9]+\.[0-9]+) ',
|
||||||
|
webpage, 'upload_date', fatal=False))
|
||||||
|
|
||||||
|
duration = parse_duration(self._html_search_regex(
|
||||||
|
r' Länge: ([0-9]+:[0-9]+)</span>', webpage, 'duration',
|
||||||
|
fatal=False))
|
||||||
|
|
||||||
|
view_count = str_to_int(self._html_search_regex(
|
||||||
|
r' Zuschauer: ([0-9\.]+) ', webpage,
|
||||||
|
'view_count', fatal=False))
|
||||||
|
|
||||||
|
comment_count = int_or_none(self._html_search_regex(
|
||||||
|
r'>Kommentieren \(([0-9]+)\)</a>', webpage, 'comment_count',
|
||||||
|
fatal=False))
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'url': url,
|
||||||
|
'ext': 'mp4',
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'description': description,
|
||||||
|
'upload_date': upload_date,
|
||||||
|
'duration': duration,
|
||||||
|
'view_count': view_count,
|
||||||
|
'comment_count': comment_count
|
||||||
|
}
|
@ -8,6 +8,7 @@ from ..utils import (
|
|||||||
compat_urllib_request,
|
compat_urllib_request,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class GDCVaultIE(InfoExtractor):
|
class GDCVaultIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?gdcvault\.com/play/(?P<id>\d+)/(?P<name>(\w|-)+)'
|
_VALID_URL = r'https?://(?:www\.)?gdcvault\.com/play/(?P<id>\d+)/(?P<name>(\w|-)+)'
|
||||||
_TESTS = [
|
_TESTS = [
|
||||||
@ -31,6 +32,15 @@ class GDCVaultIE(InfoExtractor):
|
|||||||
'skip_download': True, # Requires rtmpdump
|
'skip_download': True, # Requires rtmpdump
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://www.gdcvault.com/play/1015301/Thexder-Meets-Windows-95-or',
|
||||||
|
'md5': 'a5eb77996ef82118afbbe8e48731b98e',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '1015301',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Thexder Meets Windows 95, or Writing Great Games in the Windows 95 Environment',
|
||||||
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
def _parse_mp4(self, xml_description):
|
def _parse_mp4(self, xml_description):
|
||||||
@ -103,18 +113,40 @@ class GDCVaultIE(InfoExtractor):
|
|||||||
webpage_url = 'http://www.gdcvault.com/play/' + video_id
|
webpage_url = 'http://www.gdcvault.com/play/' + video_id
|
||||||
start_page = self._download_webpage(webpage_url, video_id)
|
start_page = self._download_webpage(webpage_url, video_id)
|
||||||
|
|
||||||
xml_root = self._html_search_regex(r'<iframe src="(?P<xml_root>.*?)player.html.*?".*?</iframe>', start_page, 'xml root', None, False)
|
direct_url = self._search_regex(
|
||||||
|
r's1\.addVariable\("file",\s*encodeURIComponent\("(/[^"]+)"\)\);',
|
||||||
|
start_page, 'url', default=None)
|
||||||
|
if direct_url:
|
||||||
|
video_url = 'http://www.gdcvault.com/' + direct_url
|
||||||
|
title = self._html_search_regex(
|
||||||
|
r'<td><strong>Session Name</strong></td>\s*<td>(.*?)</td>',
|
||||||
|
start_page, 'title')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'url': video_url,
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': title,
|
||||||
|
}
|
||||||
|
|
||||||
|
xml_root = self._html_search_regex(
|
||||||
|
r'<iframe src="(?P<xml_root>.*?)player.html.*?".*?</iframe>',
|
||||||
|
start_page, 'xml root', default=None)
|
||||||
if xml_root is None:
|
if xml_root is None:
|
||||||
# Probably need to authenticate
|
# Probably need to authenticate
|
||||||
start_page = self._login(webpage_url, video_id)
|
login_res = self._login(webpage_url, video_id)
|
||||||
if start_page is None:
|
if login_res is None:
|
||||||
self.report_warning('Could not login.')
|
self.report_warning('Could not login.')
|
||||||
else:
|
else:
|
||||||
|
start_page = login_res
|
||||||
# Grab the url from the authenticated page
|
# Grab the url from the authenticated page
|
||||||
xml_root = self._html_search_regex(r'<iframe src="(?P<xml_root>.*?)player.html.*?".*?</iframe>', start_page, 'xml root')
|
xml_root = self._html_search_regex(
|
||||||
|
r'<iframe src="(.*?)player.html.*?".*?</iframe>',
|
||||||
|
start_page, 'xml root')
|
||||||
|
|
||||||
xml_name = self._html_search_regex(r'<iframe src=".*?\?xml=(?P<xml_file>.+?\.xml).*?".*?</iframe>', start_page, 'xml filename', None, False)
|
xml_name = self._html_search_regex(
|
||||||
|
r'<iframe src=".*?\?xml=(.+?\.xml).*?".*?</iframe>',
|
||||||
|
start_page, 'xml filename', default=None)
|
||||||
if xml_name is None:
|
if xml_name is None:
|
||||||
# Fallback to the older format
|
# Fallback to the older format
|
||||||
xml_name = self._html_search_regex(r'<iframe src=".*?\?xmlURL=xml/(?P<xml_file>.+?\.xml).*?".*?</iframe>', start_page, 'xml filename')
|
xml_name = self._html_search_regex(r'<iframe src=".*?\?xmlURL=xml/(?P<xml_file>.+?\.xml).*?".*?</iframe>', start_page, 'xml filename')
|
||||||
|
@ -16,6 +16,7 @@ from ..utils import (
|
|||||||
|
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
HEADRequest,
|
HEADRequest,
|
||||||
|
orderedSet,
|
||||||
parse_xml,
|
parse_xml,
|
||||||
smuggle_url,
|
smuggle_url,
|
||||||
unescapeHTML,
|
unescapeHTML,
|
||||||
@ -289,6 +290,22 @@ class GenericIE(InfoExtractor):
|
|||||||
'description': 'Mario\'s life in the fast lane has never looked so good.',
|
'description': 'Mario\'s life in the fast lane has never looked so good.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
# YouTube embed via <data-embed-url="">
|
||||||
|
{
|
||||||
|
'url': 'https://play.google.com/store/apps/details?id=com.gameloft.android.ANMP.GloftA8HM',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'jpSGZsgga_I',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Asphalt 8: Airborne - Launch Trailer',
|
||||||
|
'uploader': 'Gameloft',
|
||||||
|
'uploader_id': 'gameloft',
|
||||||
|
'upload_date': '20130821',
|
||||||
|
'description': 'md5:87bd95f13d8be3e7da87a5f2c443106a',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True,
|
||||||
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
def report_download_webpage(self, video_id):
|
def report_download_webpage(self, video_id):
|
||||||
@ -383,13 +400,13 @@ class GenericIE(InfoExtractor):
|
|||||||
if not parsed_url.scheme:
|
if not parsed_url.scheme:
|
||||||
default_search = self._downloader.params.get('default_search')
|
default_search = self._downloader.params.get('default_search')
|
||||||
if default_search is None:
|
if default_search is None:
|
||||||
default_search = 'auto_warning'
|
default_search = 'fixup_error'
|
||||||
|
|
||||||
if default_search in ('auto', 'auto_warning'):
|
if default_search in ('auto', 'auto_warning', 'fixup_error'):
|
||||||
if '/' in url:
|
if '/' in url:
|
||||||
self._downloader.report_warning('The url doesn\'t specify the protocol, trying with http')
|
self._downloader.report_warning('The url doesn\'t specify the protocol, trying with http')
|
||||||
return self.url_result('http://' + url)
|
return self.url_result('http://' + url)
|
||||||
else:
|
elif default_search != 'fixup_error':
|
||||||
if default_search == 'auto_warning':
|
if default_search == 'auto_warning':
|
||||||
if re.match(r'^(?:url|URL)$', url):
|
if re.match(r'^(?:url|URL)$', url):
|
||||||
raise ExtractorError(
|
raise ExtractorError(
|
||||||
@ -397,8 +414,14 @@ class GenericIE(InfoExtractor):
|
|||||||
expected=True)
|
expected=True)
|
||||||
else:
|
else:
|
||||||
self._downloader.report_warning(
|
self._downloader.report_warning(
|
||||||
'Falling back to youtube search for %s . Set --default-search to "auto" to suppress this warning.' % url)
|
'Falling back to youtube search for %s . Set --default-search "auto" to suppress this warning.' % url)
|
||||||
return self.url_result('ytsearch:' + url)
|
return self.url_result('ytsearch:' + url)
|
||||||
|
|
||||||
|
if default_search in ('error', 'fixup_error'):
|
||||||
|
raise ExtractorError(
|
||||||
|
('%r is not a valid URL. '
|
||||||
|
'Set --default-search "ytsearch" (or run youtube-dl "ytsearch:%s" ) to search YouTube'
|
||||||
|
) % (url, url), expected=True)
|
||||||
else:
|
else:
|
||||||
assert ':' in default_search
|
assert ':' in default_search
|
||||||
return self.url_result(default_search + url)
|
return self.url_result(default_search + url)
|
||||||
@ -473,6 +496,12 @@ class GenericIE(InfoExtractor):
|
|||||||
video_uploader = self._search_regex(
|
video_uploader = self._search_regex(
|
||||||
r'^(?:https?://)?([^/]*)/.*', url, 'video uploader')
|
r'^(?:https?://)?([^/]*)/.*', url, 'video uploader')
|
||||||
|
|
||||||
|
# Helper method
|
||||||
|
def _playlist_from_matches(matches, getter, ie=None):
|
||||||
|
urlrs = orderedSet(self.url_result(getter(m), ie) for m in matches)
|
||||||
|
return self.playlist_result(
|
||||||
|
urlrs, playlist_id=video_id, playlist_title=video_title)
|
||||||
|
|
||||||
# Look for BrightCove:
|
# Look for BrightCove:
|
||||||
bc_urls = BrightcoveIE._extract_brightcove_urls(webpage)
|
bc_urls = BrightcoveIE._extract_brightcove_urls(webpage)
|
||||||
if bc_urls:
|
if bc_urls:
|
||||||
@ -508,6 +537,7 @@ class GenericIE(InfoExtractor):
|
|||||||
matches = re.findall(r'''(?x)
|
matches = re.findall(r'''(?x)
|
||||||
(?:
|
(?:
|
||||||
<iframe[^>]+?src=|
|
<iframe[^>]+?src=|
|
||||||
|
data-video-url=|
|
||||||
<embed[^>]+?src=|
|
<embed[^>]+?src=|
|
||||||
embedSWF\(?:\s*
|
embedSWF\(?:\s*
|
||||||
)
|
)
|
||||||
@ -516,19 +546,15 @@ class GenericIE(InfoExtractor):
|
|||||||
(?:embed|v)/.+?)
|
(?:embed|v)/.+?)
|
||||||
\1''', webpage)
|
\1''', webpage)
|
||||||
if matches:
|
if matches:
|
||||||
urlrs = [self.url_result(unescapeHTML(tuppl[1]), 'Youtube')
|
return _playlist_from_matches(
|
||||||
for tuppl in matches]
|
matches, lambda m: unescapeHTML(m[1]), ie='Youtube')
|
||||||
return self.playlist_result(
|
|
||||||
urlrs, playlist_id=video_id, playlist_title=video_title)
|
|
||||||
|
|
||||||
# Look for embedded Dailymotion player
|
# Look for embedded Dailymotion player
|
||||||
matches = re.findall(
|
matches = re.findall(
|
||||||
r'<iframe[^>]+?src=(["\'])(?P<url>(?:https?:)?//(?:www\.)?dailymotion\.com/embed/video/.+?)\1', webpage)
|
r'<iframe[^>]+?src=(["\'])(?P<url>(?:https?:)?//(?:www\.)?dailymotion\.com/embed/video/.+?)\1', webpage)
|
||||||
if matches:
|
if matches:
|
||||||
urlrs = [self.url_result(unescapeHTML(tuppl[1]))
|
return _playlist_from_matches(
|
||||||
for tuppl in matches]
|
matches, lambda m: unescapeHTML(m[1]))
|
||||||
return self.playlist_result(
|
|
||||||
urlrs, playlist_id=video_id, playlist_title=video_title)
|
|
||||||
|
|
||||||
# Look for embedded Wistia player
|
# Look for embedded Wistia player
|
||||||
match = re.search(
|
match = re.search(
|
||||||
@ -620,6 +646,11 @@ class GenericIE(InfoExtractor):
|
|||||||
if mobj is not None:
|
if mobj is not None:
|
||||||
return self.url_result(mobj.group('url'), 'VK')
|
return self.url_result(mobj.group('url'), 'VK')
|
||||||
|
|
||||||
|
# Look for embedded ivi player
|
||||||
|
mobj = re.search(r'<embed[^>]+?src=(["\'])(?P<url>https?://(?:www\.)?ivi\.ru/video/player.+?)\1', webpage)
|
||||||
|
if mobj is not None:
|
||||||
|
return self.url_result(mobj.group('url'), 'Ivi')
|
||||||
|
|
||||||
# Look for embedded Huffington Post player
|
# Look for embedded Huffington Post player
|
||||||
mobj = re.search(
|
mobj = re.search(
|
||||||
r'<iframe[^>]+?src=(["\'])(?P<url>https?://embed\.live\.huffingtonpost\.com/.+?)\1', webpage)
|
r'<iframe[^>]+?src=(["\'])(?P<url>https?://embed\.live\.huffingtonpost\.com/.+?)\1', webpage)
|
||||||
@ -637,10 +668,8 @@ class GenericIE(InfoExtractor):
|
|||||||
# Look for funnyordie embed
|
# Look for funnyordie embed
|
||||||
matches = re.findall(r'<iframe[^>]+?src="(https?://(?:www\.)?funnyordie\.com/embed/[^"]+)"', webpage)
|
matches = re.findall(r'<iframe[^>]+?src="(https?://(?:www\.)?funnyordie\.com/embed/[^"]+)"', webpage)
|
||||||
if matches:
|
if matches:
|
||||||
urlrs = [self.url_result(unescapeHTML(eurl), 'FunnyOrDie')
|
return _playlist_from_matches(
|
||||||
for eurl in matches]
|
matches, getter=unescapeHTML, ie='FunnyOrDie')
|
||||||
return self.playlist_result(
|
|
||||||
urlrs, playlist_id=video_id, playlist_title=video_title)
|
|
||||||
|
|
||||||
# Look for embedded RUTV player
|
# Look for embedded RUTV player
|
||||||
rutv_url = RUTVIE._extract_url(webpage)
|
rutv_url = RUTVIE._extract_url(webpage)
|
||||||
@ -695,6 +724,20 @@ class GenericIE(InfoExtractor):
|
|||||||
url = unescapeHTML(mobj.group('url'))
|
url = unescapeHTML(mobj.group('url'))
|
||||||
return self.url_result(url, ie='MTVServicesEmbedded')
|
return self.url_result(url, ie='MTVServicesEmbedded')
|
||||||
|
|
||||||
|
# Look for embedded yahoo player
|
||||||
|
mobj = re.search(
|
||||||
|
r'<iframe[^>]+?src=(["\'])(?P<url>https?://(?:screen|movies)\.yahoo\.com/.+?\.html\?format=embed)\1',
|
||||||
|
webpage)
|
||||||
|
if mobj is not None:
|
||||||
|
return self.url_result(mobj.group('url'), 'Yahoo')
|
||||||
|
|
||||||
|
# Look for embedded sbs.com.au player
|
||||||
|
mobj = re.search(
|
||||||
|
r'<iframe[^>]+?src=(["\'])(?P<url>https?://(?:www\.)sbs\.com\.au/ondemand/video/single/.+?)\1',
|
||||||
|
webpage)
|
||||||
|
if mobj is not None:
|
||||||
|
return self.url_result(mobj.group('url'), 'SBS')
|
||||||
|
|
||||||
# Start with something easy: JW Player in SWFObject
|
# Start with something easy: JW Player in SWFObject
|
||||||
found = re.findall(r'flashvars: [\'"](?:.*&)?file=(http[^\'"&]*)', webpage)
|
found = re.findall(r'flashvars: [\'"](?:.*&)?file=(http[^\'"&]*)', webpage)
|
||||||
if not found:
|
if not found:
|
||||||
|
58
youtube_dl/extractor/godtube.py
Normal file
58
youtube_dl/extractor/godtube.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
parse_duration,
|
||||||
|
parse_iso8601,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class GodTubeIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?godtube\.com/watch/\?v=(?P<id>[\da-zA-Z]+)'
|
||||||
|
_TESTS = [
|
||||||
|
{
|
||||||
|
'url': 'https://www.godtube.com/watch/?v=0C0CNNNU',
|
||||||
|
'md5': '77108c1e4ab58f48031101a1a2119789',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '0C0CNNNU',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Woman at the well.',
|
||||||
|
'duration': 159,
|
||||||
|
'timestamp': 1205712000,
|
||||||
|
'uploader': 'beverlybmusic',
|
||||||
|
'upload_date': '20080317',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
config = self._download_xml(
|
||||||
|
'http://www.godtube.com/resource/mediaplayer/%s.xml' % video_id.lower(),
|
||||||
|
video_id, 'Downloading player config XML')
|
||||||
|
|
||||||
|
video_url = config.find('.//file').text
|
||||||
|
uploader = config.find('.//author').text
|
||||||
|
timestamp = parse_iso8601(config.find('.//date').text)
|
||||||
|
duration = parse_duration(config.find('.//duration').text)
|
||||||
|
thumbnail = config.find('.//image').text
|
||||||
|
|
||||||
|
media = self._download_xml(
|
||||||
|
'http://www.godtube.com/media/xml/?v=%s' % video_id, video_id, 'Downloading media XML')
|
||||||
|
|
||||||
|
title = media.find('.//title').text
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'url': video_url,
|
||||||
|
'title': title,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'timestamp': timestamp,
|
||||||
|
'uploader': uploader,
|
||||||
|
'duration': duration,
|
||||||
|
}
|
@ -52,8 +52,7 @@ class GooglePlusIE(InfoExtractor):
|
|||||||
|
|
||||||
# Extract title
|
# Extract title
|
||||||
# Get the first line for title
|
# Get the first line for title
|
||||||
video_title = self._html_search_regex(r'<meta name\=\"Description\" content\=\"(.*?)[\n<"]',
|
video_title = self._og_search_description(webpage).splitlines()[0]
|
||||||
webpage, 'title', default='NA')
|
|
||||||
|
|
||||||
# Step 2, Simulate clicking the image box to launch video
|
# Step 2, Simulate clicking the image box to launch video
|
||||||
DOMAIN = 'https://plus.google.com/'
|
DOMAIN = 'https://plus.google.com/'
|
||||||
|
@ -12,7 +12,12 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class GorillaVidIE(InfoExtractor):
|
class GorillaVidIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?gorillavid\.in/(?:embed-)?(?P<id>[0-9a-zA-Z]+)(?:-[0-9]+x[0-9]+\.html)?'
|
IE_DESC = 'GorillaVid.in and daclips.in'
|
||||||
|
_VALID_URL = r'''(?x)
|
||||||
|
https?://(?P<host>(?:www\.)?
|
||||||
|
(?:daclips\.in|gorillavid\.in))/
|
||||||
|
(?:embed-)?(?P<id>[0-9a-zA-Z]+)(?:-[0-9]+x[0-9]+\.html)?
|
||||||
|
'''
|
||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://gorillavid.in/06y9juieqpmi',
|
'url': 'http://gorillavid.in/06y9juieqpmi',
|
||||||
@ -32,15 +37,22 @@ class GorillaVidIE(InfoExtractor):
|
|||||||
'title': 'Say something nice',
|
'title': 'Say something nice',
|
||||||
'thumbnail': 're:http://.*\.jpg',
|
'thumbnail': 're:http://.*\.jpg',
|
||||||
},
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://daclips.in/3rso4kdn6f9m',
|
||||||
|
'md5': '1ad8fd39bb976eeb66004d3a4895f106',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '3rso4kdn6f9m',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Micro Pig piglets ready on 16th July 2009',
|
||||||
|
'thumbnail': 're:http://.*\.jpg',
|
||||||
|
},
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
video_id = mobj.group('id')
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
url = 'http://gorillavid.in/%s' % video_id
|
webpage = self._download_webpage('http://%s/%s' % (mobj.group('host'), video_id), video_id)
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
|
||||||
|
|
||||||
fields = dict(re.findall(r'''(?x)<input\s+
|
fields = dict(re.findall(r'''(?x)<input\s+
|
||||||
type="hidden"\s+
|
type="hidden"\s+
|
||||||
|
73
youtube_dl/extractor/goshgay.py
Normal file
73
youtube_dl/extractor/goshgay.py
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
compat_urlparse,
|
||||||
|
str_to_int,
|
||||||
|
ExtractorError,
|
||||||
|
)
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
class GoshgayIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'^(?:https?://)www.goshgay.com/video(?P<id>\d+?)($|/)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.goshgay.com/video4116282',
|
||||||
|
'md5': '268b9f3c3229105c57859e166dd72b03',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '4116282',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'md5:089833a4790b5e103285a07337f245bf',
|
||||||
|
'thumbnail': 're:http://.*\.jpg',
|
||||||
|
'age_limit': 18,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
title = self._search_regex(r'class="video-title"><h1>(.+?)<', webpage, 'title')
|
||||||
|
|
||||||
|
player_config = self._search_regex(
|
||||||
|
r'(?s)jwplayer\("player"\)\.setup\(({.+?})\)', webpage, 'config settings')
|
||||||
|
player_vars = json.loads(player_config.replace("'", '"'))
|
||||||
|
width = str_to_int(player_vars.get('width'))
|
||||||
|
height = str_to_int(player_vars.get('height'))
|
||||||
|
config_uri = player_vars.get('config')
|
||||||
|
|
||||||
|
if config_uri is None:
|
||||||
|
raise ExtractorError('Missing config URI')
|
||||||
|
node = self._download_xml(config_uri, video_id, 'Downloading player config XML',
|
||||||
|
errnote='Unable to download XML')
|
||||||
|
if node is None:
|
||||||
|
raise ExtractorError('Missing config XML')
|
||||||
|
if node.tag != 'config':
|
||||||
|
raise ExtractorError('Missing config attribute')
|
||||||
|
fns = node.findall('file')
|
||||||
|
imgs = node.findall('image')
|
||||||
|
if len(fns) != 1:
|
||||||
|
raise ExtractorError('Missing media URI')
|
||||||
|
video_url = fns[0].text
|
||||||
|
if len(imgs) < 1:
|
||||||
|
thumbnail = None
|
||||||
|
else:
|
||||||
|
thumbnail = imgs[0].text
|
||||||
|
|
||||||
|
url_comp = compat_urlparse.urlparse(url)
|
||||||
|
ref = "%s://%s%s" % (url_comp[0], url_comp[1], url_comp[2])
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'url': video_url,
|
||||||
|
'title': title,
|
||||||
|
'width': width,
|
||||||
|
'height': height,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'http_referer': ref,
|
||||||
|
'age_limit': 18,
|
||||||
|
}
|
134
youtube_dl/extractor/howstuffworks.py
Normal file
134
youtube_dl/extractor/howstuffworks.py
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import find_xpath_attr
|
||||||
|
|
||||||
|
|
||||||
|
class HowStuffWorksIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://[\da-z-]+\.howstuffworks\.com/(?:[^/]+/)*\d+-(?P<id>.+?)-video\.htm'
|
||||||
|
_TESTS = [
|
||||||
|
{
|
||||||
|
'url': 'http://adventure.howstuffworks.com/5266-cool-jobs-iditarod-musher-video.htm',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '450221',
|
||||||
|
'display_id': 'cool-jobs-iditarod-musher',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Cool Jobs - Iditarod Musher',
|
||||||
|
'description': 'md5:82bb58438a88027b8186a1fccb365f90',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# md5 is not consistent
|
||||||
|
'skip_download': True
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://adventure.howstuffworks.com/39516-deadliest-catch-jakes-farewell-pots-video.htm',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '553470',
|
||||||
|
'display_id': 'deadliest-catch-jakes-farewell-pots',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Deadliest Catch: Jake\'s Farewell Pots',
|
||||||
|
'description': 'md5:9632c346d5e43ee238028c9cefd8dbbc',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# md5 is not consistent
|
||||||
|
'skip_download': True
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://entertainment.howstuffworks.com/arts/2706-sword-swallowing-1-by-dan-meyer-video.htm',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '440011',
|
||||||
|
'display_id': 'sword-swallowing-1-by-dan-meyer',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Sword Swallowing #1 by Dan Meyer',
|
||||||
|
'description': 'md5:b2409e88172913e2e7d3d1159b0ef735',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# md5 is not consistent
|
||||||
|
'skip_download': True
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
display_id = mobj.group('id')
|
||||||
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
|
||||||
|
content_id = self._search_regex(r'var siteSectionId="(\d+)";', webpage, 'content id')
|
||||||
|
|
||||||
|
mp4 = self._search_regex(
|
||||||
|
r'''(?xs)var\s+clip\s*=\s*{\s*
|
||||||
|
.+?\s*
|
||||||
|
content_id\s*:\s*%s\s*,\s*
|
||||||
|
.+?\s*
|
||||||
|
mp4\s*:\s*\[(.*?),?\]\s*
|
||||||
|
};\s*
|
||||||
|
videoData\.push\(clip\);''' % content_id,
|
||||||
|
webpage, 'mp4', fatal=False, default=None)
|
||||||
|
|
||||||
|
smil = self._download_xml(
|
||||||
|
'http://services.media.howstuffworks.com/videos/%s/smil-service.smil' % content_id,
|
||||||
|
content_id, 'Downloading video SMIL')
|
||||||
|
|
||||||
|
http_base = find_xpath_attr(
|
||||||
|
smil,
|
||||||
|
'./{0}head/{0}meta'.format('{http://www.w3.org/2001/SMIL20/Language}'),
|
||||||
|
'name',
|
||||||
|
'httpBase').get('content')
|
||||||
|
|
||||||
|
def random_string(str_len=0):
|
||||||
|
return ''.join([random.choice(string.ascii_uppercase) for _ in range(str_len)])
|
||||||
|
|
||||||
|
URL_SUFFIX = '?v=2.11.3&fp=LNX 11,2,202,356&r=%s&g=%s' % (random_string(5), random_string(12))
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
|
||||||
|
if mp4:
|
||||||
|
for video in json.loads('[%s]' % mp4):
|
||||||
|
bitrate = video['bitrate']
|
||||||
|
fmt = {
|
||||||
|
'url': video['src'].replace('http://pmd.video.howstuffworks.com', http_base) + URL_SUFFIX,
|
||||||
|
'format_id': bitrate,
|
||||||
|
}
|
||||||
|
m = re.search(r'(?P<vbr>\d+)[Kk]', bitrate)
|
||||||
|
if m:
|
||||||
|
fmt['vbr'] = int(m.group('vbr'))
|
||||||
|
formats.append(fmt)
|
||||||
|
else:
|
||||||
|
for video in smil.findall(
|
||||||
|
'.//{0}body/{0}switch/{0}video'.format('{http://www.w3.org/2001/SMIL20/Language}')):
|
||||||
|
vbr = int(video.attrib['system-bitrate']) / 1000
|
||||||
|
formats.append({
|
||||||
|
'url': '%s/%s%s' % (http_base, video.attrib['src'], URL_SUFFIX),
|
||||||
|
'format_id': '%dk' % vbr,
|
||||||
|
'vbr': vbr,
|
||||||
|
})
|
||||||
|
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
title = self._og_search_title(webpage)
|
||||||
|
TITLE_SUFFIX = ' : HowStuffWorks'
|
||||||
|
if title.endswith(TITLE_SUFFIX):
|
||||||
|
title = title[:-len(TITLE_SUFFIX)]
|
||||||
|
|
||||||
|
description = self._og_search_description(webpage)
|
||||||
|
thumbnail = self._og_search_thumbnail(webpage)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': content_id,
|
||||||
|
'display_id': display_id,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'formats': formats,
|
||||||
|
}
|
@ -14,7 +14,7 @@ from ..utils import (
|
|||||||
class IviIE(InfoExtractor):
|
class IviIE(InfoExtractor):
|
||||||
IE_DESC = 'ivi.ru'
|
IE_DESC = 'ivi.ru'
|
||||||
IE_NAME = 'ivi'
|
IE_NAME = 'ivi'
|
||||||
_VALID_URL = r'https?://(?:www\.)?ivi\.ru/watch(?:/(?P<compilationid>[^/]+))?/(?P<videoid>\d+)'
|
_VALID_URL = r'https?://(?:www\.)?ivi\.ru/(?:watch/(?:[^/]+/)?|video/player\?.*?videoId=)(?P<videoid>\d+)'
|
||||||
|
|
||||||
_TESTS = [
|
_TESTS = [
|
||||||
# Single movie
|
# Single movie
|
||||||
|
97
youtube_dl/extractor/izlesene.py
Normal file
97
youtube_dl/extractor/izlesene.py
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
get_element_by_id,
|
||||||
|
parse_iso8601,
|
||||||
|
determine_ext,
|
||||||
|
int_or_none,
|
||||||
|
str_to_int,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class IzleseneIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:(?:www|m)\.)?izlesene\.com/(?:video|embedplayer)/(?:[^/]+/)?(?P<id>[0-9]+)'
|
||||||
|
_STREAM_URL = 'http://panel.izlesene.com/api/streamurl/{id:}/{format:}'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.izlesene.com/video/sevincten-cildirtan-dogum-gunu-hediyesi/7599694',
|
||||||
|
'md5': '4384f9f0ea65086734b881085ee05ac2',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '7599694',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Sevinçten Çıldırtan Doğum Günü Hediyesi',
|
||||||
|
'description': 'Annesi oğluna doğum günü hediyesi olarak minecraft cd si alıyor, ve çocuk hunharca seviniyor',
|
||||||
|
'thumbnail': 're:^http://.*\.jpg',
|
||||||
|
'uploader_id': 'pelikzzle',
|
||||||
|
'timestamp': 1404298698,
|
||||||
|
'upload_date': '20140702',
|
||||||
|
'duration': 95.395,
|
||||||
|
'age_limit': 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
url = 'http://www.izlesene.com/video/%s' % video_id
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
title = self._og_search_title(webpage)
|
||||||
|
description = self._og_search_description(webpage)
|
||||||
|
thumbnail = self._og_search_thumbnail(webpage)
|
||||||
|
|
||||||
|
uploader = self._html_search_regex(
|
||||||
|
r"adduserUsername\s*=\s*'([^']+)';", webpage, 'uploader', fatal=False, default='')
|
||||||
|
timestamp = parse_iso8601(self._html_search_meta(
|
||||||
|
'uploadDate', webpage, 'upload date', fatal=False))
|
||||||
|
|
||||||
|
duration = int_or_none(self._html_search_regex(
|
||||||
|
r'"videoduration"\s*:\s*"([^"]+)"', webpage, 'duration', fatal=False))
|
||||||
|
if duration:
|
||||||
|
duration /= 1000.0
|
||||||
|
|
||||||
|
view_count = str_to_int(get_element_by_id('videoViewCount', webpage))
|
||||||
|
comment_count = self._html_search_regex(
|
||||||
|
r'comment_count\s*=\s*\'([^\']+)\';', webpage, 'uploader', fatal=False)
|
||||||
|
|
||||||
|
family_friendly = self._html_search_meta(
|
||||||
|
'isFamilyFriendly', webpage, 'age limit', fatal=False)
|
||||||
|
|
||||||
|
content_url = self._html_search_meta(
|
||||||
|
'contentURL', webpage, 'content URL', fatal=False)
|
||||||
|
ext = determine_ext(content_url, 'mp4')
|
||||||
|
|
||||||
|
# Might be empty for some videos.
|
||||||
|
qualities = self._html_search_regex(
|
||||||
|
r'"quality"\s*:\s*"([^"]+)"', webpage, 'qualities', fatal=False, default='')
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
for quality in qualities.split('|'):
|
||||||
|
json = self._download_json(
|
||||||
|
self._STREAM_URL.format(id=video_id, format=quality), video_id,
|
||||||
|
note='Getting video URL for "%s" quality' % quality,
|
||||||
|
errnote='Failed to get video URL for "%s" quality' % quality
|
||||||
|
)
|
||||||
|
formats.append({
|
||||||
|
'url': json.get('streamurl'),
|
||||||
|
'ext': ext,
|
||||||
|
'format_id': '%sp' % quality if quality else 'sd',
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'uploader_id': uploader,
|
||||||
|
'timestamp': timestamp,
|
||||||
|
'duration': duration,
|
||||||
|
'view_count': int_or_none(view_count),
|
||||||
|
'comment_count': int_or_none(comment_count),
|
||||||
|
'age_limit': 18 if family_friendly == 'False' else 0,
|
||||||
|
'formats': formats,
|
||||||
|
}
|
80
youtube_dl/extractor/jove.py
Normal file
80
youtube_dl/extractor/jove.py
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
ExtractorError,
|
||||||
|
unified_strdate
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class JoveIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?jove\.com/video/(?P<id>[0-9]+)'
|
||||||
|
_CHAPTERS_URL = 'http://www.jove.com/video-chapters?videoid={video_id:}'
|
||||||
|
_TESTS = [
|
||||||
|
{
|
||||||
|
'url': 'http://www.jove.com/video/2744/electrode-positioning-montage-transcranial-direct-current',
|
||||||
|
'md5': '93723888d82dbd6ba8b3d7d0cd65dd2b',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '2744',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Electrode Positioning and Montage in Transcranial Direct Current Stimulation',
|
||||||
|
'description': 'md5:015dd4509649c0908bc27f049e0262c6',
|
||||||
|
'thumbnail': 're:^https?://.*\.png$',
|
||||||
|
'upload_date': '20110523',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://www.jove.com/video/51796/culturing-caenorhabditis-elegans-axenic-liquid-media-creation',
|
||||||
|
'md5': '914aeb356f416811d911996434811beb',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '51796',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Culturing Caenorhabditis elegans in Axenic Liquid Media and Creation of Transgenic Worms by Microparticle Bombardment',
|
||||||
|
'description': 'md5:35ff029261900583970c4023b70f1dc9',
|
||||||
|
'thumbnail': 're:^https?://.*\.png$',
|
||||||
|
'upload_date': '20140802',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
chapters_id = self._html_search_regex(
|
||||||
|
r'/video-chapters\?videoid=([0-9]+)', webpage, 'chapters id')
|
||||||
|
|
||||||
|
chapters_xml = self._download_xml(
|
||||||
|
self._CHAPTERS_URL.format(video_id=chapters_id),
|
||||||
|
video_id, note='Downloading chapters XML',
|
||||||
|
errnote='Failed to download chapters XML')
|
||||||
|
|
||||||
|
video_url = chapters_xml.attrib.get('video')
|
||||||
|
if not video_url:
|
||||||
|
raise ExtractorError('Failed to get the video URL')
|
||||||
|
|
||||||
|
title = self._html_search_meta('citation_title', webpage, 'title')
|
||||||
|
thumbnail = self._og_search_thumbnail(webpage)
|
||||||
|
description = self._html_search_regex(
|
||||||
|
r'<div id="section_body_summary"><p class="jove_content">(.+?)</p>',
|
||||||
|
webpage, 'description', fatal=False)
|
||||||
|
publish_date = unified_strdate(self._html_search_meta(
|
||||||
|
'citation_publication_date', webpage, 'publish date', fatal=False))
|
||||||
|
comment_count = self._html_search_regex(
|
||||||
|
r'<meta name="num_comments" content="(\d+) Comments?"',
|
||||||
|
webpage, 'comment count', fatal=False)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'url': video_url,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'description': description,
|
||||||
|
'upload_date': publish_date,
|
||||||
|
'comment_count': comment_count,
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import itertools
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
@ -43,10 +44,11 @@ class JustinTVIE(InfoExtractor):
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Return count of items, list of *valid* items
|
# Return count of items, list of *valid* items
|
||||||
def _parse_page(self, url, video_id):
|
def _parse_page(self, url, video_id, counter):
|
||||||
info_json = self._download_webpage(url, video_id,
|
info_json = self._download_webpage(
|
||||||
'Downloading video info JSON',
|
url, video_id,
|
||||||
'unable to download video info JSON')
|
'Downloading video info JSON on page %d' % counter,
|
||||||
|
'Unable to download video info JSON %d' % counter)
|
||||||
|
|
||||||
response = json.loads(info_json)
|
response = json.loads(info_json)
|
||||||
if type(response) != list:
|
if type(response) != list:
|
||||||
@ -138,11 +140,10 @@ class JustinTVIE(InfoExtractor):
|
|||||||
entries = []
|
entries = []
|
||||||
offset = 0
|
offset = 0
|
||||||
limit = self._JUSTIN_PAGE_LIMIT
|
limit = self._JUSTIN_PAGE_LIMIT
|
||||||
while True:
|
for counter in itertools.count(1):
|
||||||
if paged:
|
|
||||||
self.report_download_page(video_id, offset)
|
|
||||||
page_url = api + ('?offset=%d&limit=%d' % (offset, limit))
|
page_url = api + ('?offset=%d&limit=%d' % (offset, limit))
|
||||||
page_count, page_info = self._parse_page(page_url, video_id)
|
page_count, page_info = self._parse_page(
|
||||||
|
page_url, video_id, counter)
|
||||||
entries.extend(page_info)
|
entries.extend(page_info)
|
||||||
if not paged or page_count != limit:
|
if not paged or page_count != limit:
|
||||||
break
|
break
|
||||||
|
@ -8,7 +8,7 @@ from .common import InfoExtractor
|
|||||||
|
|
||||||
class KickStarterIE(InfoExtractor):
|
class KickStarterIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://www\.kickstarter\.com/projects/(?P<id>[^/]*)/.*'
|
_VALID_URL = r'https?://www\.kickstarter\.com/projects/(?P<id>[^/]*)/.*'
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
'url': 'https://www.kickstarter.com/projects/1404461844/intersection-the-story-of-josh-grant?ref=home_location',
|
'url': 'https://www.kickstarter.com/projects/1404461844/intersection-the-story-of-josh-grant?ref=home_location',
|
||||||
'md5': 'c81addca81327ffa66c642b5d8b08cab',
|
'md5': 'c81addca81327ffa66c642b5d8b08cab',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@ -18,22 +18,45 @@ class KickStarterIE(InfoExtractor):
|
|||||||
'description': 'A unique motocross documentary that examines the '
|
'description': 'A unique motocross documentary that examines the '
|
||||||
'life and mind of one of sports most elite athletes: Josh Grant.',
|
'life and mind of one of sports most elite athletes: Josh Grant.',
|
||||||
},
|
},
|
||||||
}
|
}, {
|
||||||
|
'note': 'Embedded video (not using the native kickstarter video service)',
|
||||||
|
'url': 'https://www.kickstarter.com/projects/597507018/pebble-e-paper-watch-for-iphone-and-android/posts/659178',
|
||||||
|
'playlist': [
|
||||||
|
{
|
||||||
|
'info_dict': {
|
||||||
|
'id': '78704821',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'uploader_id': 'pebble',
|
||||||
|
'uploader': 'Pebble Technology',
|
||||||
|
'title': 'Pebble iOS Notifications',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
m = re.match(self._VALID_URL, url)
|
m = re.match(self._VALID_URL, url)
|
||||||
video_id = m.group('id')
|
video_id = m.group('id')
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
video_url = self._search_regex(r'data-video-url="(.*?)"',
|
title = self._html_search_regex(
|
||||||
webpage, 'video URL')
|
r'<title>\s*(.*?)(?:\s*— Kickstarter)?\s*</title>',
|
||||||
video_title = self._html_search_regex(r'<title>(.*?)</title>',
|
webpage, 'title')
|
||||||
webpage, 'title').rpartition('— Kickstarter')[0].strip()
|
video_url = self._search_regex(
|
||||||
|
r'data-video-url="(.*?)"',
|
||||||
|
webpage, 'video URL', default=None)
|
||||||
|
if video_url is None: # No native kickstarter, look for embedded videos
|
||||||
|
return {
|
||||||
|
'_type': 'url_transparent',
|
||||||
|
'ie_key': 'Generic',
|
||||||
|
'url': url,
|
||||||
|
'title': title,
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'url': video_url,
|
'url': video_url,
|
||||||
'title': video_title,
|
'title': title,
|
||||||
'description': self._og_search_description(webpage),
|
'description': self._og_search_description(webpage),
|
||||||
'thumbnail': self._og_search_thumbnail(webpage),
|
'thumbnail': self._og_search_thumbnail(webpage),
|
||||||
}
|
}
|
||||||
|
59
youtube_dl/extractor/krasview.py
Normal file
59
youtube_dl/extractor/krasview.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
# encoding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
int_or_none,
|
||||||
|
unescapeHTML,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class KrasViewIE(InfoExtractor):
|
||||||
|
IE_DESC = 'Красвью'
|
||||||
|
_VALID_URL = r'https?://krasview\.ru/video/(?P<id>\d+)'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://krasview.ru/video/512228',
|
||||||
|
'md5': '3b91003cf85fc5db277870c8ebd98eae',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '512228',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Снег, лёд, заносы',
|
||||||
|
'description': 'Снято в городе Нягань, в Ханты-Мансийском автономном округе.',
|
||||||
|
'duration': 27,
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
flashvars = json.loads(self._search_regex(
|
||||||
|
r'flashvars\s*:\s*({.+?})\s*}\);', webpage, 'flashvars'))
|
||||||
|
|
||||||
|
video_url = flashvars['url']
|
||||||
|
title = unescapeHTML(flashvars['title'])
|
||||||
|
description = unescapeHTML(flashvars.get('subtitle') or self._og_search_description(webpage, default=None))
|
||||||
|
thumbnail = flashvars['image']
|
||||||
|
duration = int(flashvars['duration'])
|
||||||
|
filesize = int(flashvars['size'])
|
||||||
|
width = int_or_none(self._og_search_property('video:width', webpage, 'video width'))
|
||||||
|
height = int_or_none(self._og_search_property('video:height', webpage, 'video height'))
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'url': video_url,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'duration': duration,
|
||||||
|
'filesize': filesize,
|
||||||
|
'width': width,
|
||||||
|
'height': height,
|
||||||
|
}
|
@ -5,10 +5,14 @@ import json
|
|||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
|
compat_str,
|
||||||
compat_urllib_parse_urlparse,
|
compat_urllib_parse_urlparse,
|
||||||
compat_urlparse,
|
compat_urlparse,
|
||||||
|
ExtractorError,
|
||||||
|
find_xpath_attr,
|
||||||
|
int_or_none,
|
||||||
|
orderedSet,
|
||||||
xpath_with_ns,
|
xpath_with_ns,
|
||||||
compat_str,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -23,18 +27,82 @@ class LivestreamIE(InfoExtractor):
|
|||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Live from Webster Hall NYC',
|
'title': 'Live from Webster Hall NYC',
|
||||||
'upload_date': '20121012',
|
'upload_date': '20121012',
|
||||||
|
'like_count': int,
|
||||||
|
'view_count': int,
|
||||||
|
'thumbnail': 're:^http://.*\.jpg$'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def _parse_smil(self, video_id, smil_url):
|
||||||
|
formats = []
|
||||||
|
_SWITCH_XPATH = (
|
||||||
|
'.//{http://www.w3.org/2001/SMIL20/Language}body/'
|
||||||
|
'{http://www.w3.org/2001/SMIL20/Language}switch')
|
||||||
|
smil_doc = self._download_xml(
|
||||||
|
smil_url, video_id,
|
||||||
|
note='Downloading SMIL information',
|
||||||
|
errnote='Unable to download SMIL information',
|
||||||
|
fatal=False)
|
||||||
|
if smil_doc is False: # Download failed
|
||||||
|
return formats
|
||||||
|
title_node = find_xpath_attr(
|
||||||
|
smil_doc, './/{http://www.w3.org/2001/SMIL20/Language}meta',
|
||||||
|
'name', 'title')
|
||||||
|
if title_node is None:
|
||||||
|
self.report_warning('Cannot find SMIL id')
|
||||||
|
switch_node = smil_doc.find(_SWITCH_XPATH)
|
||||||
|
else:
|
||||||
|
title_id = title_node.attrib['content']
|
||||||
|
switch_node = find_xpath_attr(
|
||||||
|
smil_doc, _SWITCH_XPATH, 'id', title_id)
|
||||||
|
if switch_node is None:
|
||||||
|
raise ExtractorError('Cannot find switch node')
|
||||||
|
video_nodes = switch_node.findall(
|
||||||
|
'{http://www.w3.org/2001/SMIL20/Language}video')
|
||||||
|
|
||||||
|
for vn in video_nodes:
|
||||||
|
tbr = int_or_none(vn.attrib.get('system-bitrate'))
|
||||||
|
furl = (
|
||||||
|
'http://livestream-f.akamaihd.net/%s?v=3.0.3&fp=WIN%%2014,0,0,145' %
|
||||||
|
(vn.attrib['src']))
|
||||||
|
if 'clipBegin' in vn.attrib:
|
||||||
|
furl += '&ssek=' + vn.attrib['clipBegin']
|
||||||
|
formats.append({
|
||||||
|
'url': furl,
|
||||||
|
'format_id': 'smil_%d' % tbr,
|
||||||
|
'ext': 'flv',
|
||||||
|
'tbr': tbr,
|
||||||
|
'preference': -1000,
|
||||||
|
})
|
||||||
|
return formats
|
||||||
|
|
||||||
def _extract_video_info(self, video_data):
|
def _extract_video_info(self, video_data):
|
||||||
video_url = video_data.get('progressive_url_hd') or video_data.get('progressive_url')
|
video_id = compat_str(video_data['id'])
|
||||||
|
|
||||||
|
FORMAT_KEYS = (
|
||||||
|
('sd', 'progressive_url'),
|
||||||
|
('hd', 'progressive_url_hd'),
|
||||||
|
)
|
||||||
|
formats = [{
|
||||||
|
'format_id': format_id,
|
||||||
|
'url': video_data[key],
|
||||||
|
'quality': i + 1,
|
||||||
|
} for i, (format_id, key) in enumerate(FORMAT_KEYS)
|
||||||
|
if video_data.get(key)]
|
||||||
|
|
||||||
|
smil_url = video_data.get('smil_url')
|
||||||
|
if smil_url:
|
||||||
|
formats.extend(self._parse_smil(video_id, smil_url))
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': compat_str(video_data['id']),
|
'id': video_id,
|
||||||
'url': video_url,
|
'formats': formats,
|
||||||
'ext': 'mp4',
|
|
||||||
'title': video_data['caption'],
|
'title': video_data['caption'],
|
||||||
'thumbnail': video_data['thumbnail_url'],
|
'thumbnail': video_data.get('thumbnail_url'),
|
||||||
'upload_date': video_data['updated_at'].replace('-', '')[:8],
|
'upload_date': video_data['updated_at'].replace('-', '')[:8],
|
||||||
|
'like_count': video_data.get('likes', {}).get('total'),
|
||||||
|
'view_count': video_data.get('views'),
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
@ -43,16 +111,28 @@ class LivestreamIE(InfoExtractor):
|
|||||||
event_name = mobj.group('event_name')
|
event_name = mobj.group('event_name')
|
||||||
webpage = self._download_webpage(url, video_id or event_name)
|
webpage = self._download_webpage(url, video_id or event_name)
|
||||||
|
|
||||||
if video_id is None:
|
og_video = self._og_search_video_url(webpage, 'player url', fatal=False, default=None)
|
||||||
# This is an event page:
|
if og_video is None:
|
||||||
config_json = self._search_regex(
|
config_json = self._search_regex(
|
||||||
r'window.config = ({.*?});', webpage, 'window config')
|
r'window.config = ({.*?});', webpage, 'window config')
|
||||||
info = json.loads(config_json)['event']
|
info = json.loads(config_json)['event']
|
||||||
|
|
||||||
|
def is_relevant(vdata, vid):
|
||||||
|
result = vdata['type'] == 'video'
|
||||||
|
if video_id is not None:
|
||||||
|
result = result and compat_str(vdata['data']['id']) == vid
|
||||||
|
return result
|
||||||
|
|
||||||
videos = [self._extract_video_info(video_data['data'])
|
videos = [self._extract_video_info(video_data['data'])
|
||||||
for video_data in info['feed']['data'] if video_data['type'] == 'video']
|
for video_data in info['feed']['data']
|
||||||
return self.playlist_result(videos, info['id'], info['full_name'])
|
if is_relevant(video_data, video_id)]
|
||||||
|
if video_id is None:
|
||||||
|
# This is an event page:
|
||||||
|
return self.playlist_result(videos, info['id'], info['full_name'])
|
||||||
|
else:
|
||||||
|
if videos:
|
||||||
|
return videos[0]
|
||||||
else:
|
else:
|
||||||
og_video = self._og_search_video_url(webpage, 'player url')
|
|
||||||
query_str = compat_urllib_parse_urlparse(og_video).query
|
query_str = compat_urllib_parse_urlparse(og_video).query
|
||||||
query = compat_urlparse.parse_qs(query_str)
|
query = compat_urlparse.parse_qs(query_str)
|
||||||
api_url = query['play_url'][0].replace('.smil', '')
|
api_url = query['play_url'][0].replace('.smil', '')
|
||||||
@ -64,7 +144,10 @@ class LivestreamIE(InfoExtractor):
|
|||||||
# The original version of Livestream uses a different system
|
# The original version of Livestream uses a different system
|
||||||
class LivestreamOriginalIE(InfoExtractor):
|
class LivestreamOriginalIE(InfoExtractor):
|
||||||
IE_NAME = 'livestream:original'
|
IE_NAME = 'livestream:original'
|
||||||
_VALID_URL = r'https?://www\.livestream\.com/(?P<user>[^/]+)/video\?.*?clipId=(?P<id>.*?)(&|$)'
|
_VALID_URL = r'''(?x)https?://www\.livestream\.com/
|
||||||
|
(?P<user>[^/]+)/(?P<type>video|folder)
|
||||||
|
(?:\?.*?Id=|/)(?P<id>.*?)(&|$)
|
||||||
|
'''
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.livestream.com/dealbook/video?clipId=pla_8aa4a3f1-ba15-46a4-893b-902210e138fb',
|
'url': 'http://www.livestream.com/dealbook/video?clipId=pla_8aa4a3f1-ba15-46a4-893b-902210e138fb',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@ -78,10 +161,7 @@ class LivestreamOriginalIE(InfoExtractor):
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _extract_video(self, user, video_id):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
|
||||||
video_id = mobj.group('id')
|
|
||||||
user = mobj.group('user')
|
|
||||||
api_url = 'http://x{0}x.api.channel.livestream.com/2.0/clipdetails?extendedInfo=true&id={1}'.format(user, video_id)
|
api_url = 'http://x{0}x.api.channel.livestream.com/2.0/clipdetails?extendedInfo=true&id={1}'.format(user, video_id)
|
||||||
|
|
||||||
info = self._download_xml(api_url, video_id)
|
info = self._download_xml(api_url, video_id)
|
||||||
@ -99,3 +179,44 @@ class LivestreamOriginalIE(InfoExtractor):
|
|||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'thumbnail': thumbnail_url,
|
'thumbnail': thumbnail_url,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def _extract_folder(self, url, folder_id):
|
||||||
|
webpage = self._download_webpage(url, folder_id)
|
||||||
|
urls = orderedSet(re.findall(r'<a href="(https?://livestre\.am/.*?)"', webpage))
|
||||||
|
|
||||||
|
return {
|
||||||
|
'_type': 'playlist',
|
||||||
|
'id': folder_id,
|
||||||
|
'entries': [{
|
||||||
|
'_type': 'url',
|
||||||
|
'url': video_url,
|
||||||
|
} for video_url in urls],
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
id = mobj.group('id')
|
||||||
|
user = mobj.group('user')
|
||||||
|
url_type = mobj.group('type')
|
||||||
|
if url_type == 'folder':
|
||||||
|
return self._extract_folder(url, id)
|
||||||
|
else:
|
||||||
|
return self._extract_video(user, id)
|
||||||
|
|
||||||
|
|
||||||
|
# The server doesn't support HEAD request, the generic extractor can't detect
|
||||||
|
# the redirection
|
||||||
|
class LivestreamShortenerIE(InfoExtractor):
|
||||||
|
IE_NAME = 'livestream:shortener'
|
||||||
|
IE_DESC = False # Do not list
|
||||||
|
_VALID_URL = r'https?://livestre\.am/(?P<id>.+)'
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
id = mobj.group('id')
|
||||||
|
webpage = self._download_webpage(url, id)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'_type': 'url',
|
||||||
|
'url': self._og_search_url(webpage),
|
||||||
|
}
|
||||||
|
@ -9,6 +9,7 @@ from ..utils import (
|
|||||||
compat_urllib_request,
|
compat_urllib_request,
|
||||||
determine_ext,
|
determine_ext,
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
|
int_or_none,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -83,6 +84,21 @@ class MetacafeIE(InfoExtractor):
|
|||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
# Movieclips.com video
|
||||||
|
{
|
||||||
|
'url': 'http://www.metacafe.com/watch/mv-Wy7ZU/my_week_with_marilyn_do_you_love_me/',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'mv-Wy7ZU',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'My Week with Marilyn - Do You Love Me?',
|
||||||
|
'description': 'From the movie My Week with Marilyn - Colin (Eddie Redmayne) professes his love to Marilyn (Michelle Williams) and gets her to promise to return to set and finish the movie.',
|
||||||
|
'uploader': 'movie_trailers',
|
||||||
|
'duration': 176,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': 'requires rtmpdump',
|
||||||
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
def report_disclaimer(self):
|
def report_disclaimer(self):
|
||||||
@ -134,6 +150,7 @@ class MetacafeIE(InfoExtractor):
|
|||||||
|
|
||||||
# Extract URL, uploader and title from webpage
|
# Extract URL, uploader and title from webpage
|
||||||
self.report_extraction(video_id)
|
self.report_extraction(video_id)
|
||||||
|
video_url = None
|
||||||
mobj = re.search(r'(?m)&mediaURL=([^&]+)', webpage)
|
mobj = re.search(r'(?m)&mediaURL=([^&]+)', webpage)
|
||||||
if mobj is not None:
|
if mobj is not None:
|
||||||
mediaURL = compat_urllib_parse.unquote(mobj.group(1))
|
mediaURL = compat_urllib_parse.unquote(mobj.group(1))
|
||||||
@ -146,16 +163,17 @@ class MetacafeIE(InfoExtractor):
|
|||||||
else:
|
else:
|
||||||
gdaKey = mobj.group(1)
|
gdaKey = mobj.group(1)
|
||||||
video_url = '%s?__gda__=%s' % (mediaURL, gdaKey)
|
video_url = '%s?__gda__=%s' % (mediaURL, gdaKey)
|
||||||
else:
|
if video_url is None:
|
||||||
mobj = re.search(r'<video src="([^"]+)"', webpage)
|
mobj = re.search(r'<video src="([^"]+)"', webpage)
|
||||||
if mobj:
|
if mobj:
|
||||||
video_url = mobj.group(1)
|
video_url = mobj.group(1)
|
||||||
video_ext = 'mp4'
|
video_ext = 'mp4'
|
||||||
else:
|
if video_url is None:
|
||||||
mobj = re.search(r' name="flashvars" value="(.*?)"', webpage)
|
flashvars = self._search_regex(
|
||||||
if mobj is None:
|
r' name="flashvars" value="(.*?)"', webpage, 'flashvars',
|
||||||
raise ExtractorError('Unable to extract media URL')
|
default=None)
|
||||||
vardict = compat_parse_qs(mobj.group(1))
|
if flashvars:
|
||||||
|
vardict = compat_parse_qs(flashvars)
|
||||||
if 'mediaData' not in vardict:
|
if 'mediaData' not in vardict:
|
||||||
raise ExtractorError('Unable to extract media URL')
|
raise ExtractorError('Unable to extract media URL')
|
||||||
mobj = re.search(
|
mobj = re.search(
|
||||||
@ -165,26 +183,68 @@ class MetacafeIE(InfoExtractor):
|
|||||||
mediaURL = mobj.group('mediaURL').replace('\\/', '/')
|
mediaURL = mobj.group('mediaURL').replace('\\/', '/')
|
||||||
video_url = '%s?__gda__=%s' % (mediaURL, mobj.group('key'))
|
video_url = '%s?__gda__=%s' % (mediaURL, mobj.group('key'))
|
||||||
video_ext = determine_ext(video_url)
|
video_ext = determine_ext(video_url)
|
||||||
|
if video_url is None:
|
||||||
|
player_url = self._search_regex(
|
||||||
|
r"swfobject\.embedSWF\('([^']+)'",
|
||||||
|
webpage, 'config URL', default=None)
|
||||||
|
if player_url:
|
||||||
|
config_url = self._search_regex(
|
||||||
|
r'config=(.+)$', player_url, 'config URL')
|
||||||
|
config_doc = self._download_xml(
|
||||||
|
config_url, video_id,
|
||||||
|
note='Downloading video config')
|
||||||
|
smil_url = config_doc.find('.//properties').attrib['smil_file']
|
||||||
|
smil_doc = self._download_xml(
|
||||||
|
smil_url, video_id,
|
||||||
|
note='Downloading SMIL document')
|
||||||
|
base_url = smil_doc.find('./head/meta').attrib['base']
|
||||||
|
video_url = []
|
||||||
|
for vn in smil_doc.findall('.//video'):
|
||||||
|
br = int(vn.attrib['system-bitrate'])
|
||||||
|
play_path = vn.attrib['src']
|
||||||
|
video_url.append({
|
||||||
|
'format_id': 'smil-%d' % br,
|
||||||
|
'url': base_url,
|
||||||
|
'play_path': play_path,
|
||||||
|
'page_url': url,
|
||||||
|
'player_url': player_url,
|
||||||
|
'ext': play_path.partition(':')[0],
|
||||||
|
})
|
||||||
|
|
||||||
video_title = self._html_search_regex(r'(?im)<title>(.*) - Video</title>', webpage, 'title')
|
if video_url is None:
|
||||||
|
raise ExtractorError('Unsupported video type')
|
||||||
|
|
||||||
|
video_title = self._html_search_regex(
|
||||||
|
r'(?im)<title>(.*) - Video</title>', webpage, 'title')
|
||||||
description = self._og_search_description(webpage)
|
description = self._og_search_description(webpage)
|
||||||
thumbnail = self._og_search_thumbnail(webpage)
|
thumbnail = self._og_search_thumbnail(webpage)
|
||||||
video_uploader = self._html_search_regex(
|
video_uploader = self._html_search_regex(
|
||||||
r'submitter=(.*?);|googletag\.pubads\(\)\.setTargeting\("(?:channel|submiter)","([^"]+)"\);',
|
r'submitter=(.*?);|googletag\.pubads\(\)\.setTargeting\("(?:channel|submiter)","([^"]+)"\);',
|
||||||
webpage, 'uploader nickname', fatal=False)
|
webpage, 'uploader nickname', fatal=False)
|
||||||
|
duration = int_or_none(
|
||||||
|
self._html_search_meta('video:duration', webpage))
|
||||||
|
|
||||||
if re.search(r'"contentRating":"restricted"', webpage) is not None:
|
age_limit = (
|
||||||
age_limit = 18
|
18
|
||||||
|
if re.search(r'"contentRating":"restricted"', webpage)
|
||||||
|
else 0)
|
||||||
|
|
||||||
|
if isinstance(video_url, list):
|
||||||
|
formats = video_url
|
||||||
else:
|
else:
|
||||||
age_limit = 0
|
formats = [{
|
||||||
|
'url': video_url,
|
||||||
|
'ext': video_ext,
|
||||||
|
}]
|
||||||
|
|
||||||
|
self._sort_formats(formats)
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'url': video_url,
|
|
||||||
'description': description,
|
'description': description,
|
||||||
'uploader': video_uploader,
|
'uploader': video_uploader,
|
||||||
'title': video_title,
|
'title': video_title,
|
||||||
'thumbnail':thumbnail,
|
'thumbnail': thumbnail,
|
||||||
'ext': video_ext,
|
|
||||||
'age_limit': age_limit,
|
'age_limit': age_limit,
|
||||||
|
'formats': formats,
|
||||||
|
'duration': duration,
|
||||||
}
|
}
|
||||||
|
60
youtube_dl/extractor/mitele.py
Normal file
60
youtube_dl/extractor/mitele.py
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
compat_urllib_parse,
|
||||||
|
get_element_by_attribute,
|
||||||
|
parse_duration,
|
||||||
|
strip_jsonp,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MiTeleIE(InfoExtractor):
|
||||||
|
IE_NAME = 'mitele.es'
|
||||||
|
_VALID_URL = r'http://www\.mitele\.es/[^/]+/[^/]+/[^/]+/(?P<episode>[^/]+)/'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.mitele.es/programas-tv/diario-de/la-redaccion/programa-144/',
|
||||||
|
'md5': '6a75fe9d0d3275bead0cb683c616fddb',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '0fce117d',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Programa 144 - Tor, la web invisible',
|
||||||
|
'description': 'md5:3b6fce7eaa41b2d97358726378d9369f',
|
||||||
|
'display_id': 'programa-144',
|
||||||
|
'duration': 2913,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
episode = mobj.group('episode')
|
||||||
|
webpage = self._download_webpage(url, episode)
|
||||||
|
embed_data_json = self._search_regex(
|
||||||
|
r'MSV\.embedData\[.*?\]\s*=\s*({.*?});', webpage, 'embed data',
|
||||||
|
flags=re.DOTALL
|
||||||
|
).replace('\'', '"')
|
||||||
|
embed_data = json.loads(embed_data_json)
|
||||||
|
|
||||||
|
info_url = embed_data['flashvars']['host']
|
||||||
|
info_el = self._download_xml(info_url, episode).find('./video/info')
|
||||||
|
|
||||||
|
video_link = info_el.find('videoUrl/link').text
|
||||||
|
token_query = compat_urllib_parse.urlencode({'id': video_link})
|
||||||
|
token_info = self._download_json(
|
||||||
|
'http://token.mitele.es/?' + token_query, episode,
|
||||||
|
transform_source=strip_jsonp
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': embed_data['videoId'],
|
||||||
|
'display_id': episode,
|
||||||
|
'title': info_el.find('title').text,
|
||||||
|
'url': token_info['tokenizedUrl'],
|
||||||
|
'description': get_element_by_attribute('class', 'text', webpage),
|
||||||
|
'thumbnail': info_el.find('thumb').text,
|
||||||
|
'duration': parse_duration(info_el.find('duration').text),
|
||||||
|
}
|
116
youtube_dl/extractor/mlb.py
Normal file
116
youtube_dl/extractor/mlb.py
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
parse_duration,
|
||||||
|
parse_iso8601,
|
||||||
|
find_xpath_attr,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MLBIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://m\.mlb\.com/(?:.*?/)?video/(?:topic/[\da-z_-]+/)?v(?P<id>n?\d+)'
|
||||||
|
_TESTS = [
|
||||||
|
{
|
||||||
|
'url': 'http://m.mlb.com/sea/video/topic/51231442/v34698933/nymsea-ackley-robs-a-home-run-with-an-amazing-catch/?c_id=sea',
|
||||||
|
'md5': 'ff56a598c2cf411a9a38a69709e97079',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '34698933',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': "Ackley's spectacular catch",
|
||||||
|
'description': 'md5:7f5a981eb4f3cbc8daf2aeffa2215bf0',
|
||||||
|
'duration': 66,
|
||||||
|
'timestamp': 1405980600,
|
||||||
|
'upload_date': '20140721',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://m.mlb.com/video/topic/81536970/v34496663/mianym-stanton-practices-for-the-home-run-derby',
|
||||||
|
'md5': 'd9c022c10d21f849f49c05ae12a8a7e9',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '34496663',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Stanton prepares for Derby',
|
||||||
|
'description': 'md5:d00ce1e5fd9c9069e9c13ab4faedfa57',
|
||||||
|
'duration': 46,
|
||||||
|
'timestamp': 1405105800,
|
||||||
|
'upload_date': '20140711',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://m.mlb.com/video/topic/vtp_hrd_sponsor/v34578115/hrd-cespedes-wins-2014-gillette-home-run-derby',
|
||||||
|
'md5': '0e6e73d509321e142409b695eadd541f',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '34578115',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Cespedes repeats as Derby champ',
|
||||||
|
'description': 'md5:08df253ce265d4cf6fb09f581fafad07',
|
||||||
|
'duration': 488,
|
||||||
|
'timestamp': 1405399936,
|
||||||
|
'upload_date': '20140715',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://m.mlb.com/video/v34577915/bautista-on-derby-captaining-duties-his-performance',
|
||||||
|
'md5': 'b8fd237347b844365d74ea61d4245967',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '34577915',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Bautista on Home Run Derby',
|
||||||
|
'description': 'md5:b80b34031143d0986dddc64a8839f0fb',
|
||||||
|
'duration': 52,
|
||||||
|
'timestamp': 1405390722,
|
||||||
|
'upload_date': '20140715',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
detail = self._download_xml(
|
||||||
|
'http://m.mlb.com/gen/multimedia/detail/%s/%s/%s/%s.xml'
|
||||||
|
% (video_id[-3], video_id[-2], video_id[-1], video_id), video_id)
|
||||||
|
|
||||||
|
title = detail.find('./headline').text
|
||||||
|
description = detail.find('./big-blurb').text
|
||||||
|
duration = parse_duration(detail.find('./duration').text)
|
||||||
|
timestamp = parse_iso8601(detail.attrib['date'][:-5])
|
||||||
|
|
||||||
|
thumbnail = find_xpath_attr(
|
||||||
|
detail, './thumbnailScenarios/thumbnailScenario', 'type', '45').text
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
for media_url in detail.findall('./url'):
|
||||||
|
playback_scenario = media_url.attrib['playback_scenario']
|
||||||
|
fmt = {
|
||||||
|
'url': media_url.text,
|
||||||
|
'format_id': playback_scenario,
|
||||||
|
}
|
||||||
|
m = re.search(r'(?P<vbr>\d+)K_(?P<width>\d+)X(?P<height>\d+)', playback_scenario)
|
||||||
|
if m:
|
||||||
|
fmt.update({
|
||||||
|
'vbr': int(m.group('vbr')) * 1000,
|
||||||
|
'width': int(m.group('width')),
|
||||||
|
'height': int(m.group('height')),
|
||||||
|
})
|
||||||
|
formats.append(fmt)
|
||||||
|
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'duration': duration,
|
||||||
|
'timestamp': timestamp,
|
||||||
|
'formats': formats,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
}
|
58
youtube_dl/extractor/mojvideo.py
Normal file
58
youtube_dl/extractor/mojvideo.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
ExtractorError,
|
||||||
|
parse_duration,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MojvideoIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?mojvideo\.com/video-(?P<display_id>[^/]+)/(?P<id>[a-f0-9]+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.mojvideo.com/video-v-avtu-pred-mano-rdecelaska-alfi-nipic/3d1ed4497707730b2906',
|
||||||
|
'md5': 'f7fd662cc8ce2be107b0d4f2c0483ae7',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '3d1ed4497707730b2906',
|
||||||
|
'display_id': 'v-avtu-pred-mano-rdecelaska-alfi-nipic',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'V avtu pred mano rdečelaska - Alfi Nipič',
|
||||||
|
'thumbnail': 're:^http://.*\.jpg$',
|
||||||
|
'duration': 242,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
display_id = mobj.group('display_id')
|
||||||
|
|
||||||
|
# XML is malformed
|
||||||
|
playerapi = self._download_webpage(
|
||||||
|
'http://www.mojvideo.com/playerapi.php?v=%s&t=1' % video_id, display_id)
|
||||||
|
|
||||||
|
if '<error>true</error>' in playerapi:
|
||||||
|
error_desc = self._html_search_regex(
|
||||||
|
r'<errordesc>([^<]*)</errordesc>', playerapi, 'error description', fatal=False)
|
||||||
|
raise ExtractorError('%s said: %s' % (self.IE_NAME, error_desc), expected=True)
|
||||||
|
|
||||||
|
title = self._html_search_regex(
|
||||||
|
r'<title>([^<]+)</title>', playerapi, 'title')
|
||||||
|
video_url = self._html_search_regex(
|
||||||
|
r'<file>([^<]+)</file>', playerapi, 'video URL')
|
||||||
|
thumbnail = self._html_search_regex(
|
||||||
|
r'<preview>([^<]+)</preview>', playerapi, 'thumbnail', fatal=False)
|
||||||
|
duration = parse_duration(self._html_search_regex(
|
||||||
|
r'<duration>([^<]+)</duration>', playerapi, 'duration', fatal=False))
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'display_id': display_id,
|
||||||
|
'url': video_url,
|
||||||
|
'title': title,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'duration': duration,
|
||||||
|
}
|
87
youtube_dl/extractor/motherless.py
Normal file
87
youtube_dl/extractor/motherless.py
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
int_or_none,
|
||||||
|
unified_strdate,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MotherlessIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'http://(?:www\.)?motherless\.com/(?P<id>[A-Z0-9]+)'
|
||||||
|
_TESTS = [
|
||||||
|
{
|
||||||
|
'url': 'http://motherless.com/AC3FFE1',
|
||||||
|
'md5': '5527fef81d2e529215dad3c2d744a7d9',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'AC3FFE1',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Fucked in the ass while playing PS3',
|
||||||
|
'categories': ['Gaming', 'anal', 'reluctant', 'rough', 'Wife'],
|
||||||
|
'upload_date': '20100913',
|
||||||
|
'uploader_id': 'famouslyfuckedup',
|
||||||
|
'thumbnail': 're:http://.*\.jpg',
|
||||||
|
'age_limit': 18,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://motherless.com/532291B',
|
||||||
|
'md5': 'bc59a6b47d1f958e61fbd38a4d31b131',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '532291B',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Amazing girl playing the omegle game, PERFECT!',
|
||||||
|
'categories': ['Amateur', 'webcam', 'omegle', 'pink', 'young', 'masturbate', 'teen', 'game', 'hairy'],
|
||||||
|
'upload_date': '20140622',
|
||||||
|
'uploader_id': 'Sulivana7x',
|
||||||
|
'thumbnail': 're:http://.*\.jpg',
|
||||||
|
'age_limit': 18,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
def _real_extract(self,url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
title = self._html_search_regex(r'id="view-upload-title">\s+([^<]+)<', webpage, 'title')
|
||||||
|
|
||||||
|
video_url = self._html_search_regex(r'setup\(\{\s+"file".+: "([^"]+)",', webpage, 'video_url')
|
||||||
|
age_limit = self._rta_search(webpage)
|
||||||
|
|
||||||
|
view_count = self._html_search_regex(r'<strong>Views</strong>\s+([^<]+)<', webpage, 'view_count')
|
||||||
|
|
||||||
|
upload_date = self._html_search_regex(r'<strong>Uploaded</strong>\s+([^<]+)<', webpage, 'upload_date')
|
||||||
|
if 'Ago' in upload_date:
|
||||||
|
days = int(re.search(r'([0-9]+)', upload_date).group(1))
|
||||||
|
upload_date = (datetime.datetime.now() - datetime.timedelta(days=days)).strftime('%Y%m%d')
|
||||||
|
else:
|
||||||
|
upload_date = unified_strdate(upload_date)
|
||||||
|
|
||||||
|
like_count = self._html_search_regex(r'<strong>Favorited</strong>\s+([^<]+)<', webpage, 'like_count')
|
||||||
|
|
||||||
|
comment_count = webpage.count('class="media-comment-contents"')
|
||||||
|
uploader_id = self._html_search_regex(r'"thumb-member-username">\s+<a href="/m/([^"]+)"', webpage, 'uploader_id')
|
||||||
|
|
||||||
|
categories = self._html_search_meta('keywords', webpage)
|
||||||
|
if categories:
|
||||||
|
categories = [cat.strip() for cat in categories.split(',')]
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'upload_date': upload_date,
|
||||||
|
'uploader_id': uploader_id,
|
||||||
|
'thumbnail': self._og_search_thumbnail(webpage),
|
||||||
|
'categories': categories,
|
||||||
|
'view_count': int_or_none(view_count.replace(',', '')),
|
||||||
|
'like_count': int_or_none(like_count.replace(',', '')),
|
||||||
|
'comment_count': comment_count,
|
||||||
|
'age_limit': age_limit,
|
||||||
|
'url': video_url,
|
||||||
|
}
|
78
youtube_dl/extractor/movieclips.py
Normal file
78
youtube_dl/extractor/movieclips.py
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
ExtractorError,
|
||||||
|
compat_str,
|
||||||
|
clean_html,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MovieClipsIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://movieclips\.com/(?P<id>[\da-zA-Z]+)(?:-(?P<display_id>[\da-z-]+))?'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://movieclips.com/Wy7ZU-my-week-with-marilyn-movie-do-you-love-me/',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'Wy7ZU',
|
||||||
|
'display_id': 'my-week-with-marilyn-movie-do-you-love-me',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'My Week with Marilyn - Do You Love Me?',
|
||||||
|
'description': 'md5:e86795bd332fe3cff461e7c8dc542acb',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# rtmp download
|
||||||
|
'skip_download': True,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
display_id = mobj.group('display_id')
|
||||||
|
show_id = display_id or video_id
|
||||||
|
|
||||||
|
config = self._download_xml(
|
||||||
|
'http://config.movieclips.com/player/config/%s' % video_id,
|
||||||
|
show_id, 'Downloading player config')
|
||||||
|
|
||||||
|
if config.find('./country-region').text == 'false':
|
||||||
|
raise ExtractorError(
|
||||||
|
'%s said: %s' % (self.IE_NAME, config.find('./region_alert').text), expected=True)
|
||||||
|
|
||||||
|
properties = config.find('./video/properties')
|
||||||
|
smil_file = properties.attrib['smil_file']
|
||||||
|
|
||||||
|
smil = self._download_xml(smil_file, show_id, 'Downloading SMIL')
|
||||||
|
base_url = smil.find('./head/meta').attrib['base']
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
for video in smil.findall('./body/switch/video'):
|
||||||
|
vbr = int(video.attrib['system-bitrate']) / 1000
|
||||||
|
src = video.attrib['src']
|
||||||
|
formats.append({
|
||||||
|
'url': base_url,
|
||||||
|
'play_path': src,
|
||||||
|
'ext': src.split(':')[0],
|
||||||
|
'vbr': vbr,
|
||||||
|
'format_id': '%dk' % vbr,
|
||||||
|
})
|
||||||
|
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
title = '%s - %s' % (properties.attrib['clip_movie_title'], properties.attrib['clip_title'])
|
||||||
|
description = clean_html(compat_str(properties.attrib['clip_description']))
|
||||||
|
thumbnail = properties.attrib['image']
|
||||||
|
categories = properties.attrib['clip_categories'].split(',')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'display_id': display_id,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'categories': categories,
|
||||||
|
'formats': formats,
|
||||||
|
}
|
@ -28,7 +28,7 @@ class MporaIE(InfoExtractor):
|
|||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
data_json = self._search_regex(
|
data_json = self._search_regex(
|
||||||
r"new FM\.Player\('[^']+',\s*(\{.*?)\);\n", webpage, 'json')
|
r"new FM\.Player\('[^']+',\s*(\{.*?)\).player;", webpage, 'json')
|
||||||
|
|
||||||
data = json.loads(data_json)
|
data = json.loads(data_json)
|
||||||
|
|
||||||
|
@ -158,6 +158,9 @@ class MTVServicesInfoExtractor(InfoExtractor):
|
|||||||
if mgid.endswith('.swf'):
|
if mgid.endswith('.swf'):
|
||||||
mgid = mgid[:-4]
|
mgid = mgid[:-4]
|
||||||
except RegexNotFoundError:
|
except RegexNotFoundError:
|
||||||
|
mgid = None
|
||||||
|
|
||||||
|
if mgid is None or ':' not in mgid:
|
||||||
mgid = self._search_regex(
|
mgid = self._search_regex(
|
||||||
[r'data-mgid="(.*?)"', r'swfobject.embedSWF\(".*?(mgid:.*?)"'],
|
[r'data-mgid="(.*?)"', r'swfobject.embedSWF\(".*?(mgid:.*?)"'],
|
||||||
webpage, u'mgid')
|
webpage, u'mgid')
|
||||||
|
@ -4,7 +4,11 @@ import re
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import find_xpath_attr, compat_str
|
from ..utils import (
|
||||||
|
compat_str,
|
||||||
|
ExtractorError,
|
||||||
|
find_xpath_attr,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class NBCIE(InfoExtractor):
|
class NBCIE(InfoExtractor):
|
||||||
@ -85,11 +89,25 @@ class NBCNewsIE(InfoExtractor):
|
|||||||
flags=re.MULTILINE)
|
flags=re.MULTILINE)
|
||||||
bootstrap = json.loads(bootstrap_json)
|
bootstrap = json.loads(bootstrap_json)
|
||||||
info = bootstrap['results'][0]['video']
|
info = bootstrap['results'][0]['video']
|
||||||
playlist_url = info['fallbackPlaylistUrl'] + '?form=MPXNBCNewsAPI'
|
|
||||||
mpxid = info['mpxId']
|
mpxid = info['mpxId']
|
||||||
all_videos = self._download_json(playlist_url, title)['videos']
|
|
||||||
# The response contains additional videos
|
base_urls = [
|
||||||
info = next(v for v in all_videos if v['mpxId'] == mpxid)
|
info['fallbackPlaylistUrl'],
|
||||||
|
info['associatedPlaylistUrl'],
|
||||||
|
]
|
||||||
|
|
||||||
|
for base_url in base_urls:
|
||||||
|
playlist_url = base_url + '?form=MPXNBCNewsAPI'
|
||||||
|
all_videos = self._download_json(playlist_url, title)['videos']
|
||||||
|
|
||||||
|
try:
|
||||||
|
info = next(v for v in all_videos if v['mpxId'] == mpxid)
|
||||||
|
break
|
||||||
|
except StopIteration:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if info is None:
|
||||||
|
raise ExtractorError('Could not find video in playlists')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'_type': 'url',
|
'_type': 'url',
|
||||||
|
@ -18,15 +18,15 @@ class NDRIE(InfoExtractor):
|
|||||||
|
|
||||||
_TESTS = [
|
_TESTS = [
|
||||||
{
|
{
|
||||||
'url': 'http://www.ndr.de/fernsehen/sendungen/markt/markt7959.html',
|
'url': 'http://www.ndr.de/fernsehen/media/dienordreportage325.html',
|
||||||
'md5': 'e7a6079ca39d3568f4996cb858dd6708',
|
'md5': '4a4eeafd17c3058b65f0c8f091355855',
|
||||||
'note': 'Video file',
|
'note': 'Video file',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '7959',
|
'id': '325',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Markt - die ganze Sendung',
|
'title': 'Blaue Bohnen aus Blocken',
|
||||||
'description': 'md5:af9179cf07f67c5c12dc6d9997e05725',
|
'description': 'md5:190d71ba2ccddc805ed01547718963bc',
|
||||||
'duration': 2655,
|
'duration': 1715,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -4,18 +4,19 @@ from __future__ import unicode_literals
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from ..utils import ExtractorError
|
||||||
|
|
||||||
|
|
||||||
class NewstubeIE(InfoExtractor):
|
class NewstubeIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?newstube\.ru/media/(?P<id>.+)'
|
_VALID_URL = r'https?://(?:www\.)?newstube\.ru/media/(?P<id>.+)'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://newstube.ru/media/na-korable-progress-prodolzhaetsya-testirovanie-sistemy-kurs',
|
'url': 'http://www.newstube.ru/media/telekanal-cnn-peremestil-gorod-slavyansk-v-krym',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'd156a237-a6e9-4111-a682-039995f721f1',
|
'id': '728e0ef2-e187-4012-bac0-5a081fdcb1f6',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'title': 'На корабле «Прогресс» продолжается тестирование системы «Курс»',
|
'title': 'Телеканал CNN переместил город Славянск в Крым',
|
||||||
'description': 'md5:d0cbe7b4a6f600552617e48548d5dc77',
|
'description': 'md5:419a8c9f03442bc0b0a794d689360335',
|
||||||
'duration': 20.04,
|
'duration': 31.05,
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
# rtmp download
|
# rtmp download
|
||||||
@ -40,6 +41,10 @@ class NewstubeIE(InfoExtractor):
|
|||||||
def ns(s):
|
def ns(s):
|
||||||
return s.replace('/', '/%(ns)s') % {'ns': '{http://app1.newstube.ru/N2SiteWS/player.asmx}'}
|
return s.replace('/', '/%(ns)s') % {'ns': '{http://app1.newstube.ru/N2SiteWS/player.asmx}'}
|
||||||
|
|
||||||
|
error_message = player.find(ns('./ErrorMessage'))
|
||||||
|
if error_message is not None:
|
||||||
|
raise ExtractorError('%s returned error: %s' % (self.IE_NAME, error_message.text), expected=True)
|
||||||
|
|
||||||
session_id = player.find(ns('./SessionId')).text
|
session_id = player.find(ns('./SessionId')).text
|
||||||
media_info = player.find(ns('./Medias/MediaInfo'))
|
media_info = player.find(ns('./Medias/MediaInfo'))
|
||||||
title = media_info.find(ns('./Name')).text
|
title = media_info.find(ns('./Name')).text
|
||||||
|
@ -8,10 +8,9 @@ from ..utils import (
|
|||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
compat_urllib_request,
|
compat_urllib_request,
|
||||||
compat_urlparse,
|
compat_urlparse,
|
||||||
compat_str,
|
|
||||||
|
|
||||||
ExtractorError,
|
|
||||||
unified_strdate,
|
unified_strdate,
|
||||||
|
parse_duration,
|
||||||
|
int_or_none,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -30,6 +29,7 @@ class NiconicoIE(InfoExtractor):
|
|||||||
'uploader_id': '2698420',
|
'uploader_id': '2698420',
|
||||||
'upload_date': '20131123',
|
'upload_date': '20131123',
|
||||||
'description': '(c) copyright 2008, Blender Foundation / www.bigbuckbunny.org',
|
'description': '(c) copyright 2008, Blender Foundation / www.bigbuckbunny.org',
|
||||||
|
'duration': 33,
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
'username': 'ydl.niconico@gmail.com',
|
'username': 'ydl.niconico@gmail.com',
|
||||||
@ -37,17 +37,20 @@ class NiconicoIE(InfoExtractor):
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
_VALID_URL = r'^https?://(?:www\.|secure\.)?nicovideo\.jp/watch/([a-z][a-z][0-9]+)(?:.*)$'
|
_VALID_URL = r'https?://(?:www\.|secure\.)?nicovideo\.jp/watch/((?:[a-z]{2})?[0-9]+)'
|
||||||
_NETRC_MACHINE = 'niconico'
|
_NETRC_MACHINE = 'niconico'
|
||||||
|
# Determine whether the downloader uses authentication to download video
|
||||||
|
_AUTHENTICATE = False
|
||||||
|
|
||||||
def _real_initialize(self):
|
def _real_initialize(self):
|
||||||
self._login()
|
if self._downloader.params.get('username', None) is not None:
|
||||||
|
self._AUTHENTICATE = True
|
||||||
|
|
||||||
|
if self._AUTHENTICATE:
|
||||||
|
self._login()
|
||||||
|
|
||||||
def _login(self):
|
def _login(self):
|
||||||
(username, password) = self._get_login_info()
|
(username, password) = self._get_login_info()
|
||||||
if username is None:
|
|
||||||
# Login is required
|
|
||||||
raise ExtractorError('No login info available, needed for using %s.' % self.IE_NAME, expected=True)
|
|
||||||
|
|
||||||
# Log in
|
# Log in
|
||||||
login_form_strs = {
|
login_form_strs = {
|
||||||
@ -79,44 +82,66 @@ class NiconicoIE(InfoExtractor):
|
|||||||
'http://ext.nicovideo.jp/api/getthumbinfo/' + video_id, video_id,
|
'http://ext.nicovideo.jp/api/getthumbinfo/' + video_id, video_id,
|
||||||
note='Downloading video info page')
|
note='Downloading video info page')
|
||||||
|
|
||||||
# Get flv info
|
if self._AUTHENTICATE:
|
||||||
flv_info_webpage = self._download_webpage(
|
# Get flv info
|
||||||
'http://flapi.nicovideo.jp/api/getflv?v=' + video_id,
|
flv_info_webpage = self._download_webpage(
|
||||||
video_id, 'Downloading flv info')
|
'http://flapi.nicovideo.jp/api/getflv?v=' + video_id,
|
||||||
|
video_id, 'Downloading flv info')
|
||||||
|
else:
|
||||||
|
# Get external player info
|
||||||
|
ext_player_info = self._download_webpage(
|
||||||
|
'http://ext.nicovideo.jp/thumb_watch/' + video_id, video_id)
|
||||||
|
thumb_play_key = self._search_regex(
|
||||||
|
r'\'thumbPlayKey\'\s*:\s*\'(.*?)\'', ext_player_info, 'thumbPlayKey')
|
||||||
|
|
||||||
|
# Get flv info
|
||||||
|
flv_info_data = compat_urllib_parse.urlencode({
|
||||||
|
'k': thumb_play_key,
|
||||||
|
'v': video_id
|
||||||
|
})
|
||||||
|
flv_info_request = compat_urllib_request.Request(
|
||||||
|
'http://ext.nicovideo.jp/thumb_watch', flv_info_data,
|
||||||
|
{'Content-Type': 'application/x-www-form-urlencoded'})
|
||||||
|
flv_info_webpage = self._download_webpage(
|
||||||
|
flv_info_request, video_id,
|
||||||
|
note='Downloading flv info', errnote='Unable to download flv info')
|
||||||
|
|
||||||
video_real_url = compat_urlparse.parse_qs(flv_info_webpage)['url'][0]
|
video_real_url = compat_urlparse.parse_qs(flv_info_webpage)['url'][0]
|
||||||
|
|
||||||
# Start extracting information
|
# Start extracting information
|
||||||
video_title = video_info.find('.//title').text
|
title = video_info.find('.//title').text
|
||||||
video_extension = video_info.find('.//movie_type').text
|
extension = video_info.find('.//movie_type').text
|
||||||
video_format = video_extension.upper()
|
video_format = extension.upper()
|
||||||
video_thumbnail = video_info.find('.//thumbnail_url').text
|
thumbnail = video_info.find('.//thumbnail_url').text
|
||||||
video_description = video_info.find('.//description').text
|
description = video_info.find('.//description').text
|
||||||
video_uploader_id = video_info.find('.//user_id').text
|
upload_date = unified_strdate(video_info.find('.//first_retrieve').text.split('+')[0])
|
||||||
video_upload_date = unified_strdate(video_info.find('.//first_retrieve').text.split('+')[0])
|
view_count = int_or_none(video_info.find('.//view_counter').text)
|
||||||
video_view_count = video_info.find('.//view_counter').text
|
comment_count = int_or_none(video_info.find('.//comment_num').text)
|
||||||
video_webpage_url = video_info.find('.//watch_url').text
|
duration = parse_duration(video_info.find('.//length').text)
|
||||||
|
webpage_url = video_info.find('.//watch_url').text
|
||||||
|
|
||||||
# uploader
|
if video_info.find('.//ch_id') is not None:
|
||||||
video_uploader = video_uploader_id
|
uploader_id = video_info.find('.//ch_id').text
|
||||||
url = 'http://seiga.nicovideo.jp/api/user/info?id=' + video_uploader_id
|
uploader = video_info.find('.//ch_name').text
|
||||||
try:
|
elif video_info.find('.//user_id') is not None:
|
||||||
user_info = self._download_xml(
|
uploader_id = video_info.find('.//user_id').text
|
||||||
url, video_id, note='Downloading user information')
|
uploader = video_info.find('.//user_nickname').text
|
||||||
video_uploader = user_info.find('.//nickname').text
|
else:
|
||||||
except ExtractorError as err:
|
uploader_id = uploader = None
|
||||||
self._downloader.report_warning('Unable to download user info webpage: %s' % compat_str(err))
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'url': video_real_url,
|
'url': video_real_url,
|
||||||
'title': video_title,
|
'title': title,
|
||||||
'ext': video_extension,
|
'ext': extension,
|
||||||
'format': video_format,
|
'format': video_format,
|
||||||
'thumbnail': video_thumbnail,
|
'thumbnail': thumbnail,
|
||||||
'description': video_description,
|
'description': description,
|
||||||
'uploader': video_uploader,
|
'uploader': uploader,
|
||||||
'upload_date': video_upload_date,
|
'upload_date': upload_date,
|
||||||
'uploader_id': video_uploader_id,
|
'uploader_id': uploader_id,
|
||||||
'view_count': video_view_count,
|
'view_count': view_count,
|
||||||
'webpage_url': video_webpage_url,
|
'comment_count': comment_count,
|
||||||
|
'duration': duration,
|
||||||
|
'webpage_url': webpage_url,
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@ class NineGagIE(InfoExtractor):
|
|||||||
webpage = self._download_webpage(url, display_id)
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
|
||||||
post_view = json.loads(self._html_search_regex(
|
post_view = json.loads(self._html_search_regex(
|
||||||
r'var postView = new app\.PostView\({\s*post:\s*({.+?}),', webpage, 'post view'))
|
r'var postView = new app\.PostView\({\s*post:\s*({.+?}),\s*posts:\s*prefetchedCurrentPost', webpage, 'post view'))
|
||||||
|
|
||||||
youtube_id = post_view['videoExternalId']
|
youtube_id = post_view['videoExternalId']
|
||||||
title = post_view['title']
|
title = post_view['title']
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# encoding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
@ -8,19 +9,34 @@ from ..utils import ExtractorError
|
|||||||
|
|
||||||
|
|
||||||
class NownessIE(InfoExtractor):
|
class NownessIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?nowness\.com/[^?#]*?/(?P<id>[0-9]+)/(?P<slug>[^/]+?)(?:$|[?#])'
|
_VALID_URL = r'https?://(?:(?:www|cn)\.)?nowness\.com/[^?#]*?/(?P<id>[0-9]+)/(?P<slug>[^/]+?)(?:$|[?#])'
|
||||||
|
|
||||||
_TEST = {
|
_TESTS = [
|
||||||
'url': 'http://www.nowness.com/day/2013/6/27/3131/candor--the-art-of-gesticulation',
|
{
|
||||||
'md5': '068bc0202558c2e391924cb8cc470676',
|
'url': 'http://www.nowness.com/day/2013/6/27/3131/candor--the-art-of-gesticulation',
|
||||||
'info_dict': {
|
'md5': '068bc0202558c2e391924cb8cc470676',
|
||||||
'id': '2520295746001',
|
'info_dict': {
|
||||||
'ext': 'mp4',
|
'id': '2520295746001',
|
||||||
'description': 'Candor: The Art of Gesticulation',
|
'ext': 'mp4',
|
||||||
'uploader': 'Nowness',
|
'title': 'Candor: The Art of Gesticulation',
|
||||||
'title': 'Candor: The Art of Gesticulation',
|
'description': 'Candor: The Art of Gesticulation',
|
||||||
}
|
'thumbnail': 're:^https?://.*\.jpg',
|
||||||
}
|
'uploader': 'Nowness',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://cn.nowness.com/day/2014/8/7/4069/kasper-bj-rke-ft-jaakko-eino-kalevi--tnr',
|
||||||
|
'md5': 'e79cf125e387216f86b2e0a5b5c63aa3',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '3716354522001',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Kasper Bjørke ft. Jaakko Eino Kalevi: TNR',
|
||||||
|
'description': 'Kasper Bjørke ft. Jaakko Eino Kalevi: TNR',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg',
|
||||||
|
'uploader': 'Nowness',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
62
youtube_dl/extractor/npo.py
Normal file
62
youtube_dl/extractor/npo.py
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
unified_strdate,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class NPOIE(InfoExtractor):
|
||||||
|
IE_NAME = 'npo.nl'
|
||||||
|
_VALID_URL = r'https?://www\.npo\.nl/[^/]+/[^/]+/(?P<id>[^/?]+)'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.npo.nl/nieuwsuur/22-06-2014/VPWON_1220719',
|
||||||
|
'md5': '4b3f9c429157ec4775f2c9cb7b911016',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'VPWON_1220719',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Nieuwsuur',
|
||||||
|
'description': 'Dagelijks tussen tien en elf: nieuws, sport en achtergronden.',
|
||||||
|
'upload_date': '20140622',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
metadata = self._download_json(
|
||||||
|
'http://e.omroep.nl/metadata/aflevering/%s' % video_id,
|
||||||
|
video_id,
|
||||||
|
# We have to remove the javascript callback
|
||||||
|
transform_source=lambda j: re.sub(r'parseMetadata\((.*?)\);\n//.*$', r'\1', j)
|
||||||
|
)
|
||||||
|
token_page = self._download_webpage(
|
||||||
|
'http://ida.omroep.nl/npoplayer/i.js',
|
||||||
|
video_id,
|
||||||
|
note='Downloading token'
|
||||||
|
)
|
||||||
|
token = self._search_regex(r'npoplayer.token = "(.+?)"', token_page, 'token')
|
||||||
|
streams_info = self._download_json(
|
||||||
|
'http://ida.omroep.nl/odi/?prid=%s&puboptions=h264_std&adaptive=yes&token=%s' % (video_id, token),
|
||||||
|
video_id
|
||||||
|
)
|
||||||
|
|
||||||
|
stream_info = self._download_json(
|
||||||
|
streams_info['streams'][0] + '&type=json',
|
||||||
|
video_id,
|
||||||
|
'Downloading stream info'
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': metadata['titel'],
|
||||||
|
'ext': 'mp4',
|
||||||
|
'url': stream_info['url'],
|
||||||
|
'description': metadata['info'],
|
||||||
|
'thumbnail': metadata['images'][-1]['url'],
|
||||||
|
'upload_date': unified_strdate(metadata['gidsdatum']),
|
||||||
|
}
|
@ -38,7 +38,7 @@ class NuvidIE(InfoExtractor):
|
|||||||
webpage = self._download_webpage(
|
webpage = self._download_webpage(
|
||||||
request, video_id, 'Downloading %s page' % format_id)
|
request, video_id, 'Downloading %s page' % format_id)
|
||||||
video_url = self._html_search_regex(
|
video_url = self._html_search_regex(
|
||||||
r'<a href="([^"]+)"\s*>Continue to watch video', webpage, '%s video URL' % format_id, fatal=False)
|
r'<a\s+href="([^"]+)"\s+class="b_link">', webpage, '%s video URL' % format_id, fatal=False)
|
||||||
if not video_url:
|
if not video_url:
|
||||||
continue
|
continue
|
||||||
formats.append({
|
formats.append({
|
||||||
@ -49,19 +49,24 @@ class NuvidIE(InfoExtractor):
|
|||||||
webpage = self._download_webpage(
|
webpage = self._download_webpage(
|
||||||
'http://m.nuvid.com/video/%s' % video_id, video_id, 'Downloading video page')
|
'http://m.nuvid.com/video/%s' % video_id, video_id, 'Downloading video page')
|
||||||
title = self._html_search_regex(
|
title = self._html_search_regex(
|
||||||
r'<div class="title">\s+<h2[^>]*>([^<]+)</h2>', webpage, 'title').strip()
|
[r'<span title="([^"]+)">',
|
||||||
thumbnail = self._html_search_regex(
|
r'<div class="thumb-holder video">\s*<h5[^>]*>([^<]+)</h5>'], webpage, 'title').strip()
|
||||||
r'href="(/thumbs/[^"]+)"[^>]*data-link_type="thumbs"',
|
thumbnails = [
|
||||||
webpage, 'thumbnail URL', fatal=False)
|
{
|
||||||
|
'url': thumb_url,
|
||||||
|
} for thumb_url in re.findall(r'<img src="([^"]+)" alt="" />', webpage)
|
||||||
|
]
|
||||||
|
thumbnail = thumbnails[0]['url'] if thumbnails else None
|
||||||
duration = parse_duration(self._html_search_regex(
|
duration = parse_duration(self._html_search_regex(
|
||||||
r'Length:\s*<span>(\d{2}:\d{2})</span>',webpage, 'duration', fatal=False))
|
r'<i class="fa fa-clock-o"></i>\s*(\d{2}:\d{2})', webpage, 'duration', fatal=False))
|
||||||
upload_date = unified_strdate(self._html_search_regex(
|
upload_date = unified_strdate(self._html_search_regex(
|
||||||
r'Added:\s*<span>(\d{4}-\d{2}-\d{2})</span>', webpage, 'upload date', fatal=False))
|
r'<i class="fa fa-user"></i>\s*(\d{4}-\d{2}-\d{2})', webpage, 'upload date', fatal=False))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': title,
|
'title': title,
|
||||||
'thumbnail': 'http://m.nuvid.com%s' % thumbnail,
|
'thumbnails': thumbnails,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
'duration': duration,
|
'duration': duration,
|
||||||
'upload_date': upload_date,
|
'upload_date': upload_date,
|
||||||
'age_limit': 18,
|
'age_limit': 18,
|
||||||
|
@ -1,40 +0,0 @@
|
|||||||
# coding: utf-8
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import calendar
|
|
||||||
import datetime
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
|
||||||
|
|
||||||
# audios on oe1.orf.at are only available for 7 days, so we can't
|
|
||||||
# add tests.
|
|
||||||
|
|
||||||
|
|
||||||
class OE1IE(InfoExtractor):
|
|
||||||
IE_DESC = 'oe1.orf.at'
|
|
||||||
_VALID_URL = r'http://oe1\.orf\.at/programm/(?P<id>[0-9]+)'
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
|
||||||
mobj = re.match(self._VALID_URL, url)
|
|
||||||
show_id = mobj.group('id')
|
|
||||||
|
|
||||||
data = self._download_json(
|
|
||||||
'http://oe1.orf.at/programm/%s/konsole' % show_id,
|
|
||||||
show_id
|
|
||||||
)
|
|
||||||
|
|
||||||
timestamp = datetime.datetime.strptime('%s %s' % (
|
|
||||||
data['item']['day_label'],
|
|
||||||
data['item']['time']
|
|
||||||
), '%d.%m.%Y %H:%M')
|
|
||||||
unix_timestamp = calendar.timegm(timestamp.utctimetuple())
|
|
||||||
|
|
||||||
return {
|
|
||||||
'id': show_id,
|
|
||||||
'title': data['item']['title'],
|
|
||||||
'url': data['item']['url_stream'],
|
|
||||||
'ext': 'mp3',
|
|
||||||
'description': data['item'].get('info'),
|
|
||||||
'timestamp': unix_timestamp
|
|
||||||
}
|
|
@ -3,23 +3,38 @@ import re
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import unescapeHTML
|
from ..utils import (
|
||||||
|
unescapeHTML,
|
||||||
|
ExtractorError,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class OoyalaIE(InfoExtractor):
|
class OoyalaIE(InfoExtractor):
|
||||||
_VALID_URL = r'(?:ooyala:|https?://.+?\.ooyala\.com/.*?(?:embedCode|ec)=)(?P<id>.+?)(&|$)'
|
_VALID_URL = r'(?:ooyala:|https?://.+?\.ooyala\.com/.*?(?:embedCode|ec)=)(?P<id>.+?)(&|$)'
|
||||||
|
|
||||||
_TEST = {
|
_TESTS = [
|
||||||
# From http://it.slashdot.org/story/13/04/25/178216/recovering-data-from-broken-hard-drives-and-ssds-video
|
{
|
||||||
'url': 'http://player.ooyala.com/player.js?embedCode=pxczE2YjpfHfn1f3M-ykG_AmJRRn0PD8',
|
# From http://it.slashdot.org/story/13/04/25/178216/recovering-data-from-broken-hard-drives-and-ssds-video
|
||||||
'md5': '3f5cceb3a7bf461d6c29dc466cf8033c',
|
'url': 'http://player.ooyala.com/player.js?embedCode=pxczE2YjpfHfn1f3M-ykG_AmJRRn0PD8',
|
||||||
'info_dict': {
|
'md5': '3f5cceb3a7bf461d6c29dc466cf8033c',
|
||||||
'id': 'pxczE2YjpfHfn1f3M-ykG_AmJRRn0PD8',
|
'info_dict': {
|
||||||
'ext': 'mp4',
|
'id': 'pxczE2YjpfHfn1f3M-ykG_AmJRRn0PD8',
|
||||||
'title': 'Explaining Data Recovery from Hard Drives and SSDs',
|
'ext': 'mp4',
|
||||||
'description': 'How badly damaged does a drive have to be to defeat Russell and his crew? Apparently, smashed to bits.',
|
'title': 'Explaining Data Recovery from Hard Drives and SSDs',
|
||||||
|
'description': 'How badly damaged does a drive have to be to defeat Russell and his crew? Apparently, smashed to bits.',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
# Only available for ipad
|
||||||
|
'url': 'http://player.ooyala.com/player.js?embedCode=x1b3lqZDq9y_7kMyC2Op5qo-p077tXD0',
|
||||||
|
'md5': '4b9754921fddb68106e48c142e2a01e6',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'x1b3lqZDq9y_7kMyC2Op5qo-p077tXD0',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Simulation Overview - Levels of Simulation',
|
||||||
|
'description': '',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _url_for_embed_code(embed_code):
|
def _url_for_embed_code(embed_code):
|
||||||
@ -47,13 +62,30 @@ class OoyalaIE(InfoExtractor):
|
|||||||
player = self._download_webpage(player_url, embedCode)
|
player = self._download_webpage(player_url, embedCode)
|
||||||
mobile_url = self._search_regex(r'mobile_player_url="(.+?)&device="',
|
mobile_url = self._search_regex(r'mobile_player_url="(.+?)&device="',
|
||||||
player, 'mobile player url')
|
player, 'mobile player url')
|
||||||
mobile_player = self._download_webpage(mobile_url, embedCode)
|
# Looks like some videos are only available for particular devices
|
||||||
videos_info = self._search_regex(
|
# (e.g. http://player.ooyala.com/player.js?embedCode=x1b3lqZDq9y_7kMyC2Op5qo-p077tXD0
|
||||||
r'var streams=window.oo_testEnv\?\[\]:eval\("\((\[{.*?}\])\)"\);',
|
# is only available for ipad)
|
||||||
mobile_player, 'info').replace('\\"','"')
|
# Working around with fetching URLs for all the devices found starting with 'unknown'
|
||||||
videos_more_info = self._search_regex(r'eval\("\(({.*?\\"promo\\".*?})\)"', mobile_player, 'more info').replace('\\"','"')
|
# until we succeed or eventually fail for each device.
|
||||||
|
devices = re.findall(r'device\s*=\s*"([^"]+)";', player)
|
||||||
|
devices.remove('unknown')
|
||||||
|
devices.insert(0, 'unknown')
|
||||||
|
for device in devices:
|
||||||
|
mobile_player = self._download_webpage(
|
||||||
|
'%s&device=%s' % (mobile_url, device), embedCode,
|
||||||
|
'Downloading mobile player JS for %s device' % device)
|
||||||
|
videos_info = self._search_regex(
|
||||||
|
r'var streams=window.oo_testEnv\?\[\]:eval\("\((\[{.*?}\])\)"\);',
|
||||||
|
mobile_player, 'info', fatal=False, default=None)
|
||||||
|
if videos_info:
|
||||||
|
break
|
||||||
|
if not videos_info:
|
||||||
|
raise ExtractorError('Unable to extract info')
|
||||||
|
videos_info = videos_info.replace('\\"', '"')
|
||||||
|
videos_more_info = self._search_regex(
|
||||||
|
r'eval\("\(({.*?\\"promo\\".*?})\)"', mobile_player, 'more info').replace('\\"', '"')
|
||||||
videos_info = json.loads(videos_info)
|
videos_info = json.loads(videos_info)
|
||||||
videos_more_info =json.loads(videos_more_info)
|
videos_more_info = json.loads(videos_more_info)
|
||||||
|
|
||||||
if videos_more_info.get('lineup'):
|
if videos_more_info.get('lineup'):
|
||||||
videos = [self._extract_result(info, more_info) for (info, more_info) in zip(videos_info, videos_more_info['lineup'])]
|
videos = [self._extract_result(info, more_info) for (info, more_info) in zip(videos_info, videos_more_info['lineup'])]
|
||||||
|
@ -3,6 +3,8 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
import calendar
|
||||||
|
import datetime
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
@ -12,7 +14,9 @@ from ..utils import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ORFIE(InfoExtractor):
|
class ORFTVthekIE(InfoExtractor):
|
||||||
|
IE_NAME = 'orf:tvthek'
|
||||||
|
IE_DESC = 'ORF TVthek'
|
||||||
_VALID_URL = r'https?://tvthek\.orf\.at/(?:programs/.+?/episodes|topics/.+?|program/[^/]+)/(?P<id>\d+)'
|
_VALID_URL = r'https?://tvthek\.orf\.at/(?:programs/.+?/episodes|topics/.+?|program/[^/]+)/(?P<id>\d+)'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
@ -105,3 +109,73 @@ class ORFIE(InfoExtractor):
|
|||||||
'entries': entries,
|
'entries': entries,
|
||||||
'id': playlist_id,
|
'id': playlist_id,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Audios on ORF radio are only available for 7 days, so we can't add tests.
|
||||||
|
|
||||||
|
|
||||||
|
class ORFOE1IE(InfoExtractor):
|
||||||
|
IE_NAME = 'orf:oe1'
|
||||||
|
IE_DESC = 'Radio Österreich 1'
|
||||||
|
_VALID_URL = r'http://oe1\.orf\.at/programm/(?P<id>[0-9]+)'
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
show_id = mobj.group('id')
|
||||||
|
|
||||||
|
data = self._download_json(
|
||||||
|
'http://oe1.orf.at/programm/%s/konsole' % show_id,
|
||||||
|
show_id
|
||||||
|
)
|
||||||
|
|
||||||
|
timestamp = datetime.datetime.strptime('%s %s' % (
|
||||||
|
data['item']['day_label'],
|
||||||
|
data['item']['time']
|
||||||
|
), '%d.%m.%Y %H:%M')
|
||||||
|
unix_timestamp = calendar.timegm(timestamp.utctimetuple())
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': show_id,
|
||||||
|
'title': data['item']['title'],
|
||||||
|
'url': data['item']['url_stream'],
|
||||||
|
'ext': 'mp3',
|
||||||
|
'description': data['item'].get('info'),
|
||||||
|
'timestamp': unix_timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ORFFM4IE(InfoExtractor):
|
||||||
|
IE_DESC = 'orf:fm4'
|
||||||
|
IE_DESC = 'radio FM4'
|
||||||
|
_VALID_URL = r'http://fm4\.orf\.at/7tage/?#(?P<date>[0-9]+)/(?P<show>\w+)'
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
show_date = mobj.group('date')
|
||||||
|
show_id = mobj.group('show')
|
||||||
|
|
||||||
|
data = self._download_json(
|
||||||
|
'http://audioapi.orf.at/fm4/json/2.0/broadcasts/%s/4%s' % (show_date, show_id),
|
||||||
|
show_id
|
||||||
|
)
|
||||||
|
|
||||||
|
def extract_entry_dict(info, title, subtitle):
|
||||||
|
return {
|
||||||
|
'id': info['loopStreamId'].replace('.mp3', ''),
|
||||||
|
'url': 'http://loopstream01.apa.at/?channel=fm4&id=%s' % info['loopStreamId'],
|
||||||
|
'title': title,
|
||||||
|
'description': subtitle,
|
||||||
|
'duration': (info['end'] - info['start']) / 1000,
|
||||||
|
'timestamp': info['start'] / 1000,
|
||||||
|
'ext': 'mp3'
|
||||||
|
}
|
||||||
|
|
||||||
|
entries = [extract_entry_dict(t, data['title'], data['subtitle']) for t in data['streams']]
|
||||||
|
|
||||||
|
return {
|
||||||
|
'_type': 'playlist',
|
||||||
|
'id': show_id,
|
||||||
|
'title': data['title'],
|
||||||
|
'description': data['subtitle'],
|
||||||
|
'entries': entries
|
||||||
|
}
|
100
youtube_dl/extractor/patreon.py
Normal file
100
youtube_dl/extractor/patreon.py
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
# encoding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
js_to_json,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PatreonIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?patreon\.com/creation\?hid=(.+)'
|
||||||
|
_TESTS = [
|
||||||
|
{
|
||||||
|
'url': 'http://www.patreon.com/creation?hid=743933',
|
||||||
|
'md5': 'e25505eec1053a6e6813b8ed369875cc',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '743933',
|
||||||
|
'ext': 'mp3',
|
||||||
|
'title': 'Episode 166: David Smalley of Dogma Debate',
|
||||||
|
'uploader': 'Cognitive Dissonance Podcast',
|
||||||
|
'thumbnail': 're:^https?://.*$',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://www.patreon.com/creation?hid=754133',
|
||||||
|
'md5': '3eb09345bf44bf60451b8b0b81759d0a',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '754133',
|
||||||
|
'ext': 'mp3',
|
||||||
|
'title': 'CD 167 Extra',
|
||||||
|
'uploader': 'Cognitive Dissonance Podcast',
|
||||||
|
'thumbnail': 're:^https?://.*$',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
# Currently Patreon exposes download URL via hidden CSS, so login is not
|
||||||
|
# needed. Keeping this commented for when this inevitably changes.
|
||||||
|
'''
|
||||||
|
def _login(self):
|
||||||
|
(username, password) = self._get_login_info()
|
||||||
|
if username is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
login_form = {
|
||||||
|
'redirectUrl': 'http://www.patreon.com/',
|
||||||
|
'email': username,
|
||||||
|
'password': password,
|
||||||
|
}
|
||||||
|
|
||||||
|
request = compat_urllib_request.Request(
|
||||||
|
'https://www.patreon.com/processLogin',
|
||||||
|
compat_urllib_parse.urlencode(login_form).encode('utf-8')
|
||||||
|
)
|
||||||
|
login_page = self._download_webpage(request, None, note='Logging in as %s' % username)
|
||||||
|
|
||||||
|
if re.search(r'onLoginFailed', login_page):
|
||||||
|
raise ExtractorError('Unable to login, incorrect username and/or password', expected=True)
|
||||||
|
|
||||||
|
def _real_initialize(self):
|
||||||
|
self._login()
|
||||||
|
'''
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group(1)
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
title = self._og_search_title(webpage).strip()
|
||||||
|
|
||||||
|
attach_fn = self._html_search_regex(
|
||||||
|
r'<div class="attach"><a target="_blank" href="([^"]+)">',
|
||||||
|
webpage, 'attachment URL', default=None)
|
||||||
|
if attach_fn is not None:
|
||||||
|
video_url = 'http://www.patreon.com' + attach_fn
|
||||||
|
thumbnail = self._og_search_thumbnail(webpage)
|
||||||
|
uploader = self._html_search_regex(
|
||||||
|
r'<strong>(.*?)</strong> is creating', webpage, 'uploader')
|
||||||
|
else:
|
||||||
|
playlist_js = self._search_regex(
|
||||||
|
r'(?s)new\s+jPlayerPlaylist\(\s*\{\s*[^}]*},\s*(\[.*?,?\s*\])',
|
||||||
|
webpage, 'playlist JSON')
|
||||||
|
playlist_json = js_to_json(playlist_js)
|
||||||
|
playlist = json.loads(playlist_json)
|
||||||
|
data = playlist[0]
|
||||||
|
video_url = self._proto_relative_url(data['mp3'])
|
||||||
|
thumbnail = self._proto_relative_url(data.get('cover'))
|
||||||
|
uploader = data.get('artist')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'url': video_url,
|
||||||
|
'ext': 'mp3',
|
||||||
|
'title': title,
|
||||||
|
'uploader': uploader,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
}
|
@ -20,27 +20,74 @@ class PBSIE(InfoExtractor):
|
|||||||
)
|
)
|
||||||
'''
|
'''
|
||||||
|
|
||||||
_TEST = {
|
_TESTS = [
|
||||||
'url': 'http://www.pbs.org/tpt/constitution-usa-peter-sagal/watch/a-more-perfect-union/',
|
{
|
||||||
'md5': 'ce1888486f0908d555a8093cac9a7362',
|
'url': 'http://www.pbs.org/tpt/constitution-usa-peter-sagal/watch/a-more-perfect-union/',
|
||||||
'info_dict': {
|
'md5': 'ce1888486f0908d555a8093cac9a7362',
|
||||||
'id': '2365006249',
|
'info_dict': {
|
||||||
'ext': 'mp4',
|
'id': '2365006249',
|
||||||
'title': 'A More Perfect Union',
|
'ext': 'mp4',
|
||||||
'description': 'md5:ba0c207295339c8d6eced00b7c363c6a',
|
'title': 'A More Perfect Union',
|
||||||
'duration': 3190,
|
'description': 'md5:ba0c207295339c8d6eced00b7c363c6a',
|
||||||
|
'duration': 3190,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
{
|
||||||
|
'url': 'http://www.pbs.org/wgbh/pages/frontline/losing-iraq/',
|
||||||
|
'md5': '143c98aa54a346738a3d78f54c925321',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '2365297690',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Losing Iraq',
|
||||||
|
'description': 'md5:f5bfbefadf421e8bb8647602011caf8e',
|
||||||
|
'duration': 5050,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://www.pbs.org/newshour/bb/education-jan-june12-cyberschools_02-23/',
|
||||||
|
'md5': 'b19856d7f5351b17a5ab1dc6a64be633',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '2201174722',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Cyber Schools Gain Popularity, but Quality Questions Persist',
|
||||||
|
'description': 'md5:5871c15cba347c1b3d28ac47a73c7c28',
|
||||||
|
'duration': 801,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://www.pbs.org/wnet/gperf/dudamel-conducts-verdi-requiem-hollywood-bowl-full-episode/3374/',
|
||||||
|
'md5': 'c62859342be2a0358d6c9eb306595978',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '2365297708',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'description': 'md5:68d87ef760660eb564455eb30ca464fe',
|
||||||
|
'title': 'Dudamel Conducts Verdi Requiem at the Hollywood Bowl - Full',
|
||||||
|
'duration': 6559,
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _extract_ids(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
|
||||||
presumptive_id = mobj.group('presumptive_id')
|
presumptive_id = mobj.group('presumptive_id')
|
||||||
display_id = presumptive_id
|
display_id = presumptive_id
|
||||||
if presumptive_id:
|
if presumptive_id:
|
||||||
webpage = self._download_webpage(url, display_id)
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
|
||||||
|
MEDIA_ID_REGEXES = [
|
||||||
|
r"div\s*:\s*'videoembed'\s*,\s*mediaid\s*:\s*'(\d+)'", # frontline video embed
|
||||||
|
r'class="coveplayerid">([^<]+)<', # coveplayer
|
||||||
|
]
|
||||||
|
|
||||||
|
media_id = self._search_regex(
|
||||||
|
MEDIA_ID_REGEXES, webpage, 'media ID', fatal=False, default=None)
|
||||||
|
if media_id:
|
||||||
|
return media_id, presumptive_id
|
||||||
|
|
||||||
url = self._search_regex(
|
url = self._search_regex(
|
||||||
r'<iframe\s+id=["\']partnerPlayer["\'].*?\s+src=["\'](.*?)["\']>',
|
r'<iframe\s+(?:class|id)=["\']partnerPlayer["\'].*?\s+src=["\'](.*?)["\']>',
|
||||||
webpage, 'player URL')
|
webpage, 'player URL')
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
|
||||||
@ -57,6 +104,11 @@ class PBSIE(InfoExtractor):
|
|||||||
video_id = mobj.group('id')
|
video_id = mobj.group('id')
|
||||||
display_id = video_id
|
display_id = video_id
|
||||||
|
|
||||||
|
return video_id, display_id
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id, display_id = self._extract_ids(url)
|
||||||
|
|
||||||
info_url = 'http://video.pbs.org/videoInfo/%s?format=json' % video_id
|
info_url = 'http://video.pbs.org/videoInfo/%s?format=json' % video_id
|
||||||
info = self._download_json(info_url, display_id)
|
info = self._download_json(info_url, display_id)
|
||||||
|
|
||||||
|
82
youtube_dl/extractor/playfm.py
Normal file
82
youtube_dl/extractor/playfm.py
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
compat_urllib_parse,
|
||||||
|
compat_urllib_request,
|
||||||
|
ExtractorError,
|
||||||
|
float_or_none,
|
||||||
|
int_or_none,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PlayFMIE(InfoExtractor):
|
||||||
|
IE_NAME = 'play.fm'
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?play\.fm/[^?#]*(?P<upload_date>[0-9]{8})(?P<id>[0-9]{6})(?:$|[?#])'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.play.fm/recording/leipzigelectronicmusicbatofarparis_fr20140712137220',
|
||||||
|
'md5': 'c505f8307825a245d0c7ad1850001f22',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '137220',
|
||||||
|
'ext': 'mp3',
|
||||||
|
'title': 'LEIPZIG ELECTRONIC MUSIC @ Batofar (Paris,FR) - 2014-07-12',
|
||||||
|
'uploader': 'Sven Tasnadi',
|
||||||
|
'uploader_id': 'sventasnadi',
|
||||||
|
'duration': 5627.428,
|
||||||
|
'upload_date': '20140712',
|
||||||
|
'view_count': int,
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
upload_date = mobj.group('upload_date')
|
||||||
|
|
||||||
|
rec_data = compat_urllib_parse.urlencode({'rec_id': video_id})
|
||||||
|
req = compat_urllib_request.Request(
|
||||||
|
'http://www.play.fm/flexRead/recording', data=rec_data)
|
||||||
|
req.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
||||||
|
rec_doc = self._download_xml(req, video_id)
|
||||||
|
|
||||||
|
error_node = rec_doc.find('./error')
|
||||||
|
if error_node is not None:
|
||||||
|
raise ExtractorError('An error occured: %s (code %s)' % (
|
||||||
|
error_node.text, rec_doc.find('./status').text))
|
||||||
|
|
||||||
|
recording = rec_doc.find('./recording')
|
||||||
|
title = recording.find('./title').text
|
||||||
|
view_count = int_or_none(recording.find('./stats/playcount').text)
|
||||||
|
duration = float_or_none(recording.find('./duration').text, scale=1000)
|
||||||
|
thumbnail = recording.find('./image').text
|
||||||
|
|
||||||
|
artist = recording.find('./artists/artist')
|
||||||
|
uploader = artist.find('./name').text
|
||||||
|
uploader_id = artist.find('./slug').text
|
||||||
|
|
||||||
|
video_url = '%s//%s/%s/%s/offset/0/sh/%s/rec/%s/jingle/%s/loc/%s' % (
|
||||||
|
'http:', recording.find('./url').text,
|
||||||
|
recording.find('./_class').text, recording.find('./file_id').text,
|
||||||
|
rec_doc.find('./uuid').text, video_id,
|
||||||
|
rec_doc.find('./jingle/file_id').text,
|
||||||
|
'http%3A%2F%2Fwww.play.fm%2Fplayer',
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'url': video_url,
|
||||||
|
'ext': 'mp3',
|
||||||
|
'filesize': int_or_none(recording.find('./size').text),
|
||||||
|
'title': title,
|
||||||
|
'upload_date': upload_date,
|
||||||
|
'view_count': view_count,
|
||||||
|
'duration': duration,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'uploader': uploader,
|
||||||
|
'uploader_id': uploader_id,
|
||||||
|
}
|
@ -46,7 +46,7 @@ class PyvideoIE(InfoExtractor):
|
|||||||
return self.url_result(m_youtube.group(1), 'Youtube')
|
return self.url_result(m_youtube.group(1), 'Youtube')
|
||||||
|
|
||||||
title = self._html_search_regex(
|
title = self._html_search_regex(
|
||||||
r'<div class="section">.*?<h3(?:\s+class="[^"]*")?>([^>]+?)</h3>',
|
r'<div class="section">\s*<h3(?:\s+class="[^"]*"[^>]*)?>([^>]+?)</h3>',
|
||||||
webpage, 'title', flags=re.DOTALL)
|
webpage, 'title', flags=re.DOTALL)
|
||||||
video_url = self._search_regex(
|
video_url = self._search_regex(
|
||||||
[r'<source src="(.*?)"', r'<dt>Download</dt>.*?<a href="(.+?)"'],
|
[r'<source src="(.*?)"', r'<dt>Download</dt>.*?<a href="(.+?)"'],
|
||||||
|
@ -35,7 +35,8 @@ class RaiIE(SubtitlesInfoExtractor):
|
|||||||
'description': '',
|
'description': '',
|
||||||
'upload_date': '20140612',
|
'upload_date': '20140612',
|
||||||
'duration': 1758,
|
'duration': 1758,
|
||||||
}
|
},
|
||||||
|
'skip': 'Error 404',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'url': 'http://www.rainews.it/dl/rainews/media/state-of-the-net-Antonella-La-Carpia-regole-virali-7aafdea9-0e5d-49d5-88a6-7e65da67ae13.html',
|
'url': 'http://www.rainews.it/dl/rainews/media/state-of-the-net-Antonella-La-Carpia-regole-virali-7aafdea9-0e5d-49d5-88a6-7e65da67ae13.html',
|
||||||
|
@ -35,9 +35,7 @@ class RedTubeIE(InfoExtractor):
|
|||||||
r'<h1 class="videoTitle[^"]*">(.+?)</h1>',
|
r'<h1 class="videoTitle[^"]*">(.+?)</h1>',
|
||||||
webpage, u'title')
|
webpage, u'title')
|
||||||
|
|
||||||
video_thumbnail = self._html_search_regex(
|
video_thumbnail = self._og_search_thumbnail(webpage)
|
||||||
r'playerInnerHTML.+?<img\s+src="(.+?)"',
|
|
||||||
webpage, u'thumbnail', fatal=False)
|
|
||||||
|
|
||||||
# No self-labeling, but they describe themselves as
|
# No self-labeling, but they describe themselves as
|
||||||
# "Home of Videos Porno"
|
# "Home of Videos Porno"
|
||||||
|
44
youtube_dl/extractor/reverbnation.py
Normal file
44
youtube_dl/extractor/reverbnation.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import str_or_none
|
||||||
|
|
||||||
|
|
||||||
|
class ReverbNationIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'^https?://(?:www\.)?reverbnation\.com/.*?/song/(?P<id>\d+).*?$'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://www.reverbnation.com/alkilados/song/16965047-mona-lisa',
|
||||||
|
'md5': '3da12ebca28c67c111a7f8b262d3f7a7',
|
||||||
|
'info_dict': {
|
||||||
|
"id": "16965047",
|
||||||
|
"ext": "mp3",
|
||||||
|
"title": "MONA LISA",
|
||||||
|
"uploader": "ALKILADOS",
|
||||||
|
"uploader_id": "216429",
|
||||||
|
"thumbnail": "re:^https://gp1\.wac\.edgecastcdn\.net/.*?\.jpg$"
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
song_id = mobj.group('id')
|
||||||
|
|
||||||
|
api_res = self._download_json(
|
||||||
|
'https://api.reverbnation.com/song/%s' % song_id,
|
||||||
|
song_id,
|
||||||
|
note='Downloading information of song %s' % song_id
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': song_id,
|
||||||
|
'title': api_res.get('name'),
|
||||||
|
'url': api_res.get('url'),
|
||||||
|
'uploader': api_res.get('artist', {}).get('name'),
|
||||||
|
'uploader_id': str_or_none(api_res.get('artist', {}).get('id')),
|
||||||
|
'thumbnail': self._proto_relative_url(
|
||||||
|
api_res.get('image', api_res.get('thumbnail'))),
|
||||||
|
'ext': 'mp3',
|
||||||
|
'vcodec': 'none',
|
||||||
|
}
|
@ -30,7 +30,7 @@ class RTBFIE(InfoExtractor):
|
|||||||
page = self._download_webpage('https://www.rtbf.be/video/embed?id=%s' % video_id, video_id)
|
page = self._download_webpage('https://www.rtbf.be/video/embed?id=%s' % video_id, video_id)
|
||||||
|
|
||||||
data = json.loads(self._html_search_regex(
|
data = json.loads(self._html_search_regex(
|
||||||
r'<div class="js-player-embed" data-video="([^"]+)"', page, 'data video'))['data']
|
r'<div class="js-player-embed(?: player-embed)?" data-video="([^"]+)"', page, 'data video'))['data']
|
||||||
|
|
||||||
video_url = data.get('downloadUrl') or data.get('url')
|
video_url = data.get('downloadUrl') or data.get('url')
|
||||||
|
|
||||||
|
51
youtube_dl/extractor/rtlnl.py
Normal file
51
youtube_dl/extractor/rtlnl.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
|
class RtlXlIE(InfoExtractor):
|
||||||
|
IE_NAME = 'rtlxl.nl'
|
||||||
|
_VALID_URL = r'https?://www\.rtlxl\.nl/#!/[^/]+/(?P<uuid>[^/?]+)'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.rtlxl.nl/#!/rtl-nieuws-132237/6e4203a6-0a5e-3596-8424-c599a59e0677',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '6e4203a6-0a5e-3596-8424-c599a59e0677',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'RTL Nieuws - Laat',
|
||||||
|
'description': 'Dagelijks het laatste nieuws uit binnen- en '
|
||||||
|
'buitenland. Voor nog meer nieuws kunt u ook gebruikmaken van '
|
||||||
|
'onze mobiele apps.',
|
||||||
|
'timestamp': 1408051800,
|
||||||
|
'upload_date': '20140814',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# We download the first bytes of the first fragment, it can't be
|
||||||
|
# processed by the f4m downloader beacuse it isn't complete
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
uuid = mobj.group('uuid')
|
||||||
|
|
||||||
|
info = self._download_json(
|
||||||
|
'http://www.rtl.nl/system/s4m/vfd/version=2/uuid=%s/fmt=flash/' % uuid,
|
||||||
|
uuid)
|
||||||
|
material = info['material'][0]
|
||||||
|
episode_info = info['episodes'][0]
|
||||||
|
|
||||||
|
f4m_url = 'http://manifest.us.rtl.nl' + material['videopath']
|
||||||
|
progname = info['abstracts'][0]['name']
|
||||||
|
subtitle = material['title'] or info['episodes'][0]['name']
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': uuid,
|
||||||
|
'title': '%s - %s' % (progname, subtitle),
|
||||||
|
'formats': self._extract_f4m_formats(f4m_url, uuid),
|
||||||
|
'timestamp': material['original_date'],
|
||||||
|
'description': episode_info['synopsis'],
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user