Compare commits
278 Commits
2015.01.10
...
2015.02.01
Author | SHA1 | Date | |
---|---|---|---|
|
0a7055c90d | ||
|
9e3f19919a | ||
|
4a3da4ebdb | ||
|
027008b14e | ||
|
c6df692466 | ||
|
acf757f42e | ||
|
dd8982f19c | ||
|
654bd52f58 | ||
|
a9551e9020 | ||
|
4e980275b5 | ||
|
c172440ac5 | ||
|
e332772531 | ||
|
437cac8cc1 | ||
|
9f281cacd2 | ||
|
748a0fab8a | ||
|
c1f06d6307 | ||
|
c4e817ce4a | ||
|
9a3e5e6955 | ||
|
228d30ed06 | ||
|
057c0609fc | ||
|
17d2712d9c | ||
|
fc09240e24 | ||
|
146303136f | ||
|
96aded8d3d | ||
|
2886be15aa | ||
|
ca0f500ecf | ||
|
29aef5a33c | ||
|
9158b2b301 | ||
|
0196149c5b | ||
|
8f9312c387 | ||
|
439b9a9e9b | ||
|
8c72beb25e | ||
|
1ee94db2d0 | ||
|
e41b1f7385 | ||
|
cd596028d6 | ||
|
cc57bd33a8 | ||
|
6d593c3276 | ||
|
91755ee384 | ||
|
0692ef86ef | ||
|
439d9be27d | ||
|
b80505a409 | ||
|
e4c17d7274 | ||
|
2c58674e0e | ||
|
ef1269fb07 | ||
|
e525d9a3df | ||
|
20b4492c71 | ||
|
dee3f73787 | ||
|
d543bdc351 | ||
|
c7ff0c6422 | ||
|
01c46659c4 | ||
|
b04b885271 | ||
|
dc35bfd2d5 | ||
|
70fca8d694 | ||
|
a52c633536 | ||
|
7b6c60393e | ||
|
83e7a314b4 | ||
|
749f2ca044 | ||
|
5468ff4d91 | ||
|
1d2daaea63 | ||
|
52585fd6dc | ||
|
c03844a4ec | ||
|
6449cd807e | ||
|
e2a08185c6 | ||
|
5d6677ca28 | ||
|
5a8a29cfea | ||
|
c1708b89c0 | ||
|
83fddfd493 | ||
|
1798791df1 | ||
|
6ebb0dca9f | ||
|
cf8d6ec865 | ||
|
f452f72c6b | ||
|
3198291f26 | ||
|
02c1d5e285 | ||
|
ec4161a57d | ||
|
03d8d4df38 | ||
|
03d2d6d51b | ||
|
83fda3c000 | ||
|
4fe8495a23 | ||
|
a16f6643f0 | ||
|
adc0ae3ceb | ||
|
7bb3ceb4c7 | ||
|
75a4fc5b72 | ||
|
87673cd438 | ||
|
f345fe9db7 | ||
|
e683a48d0e | ||
|
a7a14d9586 | ||
|
219337990b | ||
|
376a770cc4 | ||
|
7e500dbd93 | ||
|
affd04a45d | ||
|
c84130e865 | ||
|
4f264c02c7 | ||
|
d205476103 | ||
|
367cc95aa7 | ||
|
206dba27a4 | ||
|
dcf53d4408 | ||
|
63be3b8989 | ||
|
18b4e9e79d | ||
|
cb454b333d | ||
|
e0d9f85aee | ||
|
b04fbd789c | ||
|
aad9556414 | ||
|
48a1e5141a | ||
|
0865f397ae | ||
|
796df3c631 | ||
|
a28383834b | ||
|
3a0d2f520a | ||
|
6348ad12a0 | ||
|
fe7710cbcc | ||
|
2103d038b3 | ||
|
6ca85be6f8 | ||
|
9f0df77ab1 | ||
|
e72c7e4123 | ||
|
2b1bd292ae | ||
|
71e7da6533 | ||
|
80a49d3d7b | ||
|
d862a4f94f | ||
|
a57e8ce658 | ||
|
96a53167fa | ||
|
6d2749aac4 | ||
|
b1b0b1ca30 | ||
|
3dee7826e7 | ||
|
c9326b38b8 | ||
|
d4f64cabf4 | ||
|
fe41ddbb28 | ||
|
ee69b99af6 | ||
|
767ff0a2d1 | ||
|
8604e882a8 | ||
|
cc1237f484 | ||
|
37f4ce538a | ||
|
7d346331b5 | ||
|
e1ccc04e9f | ||
|
881e6a1f5c | ||
|
baeaeffce5 | ||
|
c14e88f0f5 | ||
|
8940b8608e | ||
|
ec82d85acd | ||
|
cfb56d1af3 | ||
|
1e10802990 | ||
|
6695916045 | ||
|
7906d199a1 | ||
|
1070711d60 | ||
|
4b405cfc6e | ||
|
e5660ee6ae | ||
|
8011fba3ae | ||
|
587a9c2749 | ||
|
e1554a407d | ||
|
3fcfb8e9fa | ||
|
384b62028a | ||
|
b95aab8482 | ||
|
fc2d6abfe7 | ||
|
27de5625d4 | ||
|
6aa4f54d66 | ||
|
222516d97d | ||
|
a055469faf | ||
|
fdaaaaa878 | ||
|
12d1fb5aa9 | ||
|
48f00d15b1 | ||
|
3e055aa5c3 | ||
|
6896a52721 | ||
|
5779b3e1fe | ||
|
62cd676c74 | ||
|
0c17278843 | ||
|
d229ee70da | ||
|
26e274666d | ||
|
ebd46aed51 | ||
|
e793f7671c | ||
|
c2e64f71d0 | ||
|
0920e5830f | ||
|
bf7fa94ec7 | ||
|
6f58db8982 | ||
|
aa42e87340 | ||
|
649f7966f7 | ||
|
5f0d813d93 | ||
|
501f13fbf3 | ||
|
ba55168157 | ||
|
d79323136f | ||
|
08ff6ab07e | ||
|
ba655a0e4c | ||
|
b59c17e543 | ||
|
61ca9a80b3 | ||
|
317239b097 | ||
|
c2a30b250c | ||
|
c994e6bd63 | ||
|
3ee2aa7a16 | ||
|
083c9df93b | ||
|
50789175ed | ||
|
dc1b027cd4 | ||
|
f353cbdb2f | ||
|
73e449b226 | ||
|
b4a64c592b | ||
|
78111136db | ||
|
650ab5beeb | ||
|
7932de6352 | ||
|
240b9b7a5c | ||
|
bb6e38787d | ||
|
898c23c03f | ||
|
b55ee18ff3 | ||
|
e5763a7a7e | ||
|
8bb1bdfae9 | ||
|
c62b449765 | ||
|
bb0aa4cb3c | ||
|
d63528c8c7 | ||
|
c5db6bb32b | ||
|
c8dc41a6e7 | ||
|
47e0e1e0e2 | ||
|
efcddaebe9 | ||
|
5fe5112589 | ||
|
564bb5e964 | ||
|
2df54b4ba8 | ||
|
030aa5d9e7 | ||
|
c511f13f22 | ||
|
bd3cbe0716 | ||
|
fdb2ed7455 | ||
|
ba319696a9 | ||
|
910c552052 | ||
|
cce81f192c | ||
|
9d22a7dfb0 | ||
|
4f4f642822 | ||
|
2875cf01bb | ||
|
e205db3bcd | ||
|
31d4a6e212 | ||
|
aaeb86f682 | ||
|
9fa6ea2680 | ||
|
a9b6b5cd15 | ||
|
a45c0a5d67 | ||
|
c8dfe360eb | ||
|
4cfaf85c65 | ||
|
be5f2c192c | ||
|
c9ef44ce29 | ||
|
e92d4a11f5 | ||
|
f2cbc96c3e | ||
|
a69801e2c6 | ||
|
034206cec1 | ||
|
04e0bac233 | ||
|
fbef83f399 | ||
|
3d5f7a3947 | ||
|
a5fb718c50 | ||
|
227d4822ff | ||
|
5c4a81d934 | ||
|
263255eb8d | ||
|
8e2ec95575 | ||
|
8e7a9016d5 | ||
|
c85f368370 | ||
|
a0977064ce | ||
|
15aecd8711 | ||
|
20dd0b2d20 | ||
|
f934860a07 | ||
|
2aeb06d6dc | ||
|
6ccbb335d2 | ||
|
4340decad2 | ||
|
f3ff1a3696 | ||
|
aa24de39aa | ||
|
a798e64c15 | ||
|
6a5fa75490 | ||
|
8ad6b5ed9f | ||
|
d5bb814d34 | ||
|
d156a1d981 | ||
|
987493aef3 | ||
|
8bfa75451b | ||
|
c071733fd4 | ||
|
cd3063f3fa | ||
|
58b1f00d19 | ||
|
5a000b45b3 | ||
|
40b1cbafac | ||
|
4231235cda | ||
|
ca7a9c1bf7 | ||
|
247a5da704 | ||
|
d1b4617e1d | ||
|
74dcf42a85 | ||
|
a42c921598 | ||
|
f96252b913 | ||
|
04b89c9026 | ||
|
0c72eb9060 | ||
|
f9f86b0c64 | ||
|
0aed8df2bf | ||
|
2f61fe4ccc | ||
|
03359e9864 |
@@ -4,6 +4,9 @@ python:
|
|||||||
- "2.7"
|
- "2.7"
|
||||||
- "3.3"
|
- "3.3"
|
||||||
- "3.4"
|
- "3.4"
|
||||||
|
before_install:
|
||||||
|
- sudo apt-get update -qq
|
||||||
|
- sudo apt-get install -yqq rtmpdump
|
||||||
script: nosetests test --verbose
|
script: nosetests test --verbose
|
||||||
notifications:
|
notifications:
|
||||||
email:
|
email:
|
||||||
|
6
AUTHORS
6
AUTHORS
@@ -102,3 +102,9 @@ Joel Leclerc
|
|||||||
Christopher Krooss
|
Christopher Krooss
|
||||||
Ondřej Caletka
|
Ondřej Caletka
|
||||||
Dinesh S
|
Dinesh S
|
||||||
|
Johan K. Jensen
|
||||||
|
Yen Chi Hsuan
|
||||||
|
Enam Mijbah Noor
|
||||||
|
David Luhmer
|
||||||
|
Shaya Goldberg
|
||||||
|
Paul Hartmann
|
||||||
|
79
README.md
79
README.md
@@ -93,6 +93,14 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
## Video Selection:
|
## Video Selection:
|
||||||
--playlist-start NUMBER playlist video to start at (default is 1)
|
--playlist-start NUMBER playlist video to start at (default is 1)
|
||||||
--playlist-end NUMBER playlist video to end at (default is last)
|
--playlist-end NUMBER playlist video to end at (default is last)
|
||||||
|
--playlist-items ITEM_SPEC playlist video items to download. Specify
|
||||||
|
indices of the videos in the playlist
|
||||||
|
seperated by commas like: "--playlist-items
|
||||||
|
1,2,5,8" if you want to download videos
|
||||||
|
indexed 1, 2, 5, 8 in the playlist. You can
|
||||||
|
specify range: "--playlist-items
|
||||||
|
1-3,7,10-13", it will download the videos
|
||||||
|
at index 1, 2, 3, 7, 10, 11, 12 and 13.
|
||||||
--match-title REGEX download only matching titles (regex or
|
--match-title REGEX download only matching titles (regex or
|
||||||
caseless sub-string)
|
caseless sub-string)
|
||||||
--reject-title REGEX skip download for matching titles (regex or
|
--reject-title REGEX skip download for matching titles (regex or
|
||||||
@@ -124,7 +132,8 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
## Download Options:
|
## Download Options:
|
||||||
-r, --rate-limit LIMIT maximum download rate in bytes per second
|
-r, --rate-limit LIMIT maximum download rate in bytes per second
|
||||||
(e.g. 50K or 4.2M)
|
(e.g. 50K or 4.2M)
|
||||||
-R, --retries RETRIES number of retries (default is 10)
|
-R, --retries RETRIES number of retries (default is 10), or
|
||||||
|
"infinite".
|
||||||
--buffer-size SIZE size of download buffer (e.g. 1024 or 16K)
|
--buffer-size SIZE size of download buffer (e.g. 1024 or 16K)
|
||||||
(default is 1024)
|
(default is 1024)
|
||||||
--no-resize-buffer do not automatically adjust the buffer
|
--no-resize-buffer do not automatically adjust the buffer
|
||||||
@@ -132,6 +141,11 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
automatically resized from an initial value
|
automatically resized from an initial value
|
||||||
of SIZE.
|
of SIZE.
|
||||||
--playlist-reverse Download playlist videos in reverse order
|
--playlist-reverse Download playlist videos in reverse order
|
||||||
|
--xattr-set-filesize (experimental) set file xattribute
|
||||||
|
ytdl.filesize with expected filesize
|
||||||
|
--external-downloader COMMAND (experimental) Use the specified external
|
||||||
|
downloader. Currently supports
|
||||||
|
aria2c,curl,wget
|
||||||
|
|
||||||
## Filesystem Options:
|
## Filesystem Options:
|
||||||
-a, --batch-file FILE file containing URLs to download ('-' for
|
-a, --batch-file FILE file containing URLs to download ('-' for
|
||||||
@@ -191,7 +205,6 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
--write-info-json write video metadata to a .info.json file
|
--write-info-json write video metadata to a .info.json file
|
||||||
--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
|
|
||||||
--load-info FILE json file containing the video information
|
--load-info FILE json file containing the video information
|
||||||
(created with the "--write-json" option)
|
(created with the "--write-json" option)
|
||||||
--cookies FILE file to read cookies from and dump cookie
|
--cookies FILE file to read cookies from and dump cookie
|
||||||
@@ -206,6 +219,12 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
--no-cache-dir Disable filesystem caching
|
--no-cache-dir Disable filesystem caching
|
||||||
--rm-cache-dir Delete all filesystem cache files
|
--rm-cache-dir Delete all filesystem cache files
|
||||||
|
|
||||||
|
## Thumbnail images:
|
||||||
|
--write-thumbnail write thumbnail image to disk
|
||||||
|
--write-all-thumbnails write all thumbnail image formats to disk
|
||||||
|
--list-thumbnails Simulate and list all available thumbnail
|
||||||
|
formats
|
||||||
|
|
||||||
## Verbosity / Simulation Options:
|
## Verbosity / Simulation Options:
|
||||||
-q, --quiet activates quiet mode
|
-q, --quiet activates quiet mode
|
||||||
--no-warnings Ignore warnings
|
--no-warnings Ignore warnings
|
||||||
@@ -239,6 +258,10 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
files in the current directory to debug
|
files in the current directory to debug
|
||||||
problems
|
problems
|
||||||
--print-traffic Display sent and read HTTP traffic
|
--print-traffic Display sent and read HTTP traffic
|
||||||
|
-C, --call-home Contact the youtube-dl server for
|
||||||
|
debugging.
|
||||||
|
--no-call-home Do NOT contact the youtube-dl server for
|
||||||
|
debugging.
|
||||||
|
|
||||||
## Workarounds:
|
## Workarounds:
|
||||||
--encoding ENCODING Force the specified encoding (experimental)
|
--encoding ENCODING Force the specified encoding (experimental)
|
||||||
@@ -255,6 +278,8 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
--bidi-workaround Work around terminals that lack
|
--bidi-workaround Work around terminals that lack
|
||||||
bidirectional text support. Requires bidiv
|
bidirectional text support. Requires bidiv
|
||||||
or fribidi executable in PATH
|
or fribidi executable in PATH
|
||||||
|
--sleep-interval SECONDS Number of seconds to sleep before each
|
||||||
|
download.
|
||||||
|
|
||||||
## 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
|
||||||
@@ -263,10 +288,22 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
by extension for the extensions aac, m4a,
|
by extension for the extensions aac, m4a,
|
||||||
mp3, mp4, ogg, wav, webm. You can also use
|
mp3, mp4, ogg, wav, webm. You can also use
|
||||||
the special names "best", "bestvideo",
|
the special names "best", "bestvideo",
|
||||||
"bestaudio", "worst". By default, youtube-
|
"bestaudio", "worst". You can filter the
|
||||||
dl will pick the best quality. Use commas
|
video results by putting a condition in
|
||||||
to download multiple audio formats, such as
|
brackets, as in -f "best[height=720]" (or
|
||||||
-f
|
-f "[filesize>10M]"). This works for
|
||||||
|
filesize, height, width, tbr, abr, vbr, and
|
||||||
|
fps and the comparisons <, <=, >, >=, =, !=
|
||||||
|
. Formats for which the value is not known
|
||||||
|
are excluded unless you put a question mark
|
||||||
|
(?) after the operator. You can combine
|
||||||
|
format filters, so -f "[height <=?
|
||||||
|
720][tbr>500]" selects up to 720p videos
|
||||||
|
(or videos where the height is not known)
|
||||||
|
with a bitrate of at least 500 KBit/s. By
|
||||||
|
default, youtube-dl will pick the best
|
||||||
|
quality. Use commas to download multiple
|
||||||
|
audio formats, such as -f
|
||||||
136/137/mp4/bestvideo,140/m4a/bestaudio.
|
136/137/mp4/bestvideo,140/m4a/bestaudio.
|
||||||
You can merge the video and audio of two
|
You can merge the video and audio of two
|
||||||
formats into a single file using -f <video-
|
formats into a single file using -f <video-
|
||||||
@@ -300,7 +337,8 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
|
|
||||||
## Authentication Options:
|
## Authentication Options:
|
||||||
-u, --username USERNAME login with this account ID
|
-u, --username USERNAME login with this account ID
|
||||||
-p, --password PASSWORD account password
|
-p, --password PASSWORD account password. If this option is left
|
||||||
|
out, youtube-dl will ask interactively.
|
||||||
-2, --twofactor TWOFACTOR two-factor auth code
|
-2, --twofactor TWOFACTOR two-factor auth code
|
||||||
-n, --netrc use .netrc authentication data
|
-n, --netrc use .netrc authentication data
|
||||||
--video-password PASSWORD video password (vimeo, smotri)
|
--video-password PASSWORD video password (vimeo, smotri)
|
||||||
@@ -440,9 +478,15 @@ Apparently YouTube requires you to pass a CAPTCHA test if you download too much.
|
|||||||
|
|
||||||
Once the video is fully downloaded, use any video player, such as [vlc](http://www.videolan.org) or [mplayer](http://www.mplayerhq.hu/).
|
Once the video is fully downloaded, use any video player, such as [vlc](http://www.videolan.org) or [mplayer](http://www.mplayerhq.hu/).
|
||||||
|
|
||||||
### The links provided by youtube-dl -g are not working anymore
|
### I extracted a video URL with -g, but it does not play on another machine / in my webbrowser.
|
||||||
|
|
||||||
The URLs youtube-dl outputs require the downloader to have the correct cookies. Use the `--cookies` option to write the required cookies into a file, and advise your downloader to read cookies from that file. Some sites also require a common user agent to be used, use `--dump-user-agent` to see the one in use by youtube-dl.
|
It depends a lot on the service. In many cases, requests for the video (to download/play it) must come from the same IP address and with the same cookies. Use the `--cookies` option to write the required cookies into a file, and advise your downloader to read cookies from that file. Some sites also require a common user agent to be used, use `--dump-user-agent` to see the one in use by youtube-dl.
|
||||||
|
|
||||||
|
It may be beneficial to use IPv6; in some cases, the restrictions are only applied to IPv4. Some services (sometimes only for a subset of videos) do not restrict the video URL by IP address, cookie, or user-agent, but these are the exception rather than the rule.
|
||||||
|
|
||||||
|
Please bear in mind that some URL protocols are **not** supported by browsers out of the box, including RTMP. If you are using -g, your own downloader must support these as well.
|
||||||
|
|
||||||
|
If you want to play the video on a machine that is not running youtube-dl, you can relay the video content from the machine that runs youtube-dl. You can use `-o -` to let youtube-dl stream a video to stdout, or simply allow the player to download the files written by youtube-dl in turn.
|
||||||
|
|
||||||
### ERROR: no fmt_url_map or conn information found in video info
|
### ERROR: no fmt_url_map or conn information found in video info
|
||||||
|
|
||||||
@@ -477,6 +521,17 @@ To make a different directory work - either for ffmpeg, or for youtube-dl, or fo
|
|||||||
|
|
||||||
From then on, after restarting your shell, you will be able to access both youtube-dl and ffmpeg (and youtube-dl will be able to find ffmpeg) by simply typing `youtube-dl` or `ffmpeg`, no matter what directory you're in.
|
From then on, after restarting your shell, you will be able to access both youtube-dl and ffmpeg (and youtube-dl will be able to find ffmpeg) by simply typing `youtube-dl` or `ffmpeg`, no matter what directory you're in.
|
||||||
|
|
||||||
|
### How do I put downloads into a specific folder?
|
||||||
|
|
||||||
|
Use the `-o` to specify an [output template](#output-template), for example `-o "/home/user/videos/%(title)s-%(id)s.%(ext)s"`. If you want this for all of your downloads, put the option into your [configuration file](#configuration).
|
||||||
|
|
||||||
|
### How do I download a video starting with a `-` ?
|
||||||
|
|
||||||
|
Either prepend `http://www.youtube.com/watch?v=` or separate the ID from the options with `--`:
|
||||||
|
|
||||||
|
youtube-dl -- -wNyEUrxzFU
|
||||||
|
youtube-dl "http://www.youtube.com/watch?v=-wNyEUrxzFU"
|
||||||
|
|
||||||
### How can I detect whether a given URL is supported by youtube-dl?
|
### How can I detect whether a given URL is supported by youtube-dl?
|
||||||
|
|
||||||
For one, have a look at the [list of supported sites](docs/supportedsites). Note that it can sometimes happen that the site changes its URL scheme (say, from http://example.com/v/1234567 to http://example.com/v/1234567 ) and youtube-dl reports an URL of a service in that list as unsupported. In that case, simply report a bug.
|
For one, have a look at the [list of supported sites](docs/supportedsites). Note that it can sometimes happen that the site changes its URL scheme (say, from http://example.com/v/1234567 to http://example.com/v/1234567 ) and youtube-dl reports an URL of a service in that list as unsupported. In that case, simply report a bug.
|
||||||
@@ -557,7 +612,7 @@ If you want to add support for a new site, you can follow this quick list (assum
|
|||||||
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. 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.
|
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 [flake8](https://pypi.python.org/pypi/flake8).
|
||||||
9. When the tests pass, [add](http://git-scm.com/docs/git-add) the new files and [commit](http://git-scm.com/docs/git-commit) them and [push](http://git-scm.com/docs/git-push) the result, like this:
|
9. When the tests pass, [add](http://git-scm.com/docs/git-add) the new files and [commit](http://git-scm.com/docs/git-commit) them and [push](http://git-scm.com/docs/git-push) the result, like this:
|
||||||
|
|
||||||
$ git add youtube_dl/extractor/__init__.py
|
$ git add youtube_dl/extractor/__init__.py
|
||||||
@@ -625,7 +680,9 @@ with youtube_dl.YoutubeDL(ydl_opts) as ydl:
|
|||||||
|
|
||||||
Bugs and suggestions should be reported at: <https://github.com/rg3/youtube-dl/issues> . Unless you were prompted so or there is another pertinent reason (e.g. GitHub fails to accept the bug report), please do not send bug reports via personal email. For discussions, join us in the irc channel #youtube-dl on freenode.
|
Bugs and suggestions should be reported at: <https://github.com/rg3/youtube-dl/issues> . Unless you were prompted so or there is another pertinent reason (e.g. GitHub fails to accept the bug report), please do not send bug reports via personal email. For discussions, join us in the irc channel #youtube-dl on freenode.
|
||||||
|
|
||||||
Please include the full output of the command when run with `--verbose`. The output (including the first lines) contain important debugging information. Issues without the full output are often not reproducible and therefore do not get solved in short order, if ever.
|
**Please include the full output of youtube-dl when run with `-v`**.
|
||||||
|
|
||||||
|
The output (including the first lines) contain important debugging information. Issues without the full output are often not reproducible and therefore do not get solved in short order, if ever.
|
||||||
|
|
||||||
Please re-read your issue once again to avoid a couple of common mistakes (you can and should use this as a checklist):
|
Please re-read your issue once again to avoid a couple of common mistakes (you can and should use this as a checklist):
|
||||||
|
|
||||||
|
@@ -2,5 +2,5 @@
|
|||||||
universal = True
|
universal = True
|
||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
exclude = youtube_dl/extractor/__init__.py,devscripts/buildserver.py,setup.py,build
|
exclude = youtube_dl/extractor/__init__.py,devscripts/buildserver.py,setup.py,build,.git
|
||||||
ignore = E501
|
ignore = E501
|
||||||
|
@@ -140,7 +140,7 @@ def expect_info_dict(self, got_dict, expected_dict):
|
|||||||
# Are checkable fields missing from the test case definition?
|
# Are checkable fields missing from the test case definition?
|
||||||
test_info_dict = dict((key, value if not isinstance(value, compat_str) or len(value) < 250 else 'md5:' + md5(value))
|
test_info_dict = dict((key, value if not isinstance(value, compat_str) or len(value) < 250 else 'md5:' + md5(value))
|
||||||
for key, value in got_dict.items()
|
for key, value in got_dict.items()
|
||||||
if value and key in ('title', 'description', 'uploader', 'upload_date', 'timestamp', 'uploader_id', 'location'))
|
if value and key in ('id', 'title', 'description', 'uploader', 'upload_date', 'timestamp', 'uploader_id', 'location'))
|
||||||
missing_keys = set(test_info_dict.keys()) - set(expected_dict.keys())
|
missing_keys = set(test_info_dict.keys()) - set(expected_dict.keys())
|
||||||
if missing_keys:
|
if missing_keys:
|
||||||
def _repr(v):
|
def _repr(v):
|
||||||
@@ -148,9 +148,15 @@ def expect_info_dict(self, got_dict, expected_dict):
|
|||||||
return "'%s'" % v.replace('\\', '\\\\').replace("'", "\\'").replace('\n', '\\n')
|
return "'%s'" % v.replace('\\', '\\\\').replace("'", "\\'").replace('\n', '\\n')
|
||||||
else:
|
else:
|
||||||
return repr(v)
|
return repr(v)
|
||||||
info_dict_str = ''.join(
|
info_dict_str = ''
|
||||||
|
if len(missing_keys) != len(expected_dict):
|
||||||
|
info_dict_str += ''.join(
|
||||||
' %s: %s,\n' % (_repr(k), _repr(v))
|
' %s: %s,\n' % (_repr(k), _repr(v))
|
||||||
for k, v in test_info_dict.items())
|
for k, v in test_info_dict.items() if k not in missing_keys)
|
||||||
|
info_dict_str += '\n'
|
||||||
|
info_dict_str += ''.join(
|
||||||
|
' %s: %s,\n' % (_repr(k), _repr(test_info_dict[k]))
|
||||||
|
for k in missing_keys)
|
||||||
write_string(
|
write_string(
|
||||||
'\n\'info_dict\': {\n' + info_dict_str + '}\n', out=sys.stderr)
|
'\n\'info_dict\': {\n' + info_dict_str + '}\n', out=sys.stderr)
|
||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
|
@@ -281,6 +281,61 @@ class TestFormatSelection(unittest.TestCase):
|
|||||||
downloaded = ydl.downloaded_info_dicts[0]
|
downloaded = ydl.downloaded_info_dicts[0]
|
||||||
self.assertEqual(downloaded['format_id'], f1id)
|
self.assertEqual(downloaded['format_id'], f1id)
|
||||||
|
|
||||||
|
def test_format_filtering(self):
|
||||||
|
formats = [
|
||||||
|
{'format_id': 'A', 'filesize': 500, 'width': 1000},
|
||||||
|
{'format_id': 'B', 'filesize': 1000, 'width': 500},
|
||||||
|
{'format_id': 'C', 'filesize': 1000, 'width': 400},
|
||||||
|
{'format_id': 'D', 'filesize': 2000, 'width': 600},
|
||||||
|
{'format_id': 'E', 'filesize': 3000},
|
||||||
|
{'format_id': 'F'},
|
||||||
|
{'format_id': 'G', 'filesize': 1000000},
|
||||||
|
]
|
||||||
|
for f in formats:
|
||||||
|
f['url'] = 'http://_/'
|
||||||
|
f['ext'] = 'unknown'
|
||||||
|
info_dict = _make_result(formats)
|
||||||
|
|
||||||
|
ydl = YDL({'format': 'best[filesize<3000]'})
|
||||||
|
ydl.process_ie_result(info_dict)
|
||||||
|
downloaded = ydl.downloaded_info_dicts[0]
|
||||||
|
self.assertEqual(downloaded['format_id'], 'D')
|
||||||
|
|
||||||
|
ydl = YDL({'format': 'best[filesize<=3000]'})
|
||||||
|
ydl.process_ie_result(info_dict)
|
||||||
|
downloaded = ydl.downloaded_info_dicts[0]
|
||||||
|
self.assertEqual(downloaded['format_id'], 'E')
|
||||||
|
|
||||||
|
ydl = YDL({'format': 'best[filesize <= ? 3000]'})
|
||||||
|
ydl.process_ie_result(info_dict)
|
||||||
|
downloaded = ydl.downloaded_info_dicts[0]
|
||||||
|
self.assertEqual(downloaded['format_id'], 'F')
|
||||||
|
|
||||||
|
ydl = YDL({'format': 'best [filesize = 1000] [width>450]'})
|
||||||
|
ydl.process_ie_result(info_dict)
|
||||||
|
downloaded = ydl.downloaded_info_dicts[0]
|
||||||
|
self.assertEqual(downloaded['format_id'], 'B')
|
||||||
|
|
||||||
|
ydl = YDL({'format': 'best [filesize = 1000] [width!=450]'})
|
||||||
|
ydl.process_ie_result(info_dict)
|
||||||
|
downloaded = ydl.downloaded_info_dicts[0]
|
||||||
|
self.assertEqual(downloaded['format_id'], 'C')
|
||||||
|
|
||||||
|
ydl = YDL({'format': '[filesize>?1]'})
|
||||||
|
ydl.process_ie_result(info_dict)
|
||||||
|
downloaded = ydl.downloaded_info_dicts[0]
|
||||||
|
self.assertEqual(downloaded['format_id'], 'G')
|
||||||
|
|
||||||
|
ydl = YDL({'format': '[filesize<1M]'})
|
||||||
|
ydl.process_ie_result(info_dict)
|
||||||
|
downloaded = ydl.downloaded_info_dicts[0]
|
||||||
|
self.assertEqual(downloaded['format_id'], 'E')
|
||||||
|
|
||||||
|
ydl = YDL({'format': '[filesize<1MiB]'})
|
||||||
|
ydl.process_ie_result(info_dict)
|
||||||
|
downloaded = ydl.downloaded_info_dicts[0]
|
||||||
|
self.assertEqual(downloaded['format_id'], 'G')
|
||||||
|
|
||||||
def test_add_extra_info(self):
|
def test_add_extra_info(self):
|
||||||
test_dict = {
|
test_dict = {
|
||||||
'extractor': 'Foo',
|
'extractor': 'Foo',
|
||||||
|
@@ -14,7 +14,6 @@ from test.helper import gettestcases
|
|||||||
from youtube_dl.extractor import (
|
from youtube_dl.extractor import (
|
||||||
FacebookIE,
|
FacebookIE,
|
||||||
gen_extractors,
|
gen_extractors,
|
||||||
TwitchIE,
|
|
||||||
YoutubeIE,
|
YoutubeIE,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -72,18 +71,6 @@ class TestAllURLsMatching(unittest.TestCase):
|
|||||||
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'])
|
||||||
|
|
||||||
def test_twitch_channelid_matching(self):
|
|
||||||
self.assertTrue(TwitchIE.suitable('twitch.tv/vanillatv'))
|
|
||||||
self.assertTrue(TwitchIE.suitable('www.twitch.tv/vanillatv'))
|
|
||||||
self.assertTrue(TwitchIE.suitable('http://www.twitch.tv/vanillatv'))
|
|
||||||
self.assertTrue(TwitchIE.suitable('http://www.twitch.tv/vanillatv/'))
|
|
||||||
|
|
||||||
def test_twitch_videoid_matching(self):
|
|
||||||
self.assertTrue(TwitchIE.suitable('http://www.twitch.tv/vanillatv/b/328087483'))
|
|
||||||
|
|
||||||
def test_twitch_chapterid_matching(self):
|
|
||||||
self.assertTrue(TwitchIE.suitable('http://www.twitch.tv/tsm_theoddone/c/2349361'))
|
|
||||||
|
|
||||||
def test_youtube_extract(self):
|
def test_youtube_extract(self):
|
||||||
assertExtractId = lambda url, id: self.assertEqual(YoutubeIE.extract_id(url), id)
|
assertExtractId = lambda url, id: self.assertEqual(YoutubeIE.extract_id(url), id)
|
||||||
assertExtractId('http://www.youtube.com/watch?&v=BaW_jenozKc', 'BaW_jenozKc')
|
assertExtractId('http://www.youtube.com/watch?&v=BaW_jenozKc', 'BaW_jenozKc')
|
||||||
@@ -115,8 +102,6 @@ class TestAllURLsMatching(unittest.TestCase):
|
|||||||
self.assertMatch(':ythistory', ['youtube:history'])
|
self.assertMatch(':ythistory', ['youtube:history'])
|
||||||
self.assertMatch(':thedailyshow', ['ComedyCentralShows'])
|
self.assertMatch(':thedailyshow', ['ComedyCentralShows'])
|
||||||
self.assertMatch(':tds', ['ComedyCentralShows'])
|
self.assertMatch(':tds', ['ComedyCentralShows'])
|
||||||
self.assertMatch(':colbertreport', ['ComedyCentralShows'])
|
|
||||||
self.assertMatch(':cr', ['ComedyCentralShows'])
|
|
||||||
|
|
||||||
def test_vimeo_matching(self):
|
def test_vimeo_matching(self):
|
||||||
self.assertMatch('http://vimeo.com/channels/tributes', ['vimeo:channel'])
|
self.assertMatch('http://vimeo.com/channels/tributes', ['vimeo:channel'])
|
||||||
|
@@ -89,7 +89,7 @@ def generator(test_case):
|
|||||||
|
|
||||||
for tc in test_cases:
|
for tc in test_cases:
|
||||||
info_dict = tc.get('info_dict', {})
|
info_dict = tc.get('info_dict', {})
|
||||||
if not tc.get('file') and not (info_dict.get('id') and info_dict.get('ext')):
|
if 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:
|
||||||
@@ -116,7 +116,7 @@ def generator(test_case):
|
|||||||
expect_warnings(ydl, test_case.get('expected_warnings', []))
|
expect_warnings(ydl, test_case.get('expected_warnings', []))
|
||||||
|
|
||||||
def get_tc_filename(tc):
|
def get_tc_filename(tc):
|
||||||
return tc.get('file') or ydl.prepare_filename(tc.get('info_dict', {}))
|
return ydl.prepare_filename(tc.get('info_dict', {}))
|
||||||
|
|
||||||
res_dict = None
|
res_dict = None
|
||||||
|
|
||||||
|
72
test/test_http.py
Normal file
72
test/test_http.py
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
# Allow direct execution
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
from youtube_dl import YoutubeDL
|
||||||
|
from youtube_dl.compat import compat_http_server
|
||||||
|
import ssl
|
||||||
|
import threading
|
||||||
|
|
||||||
|
TEST_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPTestRequestHandler(compat_http_server.BaseHTTPRequestHandler):
|
||||||
|
def log_message(self, format, *args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def do_GET(self):
|
||||||
|
if self.path == '/video.html':
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-Type', 'text/html; charset=utf-8')
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(b'<html><video src="/vid.mp4" /></html>')
|
||||||
|
elif self.path == '/vid.mp4':
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-Type', 'video/mp4')
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(b'\x00\x00\x00\x00\x20\x66\x74[video]')
|
||||||
|
else:
|
||||||
|
assert False
|
||||||
|
|
||||||
|
|
||||||
|
class FakeLogger(object):
|
||||||
|
def debug(self, msg):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def warning(self, msg):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def error(self, msg):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TestHTTP(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
certfn = os.path.join(TEST_DIR, 'testcert.pem')
|
||||||
|
self.httpd = compat_http_server.HTTPServer(
|
||||||
|
('localhost', 0), HTTPTestRequestHandler)
|
||||||
|
self.httpd.socket = ssl.wrap_socket(
|
||||||
|
self.httpd.socket, certfile=certfn, server_side=True)
|
||||||
|
self.port = self.httpd.socket.getsockname()[1]
|
||||||
|
self.server_thread = threading.Thread(target=self.httpd.serve_forever)
|
||||||
|
self.server_thread.daemon = True
|
||||||
|
self.server_thread.start()
|
||||||
|
|
||||||
|
def test_nocheckcertificate(self):
|
||||||
|
if sys.version_info >= (2, 7, 9): # No certificate checking anyways
|
||||||
|
ydl = YoutubeDL({'logger': FakeLogger()})
|
||||||
|
self.assertRaises(
|
||||||
|
Exception,
|
||||||
|
ydl.extract_info, 'https://localhost:%d/video.html' % self.port)
|
||||||
|
|
||||||
|
ydl = YoutubeDL({'logger': FakeLogger(), 'nocheckcertificate': True})
|
||||||
|
r = ydl.extract_info('https://localhost:%d/video.html' % self.port)
|
||||||
|
self.assertEqual(r['url'], 'https://localhost:%d/vid.mp4' % self.port)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
95
test/test_jsinterp.py
Normal file
95
test/test_jsinterp.py
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
# Allow direct execution
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
from youtube_dl.jsinterp import JSInterpreter
|
||||||
|
|
||||||
|
|
||||||
|
class TestJSInterpreter(unittest.TestCase):
|
||||||
|
def test_basic(self):
|
||||||
|
jsi = JSInterpreter('function x(){;}')
|
||||||
|
self.assertEqual(jsi.call_function('x'), None)
|
||||||
|
|
||||||
|
jsi = JSInterpreter('function x3(){return 42;}')
|
||||||
|
self.assertEqual(jsi.call_function('x3'), 42)
|
||||||
|
|
||||||
|
def test_calc(self):
|
||||||
|
jsi = JSInterpreter('function x4(a){return 2*a+1;}')
|
||||||
|
self.assertEqual(jsi.call_function('x4', 3), 7)
|
||||||
|
|
||||||
|
def test_empty_return(self):
|
||||||
|
jsi = JSInterpreter('function f(){return; y()}')
|
||||||
|
self.assertEqual(jsi.call_function('f'), None)
|
||||||
|
|
||||||
|
def test_morespace(self):
|
||||||
|
jsi = JSInterpreter('function x (a) { return 2 * a + 1 ; }')
|
||||||
|
self.assertEqual(jsi.call_function('x', 3), 7)
|
||||||
|
|
||||||
|
jsi = JSInterpreter('function f () { x = 2 ; return x; }')
|
||||||
|
self.assertEqual(jsi.call_function('f'), 2)
|
||||||
|
|
||||||
|
def test_strange_chars(self):
|
||||||
|
jsi = JSInterpreter('function $_xY1 ($_axY1) { var $_axY2 = $_axY1 + 1; return $_axY2; }')
|
||||||
|
self.assertEqual(jsi.call_function('$_xY1', 20), 21)
|
||||||
|
|
||||||
|
def test_operators(self):
|
||||||
|
jsi = JSInterpreter('function f(){return 1 << 5;}')
|
||||||
|
self.assertEqual(jsi.call_function('f'), 32)
|
||||||
|
|
||||||
|
jsi = JSInterpreter('function f(){return 19 & 21;}')
|
||||||
|
self.assertEqual(jsi.call_function('f'), 17)
|
||||||
|
|
||||||
|
jsi = JSInterpreter('function f(){return 11 >> 2;}')
|
||||||
|
self.assertEqual(jsi.call_function('f'), 2)
|
||||||
|
|
||||||
|
def test_array_access(self):
|
||||||
|
jsi = JSInterpreter('function f(){var x = [1,2,3]; x[0] = 4; x[0] = 5; x[2] = 7; return x;}')
|
||||||
|
self.assertEqual(jsi.call_function('f'), [5, 2, 7])
|
||||||
|
|
||||||
|
def test_parens(self):
|
||||||
|
jsi = JSInterpreter('function f(){return (1) + (2) * ((( (( (((((3)))))) )) ));}')
|
||||||
|
self.assertEqual(jsi.call_function('f'), 7)
|
||||||
|
|
||||||
|
jsi = JSInterpreter('function f(){return (1 + 2) * 3;}')
|
||||||
|
self.assertEqual(jsi.call_function('f'), 9)
|
||||||
|
|
||||||
|
def test_assignments(self):
|
||||||
|
jsi = JSInterpreter('function f(){var x = 20; x = 30 + 1; return x;}')
|
||||||
|
self.assertEqual(jsi.call_function('f'), 31)
|
||||||
|
|
||||||
|
jsi = JSInterpreter('function f(){var x = 20; x += 30 + 1; return x;}')
|
||||||
|
self.assertEqual(jsi.call_function('f'), 51)
|
||||||
|
|
||||||
|
jsi = JSInterpreter('function f(){var x = 20; x -= 30 + 1; return x;}')
|
||||||
|
self.assertEqual(jsi.call_function('f'), -11)
|
||||||
|
|
||||||
|
def test_comments(self):
|
||||||
|
jsi = JSInterpreter('''
|
||||||
|
function x() {
|
||||||
|
var x = /* 1 + */ 2;
|
||||||
|
var y = /* 30
|
||||||
|
* 40 */ 50;
|
||||||
|
return x + y;
|
||||||
|
}
|
||||||
|
''')
|
||||||
|
self.assertEqual(jsi.call_function('x'), 52)
|
||||||
|
|
||||||
|
def test_precedence(self):
|
||||||
|
jsi = JSInterpreter('''
|
||||||
|
function x() {
|
||||||
|
var a = [10, 20, 30, 40, 50];
|
||||||
|
var b = 6;
|
||||||
|
a[0]=a[b%a.length];
|
||||||
|
return a;
|
||||||
|
}''')
|
||||||
|
self.assertEqual(jsi.call_function('x'), [20, 20, 30, 40, 50])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
@@ -28,6 +28,7 @@ from youtube_dl.utils import (
|
|||||||
fix_xml_ampersands,
|
fix_xml_ampersands,
|
||||||
InAdvancePagedList,
|
InAdvancePagedList,
|
||||||
intlist_to_bytes,
|
intlist_to_bytes,
|
||||||
|
is_html,
|
||||||
js_to_json,
|
js_to_json,
|
||||||
limit_length,
|
limit_length,
|
||||||
OnDemandPagedList,
|
OnDemandPagedList,
|
||||||
@@ -51,6 +52,7 @@ from youtube_dl.utils import (
|
|||||||
urlencode_postdata,
|
urlencode_postdata,
|
||||||
version_tuple,
|
version_tuple,
|
||||||
xpath_with_ns,
|
xpath_with_ns,
|
||||||
|
render_table,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -79,6 +81,10 @@ class TestUtil(unittest.TestCase):
|
|||||||
tests = '\u043a\u0438\u0440\u0438\u043b\u043b\u0438\u0446\u0430'
|
tests = '\u043a\u0438\u0440\u0438\u043b\u043b\u0438\u0446\u0430'
|
||||||
self.assertEqual(sanitize_filename(tests), tests)
|
self.assertEqual(sanitize_filename(tests), tests)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sanitize_filename('New World record at 0:12:34'),
|
||||||
|
'New World record at 0_12_34')
|
||||||
|
|
||||||
forbidden = '"\0\\/'
|
forbidden = '"\0\\/'
|
||||||
for fc in forbidden:
|
for fc in forbidden:
|
||||||
for fbc in forbidden:
|
for fbc in forbidden:
|
||||||
@@ -144,6 +150,7 @@ class TestUtil(unittest.TestCase):
|
|||||||
self.assertEqual(unified_strdate('8/7/2009'), '20090708')
|
self.assertEqual(unified_strdate('8/7/2009'), '20090708')
|
||||||
self.assertEqual(unified_strdate('Dec 14, 2012'), '20121214')
|
self.assertEqual(unified_strdate('Dec 14, 2012'), '20121214')
|
||||||
self.assertEqual(unified_strdate('2012/10/11 01:56:38 +0000'), '20121011')
|
self.assertEqual(unified_strdate('2012/10/11 01:56:38 +0000'), '20121011')
|
||||||
|
self.assertEqual(unified_strdate('1968 12 10'), '19681210')
|
||||||
self.assertEqual(unified_strdate('1968-12-10'), '19681210')
|
self.assertEqual(unified_strdate('1968-12-10'), '19681210')
|
||||||
self.assertEqual(unified_strdate('28/01/2014 21:00:00 +0100'), '20140128')
|
self.assertEqual(unified_strdate('28/01/2014 21:00:00 +0100'), '20140128')
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
@@ -208,6 +215,8 @@ class TestUtil(unittest.TestCase):
|
|||||||
|
|
||||||
def test_parse_duration(self):
|
def test_parse_duration(self):
|
||||||
self.assertEqual(parse_duration(None), None)
|
self.assertEqual(parse_duration(None), None)
|
||||||
|
self.assertEqual(parse_duration(False), None)
|
||||||
|
self.assertEqual(parse_duration('invalid'), None)
|
||||||
self.assertEqual(parse_duration('1'), 1)
|
self.assertEqual(parse_duration('1'), 1)
|
||||||
self.assertEqual(parse_duration('1337:12'), 80232)
|
self.assertEqual(parse_duration('1337:12'), 80232)
|
||||||
self.assertEqual(parse_duration('9:12:43'), 33163)
|
self.assertEqual(parse_duration('9:12:43'), 33163)
|
||||||
@@ -410,5 +419,31 @@ ffmpeg version 2.4.4 Copyright (c) 2000-2014 the FFmpeg ...'''), '2.4.4')
|
|||||||
self.assertTrue(age_restricted(18, 14))
|
self.assertTrue(age_restricted(18, 14))
|
||||||
self.assertFalse(age_restricted(18, 18))
|
self.assertFalse(age_restricted(18, 18))
|
||||||
|
|
||||||
|
def test_is_html(self):
|
||||||
|
self.assertFalse(is_html(b'\x49\x44\x43<html'))
|
||||||
|
self.assertTrue(is_html(b'<!DOCTYPE foo>\xaaa'))
|
||||||
|
self.assertTrue(is_html( # UTF-8 with BOM
|
||||||
|
b'\xef\xbb\xbf<!DOCTYPE foo>\xaaa'))
|
||||||
|
self.assertTrue(is_html( # UTF-16-LE
|
||||||
|
b'\xff\xfe<\x00h\x00t\x00m\x00l\x00>\x00\xe4\x00'
|
||||||
|
))
|
||||||
|
self.assertTrue(is_html( # UTF-16-BE
|
||||||
|
b'\xfe\xff\x00<\x00h\x00t\x00m\x00l\x00>\x00\xe4'
|
||||||
|
))
|
||||||
|
self.assertTrue(is_html( # UTF-32-BE
|
||||||
|
b'\x00\x00\xFE\xFF\x00\x00\x00<\x00\x00\x00h\x00\x00\x00t\x00\x00\x00m\x00\x00\x00l\x00\x00\x00>\x00\x00\x00\xe4'))
|
||||||
|
self.assertTrue(is_html( # UTF-32-LE
|
||||||
|
b'\xFF\xFE\x00\x00<\x00\x00\x00h\x00\x00\x00t\x00\x00\x00m\x00\x00\x00l\x00\x00\x00>\x00\x00\x00\xe4\x00\x00\x00'))
|
||||||
|
|
||||||
|
def test_render_table(self):
|
||||||
|
self.assertEqual(
|
||||||
|
render_table(
|
||||||
|
['a', 'bcd'],
|
||||||
|
[[123, 4], [9999, 51]]),
|
||||||
|
'a bcd\n'
|
||||||
|
'123 4\n'
|
||||||
|
'9999 51')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
52
test/testcert.pem
Normal file
52
test/testcert.pem
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDMF0bAzaHAdIyB
|
||||||
|
HRmnIp4vv40lGqEePmWqicCl0QZ0wsb5dNysSxSa7330M2QeQopGfdaUYF1uTcNp
|
||||||
|
Qx6ECgBSfg+RrOBI7r/u4F+sKX8MUXVaf/5QoBUrGNGSn/pp7HMGOuQqO6BVg4+h
|
||||||
|
A1ySSwUG8mZItLRry1ISyErmW8b9xlqfd97uLME/5tX+sMelRFjUbAx8A4CK58Ev
|
||||||
|
mMguHVTlXzx5RMdYcf1VScYcjlV/qA45uzP8zwI5aigfcmUD+tbGuQRhKxUhmw0J
|
||||||
|
aobtOR6+JSOAULW5gYa/egE4dWLwbyM6b6eFbdnjlQzEA1EW7ChMPAW/Mo83KyiP
|
||||||
|
tKMCSQulAgMBAAECggEALCfBDAexPjU5DNoh6bIorUXxIJzxTNzNHCdvgbCGiA54
|
||||||
|
BBKPh8s6qwazpnjT6WQWDIg/O5zZufqjE4wM9x4+0Zoqfib742ucJO9wY4way6x4
|
||||||
|
Clt0xzbLPabB+MoZ4H7ip+9n2+dImhe7pGdYyOHoNYeOL57BBi1YFW42Hj6u/8pd
|
||||||
|
63YCXisto3Rz1YvRQVjwsrS+cRKZlzAFQRviL30jav7Wh1aWEfcXxjj4zhm8pJdk
|
||||||
|
ITGtq6howz57M0NtX6hZnfe8ywzTnDFIGKIMA2cYHuYJcBh9bc4tCGubTvTKK9UE
|
||||||
|
8fM+f6UbfGqfpKCq1mcgs0XMoFDSzKS9+mSJn0+5JQKBgQD+OCKaeH3Yzw5zGnlw
|
||||||
|
XuQfMJGNcgNr+ImjmvzUAC2fAZUJLAcQueE5kzMv5Fmd+EFE2CEX1Vit3tg0SXvA
|
||||||
|
G+bq609doILHMA03JHnV1npO/YNIhG3AAtJlKYGxQNfWH9mflYj9mEui8ZFxG52o
|
||||||
|
zWhHYuifOjjZszUR+/eio6NPzwKBgQDNhUBTrT8LIX4SE/EFUiTlYmWIvOMgXYvN
|
||||||
|
8Cm3IRNQ/yyphZaXEU0eJzfX5uCDfSVOgd6YM/2pRah+t+1Hvey4H8e0GVTu5wMP
|
||||||
|
gkkqwKPGIR1YOmlw6ippqwvoJD7LuYrm6Q4D6e1PvkjwCq6lEndrOPmPrrXNd0JJ
|
||||||
|
XO60y3U2SwKBgQDLkyZarryQXxcCI6Q10Tc6pskYDMIit095PUbTeiUOXNT9GE28
|
||||||
|
Hi32ziLCakk9kCysNasii81MxtQ54tJ/f5iGbNMMddnkKl2a19Hc5LjjAm4cJzg/
|
||||||
|
98KGEhvyVqvAo5bBDZ06/rcrD+lZOzUglQS5jcIcqCIYa0LHWQ/wJLxFzwKBgFcZ
|
||||||
|
1SRhdSmDfUmuF+S4ZpistflYjC3IV5rk4NkS9HvMWaJS0nqdw4A3AMzItXgkjq4S
|
||||||
|
DkOVLTkTI5Do5HAWRv/VwC5M2hkR4NMu1VGAKSisGiKtRsirBWSZMEenLNHshbjN
|
||||||
|
Jrpz5rZ4H7NT46ZkCCZyFBpX4gb9NyOedjA7Via3AoGARF8RxbYjnEGGFuhnbrJB
|
||||||
|
FTPR0vaL4faY3lOgRZ8jOG9V2c9Hzi/y8a8TU4C11jnJSDqYCXBTd5XN28npYxtD
|
||||||
|
pjRsCwy6ze+yvYXPO7C978eMG3YRyj366NXUxnXN59ibwe/lxi2OD9z8J1LEdF6z
|
||||||
|
VJua1Wn8HKxnXMI61DhTCSo=
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIEEzCCAvugAwIBAgIJAK1haYi6gmSKMA0GCSqGSIb3DQEBCwUAMIGeMQswCQYD
|
||||||
|
VQQGEwJERTEMMAoGA1UECAwDTlJXMRQwEgYDVQQHDAtEdWVzc2VsZG9yZjEbMBkG
|
||||||
|
A1UECgwSeW91dHViZS1kbCBwcm9qZWN0MRkwFwYDVQQLDBB5b3V0dWJlLWRsIHRl
|
||||||
|
c3RzMRIwEAYDVQQDDAlsb2NhbGhvc3QxHzAdBgkqhkiG9w0BCQEWEHBoaWhhZ0Bw
|
||||||
|
aGloYWcuZGUwIBcNMTUwMTMwMDExNTA4WhgPMjExNTAxMDYwMTE1MDhaMIGeMQsw
|
||||||
|
CQYDVQQGEwJERTEMMAoGA1UECAwDTlJXMRQwEgYDVQQHDAtEdWVzc2VsZG9yZjEb
|
||||||
|
MBkGA1UECgwSeW91dHViZS1kbCBwcm9qZWN0MRkwFwYDVQQLDBB5b3V0dWJlLWRs
|
||||||
|
IHRlc3RzMRIwEAYDVQQDDAlsb2NhbGhvc3QxHzAdBgkqhkiG9w0BCQEWEHBoaWhh
|
||||||
|
Z0BwaGloYWcuZGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDMF0bA
|
||||||
|
zaHAdIyBHRmnIp4vv40lGqEePmWqicCl0QZ0wsb5dNysSxSa7330M2QeQopGfdaU
|
||||||
|
YF1uTcNpQx6ECgBSfg+RrOBI7r/u4F+sKX8MUXVaf/5QoBUrGNGSn/pp7HMGOuQq
|
||||||
|
O6BVg4+hA1ySSwUG8mZItLRry1ISyErmW8b9xlqfd97uLME/5tX+sMelRFjUbAx8
|
||||||
|
A4CK58EvmMguHVTlXzx5RMdYcf1VScYcjlV/qA45uzP8zwI5aigfcmUD+tbGuQRh
|
||||||
|
KxUhmw0JaobtOR6+JSOAULW5gYa/egE4dWLwbyM6b6eFbdnjlQzEA1EW7ChMPAW/
|
||||||
|
Mo83KyiPtKMCSQulAgMBAAGjUDBOMB0GA1UdDgQWBBTBUZoqhQkzHQ6xNgZfFxOd
|
||||||
|
ZEVt8TAfBgNVHSMEGDAWgBTBUZoqhQkzHQ6xNgZfFxOdZEVt8TAMBgNVHRMEBTAD
|
||||||
|
AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCUOCl3T/J9B08Z+ijfOJAtkbUaEHuVZb4x
|
||||||
|
5EpZSy2ZbkLvtsftMFieHVNXn9dDswQc5qjYStCC4o60LKw4M6Y63FRsAZ/DNaqb
|
||||||
|
PY3jyCyuugZ8/sNf50vHYkAcF7SQYqOQFQX4TQsNUk2xMJIt7H0ErQFmkf/u3dg6
|
||||||
|
cy89zkT462IwxzSG7NNhIlRkL9o5qg+Y1mF9eZA1B0rcL6hO24PPTHOd90HDChBu
|
||||||
|
SZ6XMi/LzYQSTf0Vg2R+uMIVlzSlkdcZ6sqVnnqeLL8dFyIa4e9sj/D4ZCYP8Mqe
|
||||||
|
Z73H5/NNhmwCHRqVUTgm307xblQaWGhwAiDkaRvRW2aJQ0qGEdZK
|
||||||
|
-----END CERTIFICATE-----
|
@@ -10,6 +10,7 @@ import io
|
|||||||
import itertools
|
import itertools
|
||||||
import json
|
import json
|
||||||
import locale
|
import locale
|
||||||
|
import operator
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import re
|
import re
|
||||||
@@ -24,6 +25,7 @@ if os.name == 'nt':
|
|||||||
import ctypes
|
import ctypes
|
||||||
|
|
||||||
from .compat import (
|
from .compat import (
|
||||||
|
compat_basestring,
|
||||||
compat_cookiejar,
|
compat_cookiejar,
|
||||||
compat_expanduser,
|
compat_expanduser,
|
||||||
compat_http_client,
|
compat_http_client,
|
||||||
@@ -49,15 +51,19 @@ from .utils import (
|
|||||||
make_HTTPS_handler,
|
make_HTTPS_handler,
|
||||||
MaxDownloadsReached,
|
MaxDownloadsReached,
|
||||||
PagedList,
|
PagedList,
|
||||||
|
parse_filesize,
|
||||||
PostProcessingError,
|
PostProcessingError,
|
||||||
platform_name,
|
platform_name,
|
||||||
preferredencoding,
|
preferredencoding,
|
||||||
|
render_table,
|
||||||
SameFileError,
|
SameFileError,
|
||||||
sanitize_filename,
|
sanitize_filename,
|
||||||
|
std_headers,
|
||||||
subtitles_filename,
|
subtitles_filename,
|
||||||
takewhile_inclusive,
|
takewhile_inclusive,
|
||||||
UnavailableVideoError,
|
UnavailableVideoError,
|
||||||
url_basename,
|
url_basename,
|
||||||
|
version_tuple,
|
||||||
write_json_file,
|
write_json_file,
|
||||||
write_string,
|
write_string,
|
||||||
YoutubeDLHandler,
|
YoutubeDLHandler,
|
||||||
@@ -70,6 +76,7 @@ from .extractor import get_info_extractor, gen_extractors
|
|||||||
from .downloader import get_suitable_downloader
|
from .downloader import get_suitable_downloader
|
||||||
from .downloader.rtmp import rtmpdump_version
|
from .downloader.rtmp import rtmpdump_version
|
||||||
from .postprocessor import (
|
from .postprocessor import (
|
||||||
|
FFmpegFixupM4aPP,
|
||||||
FFmpegFixupStretchedPP,
|
FFmpegFixupStretchedPP,
|
||||||
FFmpegMergerPP,
|
FFmpegMergerPP,
|
||||||
FFmpegPostProcessor,
|
FFmpegPostProcessor,
|
||||||
@@ -131,6 +138,7 @@ class YoutubeDL(object):
|
|||||||
nooverwrites: Prevent overwriting files.
|
nooverwrites: Prevent overwriting files.
|
||||||
playliststart: Playlist item to start at.
|
playliststart: Playlist item to start at.
|
||||||
playlistend: Playlist item to end at.
|
playlistend: Playlist item to end at.
|
||||||
|
playlist_items: Specific indices of playlist to download.
|
||||||
playlistreverse: Download playlist items in reverse order.
|
playlistreverse: Download playlist items in reverse order.
|
||||||
matchtitle: Download only matching titles.
|
matchtitle: Download only matching titles.
|
||||||
rejecttitle: Reject downloads for matching titles.
|
rejecttitle: Reject downloads for matching titles.
|
||||||
@@ -140,6 +148,7 @@ class YoutubeDL(object):
|
|||||||
writeinfojson: Write the video description to a .info.json file
|
writeinfojson: Write the video description to a .info.json file
|
||||||
writeannotations: Write the video annotations to a .annotations.xml file
|
writeannotations: Write the video annotations to a .annotations.xml file
|
||||||
writethumbnail: Write the thumbnail image to a file
|
writethumbnail: Write the thumbnail image to a file
|
||||||
|
write_all_thumbnails: Write all thumbnail formats to files
|
||||||
writesubtitles: Write the video subtitles to a file
|
writesubtitles: Write the video subtitles to a file
|
||||||
writeautomaticsub: Write the automatic subtitles to a file
|
writeautomaticsub: Write the automatic subtitles to a file
|
||||||
allsubtitles: Downloads all the subtitles of the video
|
allsubtitles: Downloads all the subtitles of the video
|
||||||
@@ -190,11 +199,12 @@ class YoutubeDL(object):
|
|||||||
postprocessor.
|
postprocessor.
|
||||||
progress_hooks: A list of functions that get called on download
|
progress_hooks: A list of functions that get called on download
|
||||||
progress, with a dictionary with the entries
|
progress, with a dictionary with the entries
|
||||||
* filename: The final filename
|
* status: One of "downloading" and "finished".
|
||||||
* status: One of "downloading" and "finished"
|
Check this first and ignore unknown values.
|
||||||
|
|
||||||
The dict may also have some of the following entries:
|
|
||||||
|
|
||||||
|
If status is one of "downloading" or "finished", the
|
||||||
|
following properties may also be present:
|
||||||
|
* filename: The final filename (always present)
|
||||||
* downloaded_bytes: Bytes on disk
|
* downloaded_bytes: Bytes on disk
|
||||||
* total_bytes: Size of the whole file, None if unknown
|
* total_bytes: Size of the whole file, None if unknown
|
||||||
* tmpfilename: The filename we're currently writing to
|
* tmpfilename: The filename we're currently writing to
|
||||||
@@ -210,14 +220,21 @@ class YoutubeDL(object):
|
|||||||
- "never": do nothing
|
- "never": do nothing
|
||||||
- "warn": only emit a warning
|
- "warn": only emit a warning
|
||||||
- "detect_or_warn": check whether we can do anything
|
- "detect_or_warn": check whether we can do anything
|
||||||
about it, warn otherwise
|
about it, warn otherwise (default)
|
||||||
source_address: (Experimental) Client-side IP address to bind to.
|
source_address: (Experimental) Client-side IP address to bind to.
|
||||||
|
call_home: Boolean, true iff we are allowed to contact the
|
||||||
|
youtube-dl servers for debugging.
|
||||||
|
sleep_interval: Number of seconds to sleep before each download.
|
||||||
|
external_downloader: Executable of the external downloader to call.
|
||||||
|
listformats: Print an overview of available video formats and exit.
|
||||||
|
list_thumbnails: Print a table of all thumbnails and exit.
|
||||||
|
|
||||||
|
|
||||||
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:
|
||||||
nopart, updatetime, buffersize, ratelimit, min_filesize, max_filesize, test,
|
nopart, updatetime, buffersize, ratelimit, min_filesize, max_filesize, test,
|
||||||
noresizebuffer, retries, continuedl, noprogress, consoletitle
|
noresizebuffer, retries, continuedl, noprogress, consoletitle,
|
||||||
|
xattr_set_filesize.
|
||||||
|
|
||||||
The following options are used by the post processors:
|
The following options are used by the post processors:
|
||||||
prefer_ffmpeg: If True, use ffmpeg instead of avconv if both are available,
|
prefer_ffmpeg: If True, use ffmpeg instead of avconv if both are available,
|
||||||
@@ -527,6 +544,11 @@ class YoutubeDL(object):
|
|||||||
outtmpl = self.params.get('outtmpl', DEFAULT_OUTTMPL)
|
outtmpl = self.params.get('outtmpl', DEFAULT_OUTTMPL)
|
||||||
tmpl = compat_expanduser(outtmpl)
|
tmpl = compat_expanduser(outtmpl)
|
||||||
filename = tmpl % template_dict
|
filename = tmpl % template_dict
|
||||||
|
# Temporary fix for #4787
|
||||||
|
# 'Treat' all problem characters by passing filename through preferredencoding
|
||||||
|
# to workaround encoding issues with subprocess on python2 @ Windows
|
||||||
|
if sys.version_info < (3, 0) and sys.platform == 'win32':
|
||||||
|
filename = encodeFilename(filename, True).decode(preferredencoding())
|
||||||
return filename
|
return filename
|
||||||
except ValueError as err:
|
except ValueError as err:
|
||||||
self.report_error('Error in output template: ' + str(err) + ' (encoding: ' + repr(preferredencoding()) + ')')
|
self.report_error('Error in output template: ' + str(err) + ' (encoding: ' + repr(preferredencoding()) + ')')
|
||||||
@@ -690,15 +712,38 @@ class YoutubeDL(object):
|
|||||||
if playlistend == -1:
|
if playlistend == -1:
|
||||||
playlistend = None
|
playlistend = None
|
||||||
|
|
||||||
|
playlistitems_str = self.params.get('playlist_items', None)
|
||||||
|
playlistitems = None
|
||||||
|
if playlistitems_str is not None:
|
||||||
|
def iter_playlistitems(format):
|
||||||
|
for string_segment in format.split(','):
|
||||||
|
if '-' in string_segment:
|
||||||
|
start, end = string_segment.split('-')
|
||||||
|
for item in range(int(start), int(end) + 1):
|
||||||
|
yield int(item)
|
||||||
|
else:
|
||||||
|
yield int(string_segment)
|
||||||
|
playlistitems = iter_playlistitems(playlistitems_str)
|
||||||
|
|
||||||
ie_entries = ie_result['entries']
|
ie_entries = ie_result['entries']
|
||||||
if isinstance(ie_entries, list):
|
if isinstance(ie_entries, list):
|
||||||
n_all_entries = len(ie_entries)
|
n_all_entries = len(ie_entries)
|
||||||
|
if playlistitems:
|
||||||
|
entries = [ie_entries[i - 1] for i in playlistitems]
|
||||||
|
else:
|
||||||
entries = ie_entries[playliststart:playlistend]
|
entries = ie_entries[playliststart:playlistend]
|
||||||
n_entries = len(entries)
|
n_entries = len(entries)
|
||||||
self.to_screen(
|
self.to_screen(
|
||||||
"[%s] playlist %s: Collected %d video ids (downloading %d of them)" %
|
"[%s] playlist %s: Collected %d video ids (downloading %d of them)" %
|
||||||
(ie_result['extractor'], playlist, n_all_entries, n_entries))
|
(ie_result['extractor'], playlist, n_all_entries, n_entries))
|
||||||
elif isinstance(ie_entries, PagedList):
|
elif isinstance(ie_entries, PagedList):
|
||||||
|
if playlistitems:
|
||||||
|
entries = []
|
||||||
|
for item in playlistitems:
|
||||||
|
entries.extend(ie_entries.getslice(
|
||||||
|
item - 1, item
|
||||||
|
))
|
||||||
|
else:
|
||||||
entries = ie_entries.getslice(
|
entries = ie_entries.getslice(
|
||||||
playliststart, playlistend)
|
playliststart, playlistend)
|
||||||
n_entries = len(entries)
|
n_entries = len(entries)
|
||||||
@@ -706,6 +751,10 @@ class YoutubeDL(object):
|
|||||||
"[%s] playlist %s: Downloading %d videos" %
|
"[%s] playlist %s: Downloading %d videos" %
|
||||||
(ie_result['extractor'], playlist, n_entries))
|
(ie_result['extractor'], playlist, n_entries))
|
||||||
else: # iterable
|
else: # iterable
|
||||||
|
if playlistitems:
|
||||||
|
entry_list = list(ie_entries)
|
||||||
|
entries = [entry_list[i - 1] for i in playlistitems]
|
||||||
|
else:
|
||||||
entries = list(itertools.islice(
|
entries = list(itertools.islice(
|
||||||
ie_entries, playliststart, playlistend))
|
ie_entries, playliststart, playlistend))
|
||||||
n_entries = len(entries)
|
n_entries = len(entries)
|
||||||
@@ -765,7 +814,59 @@ class YoutubeDL(object):
|
|||||||
else:
|
else:
|
||||||
raise Exception('Invalid result type: %s' % result_type)
|
raise Exception('Invalid result type: %s' % result_type)
|
||||||
|
|
||||||
|
def _apply_format_filter(self, format_spec, available_formats):
|
||||||
|
" Returns a tuple of the remaining format_spec and filtered formats "
|
||||||
|
|
||||||
|
OPERATORS = {
|
||||||
|
'<': operator.lt,
|
||||||
|
'<=': operator.le,
|
||||||
|
'>': operator.gt,
|
||||||
|
'>=': operator.ge,
|
||||||
|
'=': operator.eq,
|
||||||
|
'!=': operator.ne,
|
||||||
|
}
|
||||||
|
operator_rex = re.compile(r'''(?x)\s*\[
|
||||||
|
(?P<key>width|height|tbr|abr|vbr|filesize|fps)
|
||||||
|
\s*(?P<op>%s)(?P<none_inclusive>\s*\?)?\s*
|
||||||
|
(?P<value>[0-9.]+(?:[kKmMgGtTpPeEzZyY]i?[Bb]?)?)
|
||||||
|
\]$
|
||||||
|
''' % '|'.join(map(re.escape, OPERATORS.keys())))
|
||||||
|
m = operator_rex.search(format_spec)
|
||||||
|
if not m:
|
||||||
|
raise ValueError('Invalid format specification %r' % format_spec)
|
||||||
|
|
||||||
|
try:
|
||||||
|
comparison_value = int(m.group('value'))
|
||||||
|
except ValueError:
|
||||||
|
comparison_value = parse_filesize(m.group('value'))
|
||||||
|
if comparison_value is None:
|
||||||
|
comparison_value = parse_filesize(m.group('value') + 'B')
|
||||||
|
if comparison_value is None:
|
||||||
|
raise ValueError(
|
||||||
|
'Invalid value %r in format specification %r' % (
|
||||||
|
m.group('value'), format_spec))
|
||||||
|
op = OPERATORS[m.group('op')]
|
||||||
|
|
||||||
|
def _filter(f):
|
||||||
|
actual_value = f.get(m.group('key'))
|
||||||
|
if actual_value is None:
|
||||||
|
return m.group('none_inclusive')
|
||||||
|
return op(actual_value, comparison_value)
|
||||||
|
new_formats = [f for f in available_formats if _filter(f)]
|
||||||
|
|
||||||
|
new_format_spec = format_spec[:-len(m.group(0))]
|
||||||
|
if not new_format_spec:
|
||||||
|
new_format_spec = 'best'
|
||||||
|
|
||||||
|
return (new_format_spec, new_formats)
|
||||||
|
|
||||||
def select_format(self, format_spec, available_formats):
|
def select_format(self, format_spec, available_formats):
|
||||||
|
while format_spec.endswith(']'):
|
||||||
|
format_spec, available_formats = self._apply_format_filter(
|
||||||
|
format_spec, available_formats)
|
||||||
|
if not available_formats:
|
||||||
|
return None
|
||||||
|
|
||||||
if format_spec == 'best' or format_spec is None:
|
if format_spec == 'best' or format_spec is None:
|
||||||
return available_formats[-1]
|
return available_formats[-1]
|
||||||
elif format_spec == 'worst':
|
elif format_spec == 'worst':
|
||||||
@@ -805,6 +906,42 @@ class YoutubeDL(object):
|
|||||||
return matches[-1]
|
return matches[-1]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def _calc_headers(self, info_dict):
|
||||||
|
res = std_headers.copy()
|
||||||
|
|
||||||
|
add_headers = info_dict.get('http_headers')
|
||||||
|
if add_headers:
|
||||||
|
res.update(add_headers)
|
||||||
|
|
||||||
|
cookies = self._calc_cookies(info_dict)
|
||||||
|
if cookies:
|
||||||
|
res['Cookie'] = cookies
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
def _calc_cookies(self, info_dict):
|
||||||
|
class _PseudoRequest(object):
|
||||||
|
def __init__(self, url):
|
||||||
|
self.url = url
|
||||||
|
self.headers = {}
|
||||||
|
self.unverifiable = False
|
||||||
|
|
||||||
|
def add_unredirected_header(self, k, v):
|
||||||
|
self.headers[k] = v
|
||||||
|
|
||||||
|
def get_full_url(self):
|
||||||
|
return self.url
|
||||||
|
|
||||||
|
def is_unverifiable(self):
|
||||||
|
return self.unverifiable
|
||||||
|
|
||||||
|
def has_header(self, h):
|
||||||
|
return h in self.headers
|
||||||
|
|
||||||
|
pr = _PseudoRequest(info_dict['url'])
|
||||||
|
self.cookiejar.add_cookie_header(pr)
|
||||||
|
return pr.headers.get('Cookie')
|
||||||
|
|
||||||
def process_video_result(self, info_dict, download=True):
|
def process_video_result(self, info_dict, download=True):
|
||||||
assert info_dict.get('_type', 'video') == 'video'
|
assert info_dict.get('_type', 'video') == 'video'
|
||||||
|
|
||||||
@@ -819,9 +956,14 @@ class YoutubeDL(object):
|
|||||||
info_dict['playlist_index'] = None
|
info_dict['playlist_index'] = None
|
||||||
|
|
||||||
thumbnails = info_dict.get('thumbnails')
|
thumbnails = info_dict.get('thumbnails')
|
||||||
|
if thumbnails is None:
|
||||||
|
thumbnail = info_dict.get('thumbnail')
|
||||||
|
if thumbnail:
|
||||||
|
info_dict['thumbnails'] = thumbnails = [{'url': thumbnail}]
|
||||||
if thumbnails:
|
if thumbnails:
|
||||||
thumbnails.sort(key=lambda t: (
|
thumbnails.sort(key=lambda t: (
|
||||||
t.get('width'), t.get('height'), t.get('url')))
|
t.get('preference'), t.get('width'), t.get('height'),
|
||||||
|
t.get('id'), t.get('url')))
|
||||||
for t in thumbnails:
|
for t in thumbnails:
|
||||||
if 'width' in t and 'height' in t:
|
if 'width' in t and 'height' in t:
|
||||||
t['resolution'] = '%dx%d' % (t['width'], t['height'])
|
t['resolution'] = '%dx%d' % (t['width'], t['height'])
|
||||||
@@ -873,6 +1015,11 @@ class YoutubeDL(object):
|
|||||||
# Automatically determine file extension if missing
|
# Automatically determine file extension if missing
|
||||||
if 'ext' not in format:
|
if 'ext' not in format:
|
||||||
format['ext'] = determine_ext(format['url']).lower()
|
format['ext'] = determine_ext(format['url']).lower()
|
||||||
|
# Add HTTP headers, so that external programs can use them from the
|
||||||
|
# json output
|
||||||
|
full_format_info = info_dict.copy()
|
||||||
|
full_format_info.update(format)
|
||||||
|
format['http_headers'] = self._calc_headers(full_format_info)
|
||||||
|
|
||||||
format_limit = self.params.get('format_limit', None)
|
format_limit = self.params.get('format_limit', None)
|
||||||
if format_limit:
|
if format_limit:
|
||||||
@@ -888,9 +1035,12 @@ class YoutubeDL(object):
|
|||||||
# element in the 'formats' field in info_dict is info_dict itself,
|
# element in the 'formats' field in info_dict is info_dict itself,
|
||||||
# wich can't be exported to json
|
# wich can't be exported to json
|
||||||
info_dict['formats'] = formats
|
info_dict['formats'] = formats
|
||||||
if self.params.get('listformats', None):
|
if self.params.get('listformats'):
|
||||||
self.list_formats(info_dict)
|
self.list_formats(info_dict)
|
||||||
return
|
return
|
||||||
|
if self.params.get('list_thumbnails'):
|
||||||
|
self.list_thumbnails(info_dict)
|
||||||
|
return
|
||||||
|
|
||||||
req_format = self.params.get('format')
|
req_format = self.params.get('format')
|
||||||
if req_format is None:
|
if req_format is None:
|
||||||
@@ -925,7 +1075,8 @@ class YoutubeDL(object):
|
|||||||
selected_format = {
|
selected_format = {
|
||||||
'requested_formats': formats_info,
|
'requested_formats': formats_info,
|
||||||
'format': rf,
|
'format': rf,
|
||||||
'ext': formats_info[0]['ext'],
|
'format_id': '%s+%s' % (formats_info[0].get('format_id'),
|
||||||
|
formats_info[1].get('format_id')),
|
||||||
'width': formats_info[0].get('width'),
|
'width': formats_info[0].get('width'),
|
||||||
'height': formats_info[0].get('height'),
|
'height': formats_info[0].get('height'),
|
||||||
'resolution': formats_info[0].get('resolution'),
|
'resolution': formats_info[0].get('resolution'),
|
||||||
@@ -986,7 +1137,7 @@ class YoutubeDL(object):
|
|||||||
|
|
||||||
self._num_downloads += 1
|
self._num_downloads += 1
|
||||||
|
|
||||||
filename = self.prepare_filename(info_dict)
|
info_dict['_filename'] = filename = self.prepare_filename(info_dict)
|
||||||
|
|
||||||
# Forced printings
|
# Forced printings
|
||||||
if self.params.get('forcetitle', False):
|
if self.params.get('forcetitle', False):
|
||||||
@@ -1011,10 +1162,7 @@ class YoutubeDL(object):
|
|||||||
if self.params.get('forceformat', False):
|
if self.params.get('forceformat', False):
|
||||||
self.to_stdout(info_dict['format'])
|
self.to_stdout(info_dict['format'])
|
||||||
if self.params.get('forcejson', False):
|
if self.params.get('forcejson', False):
|
||||||
info_dict['_filename'] = filename
|
|
||||||
self.to_stdout(json.dumps(info_dict))
|
self.to_stdout(json.dumps(info_dict))
|
||||||
if self.params.get('dump_single_json', False):
|
|
||||||
info_dict['_filename'] = filename
|
|
||||||
|
|
||||||
# Do nothing else if in simulate mode
|
# Do nothing else if in simulate mode
|
||||||
if self.params.get('simulate', False):
|
if self.params.get('simulate', False):
|
||||||
@@ -1097,38 +1245,18 @@ class YoutubeDL(object):
|
|||||||
self.report_error('Cannot write metadata to JSON file ' + infofn)
|
self.report_error('Cannot write metadata to JSON file ' + infofn)
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.params.get('writethumbnail', False):
|
self._write_thumbnails(info_dict, filename)
|
||||||
if info_dict.get('thumbnail') is not None:
|
|
||||||
thumb_format = determine_ext(info_dict['thumbnail'], 'jpg')
|
|
||||||
thumb_filename = os.path.splitext(filename)[0] + '.' + thumb_format
|
|
||||||
if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(thumb_filename)):
|
|
||||||
self.to_screen('[%s] %s: Thumbnail is already present' %
|
|
||||||
(info_dict['extractor'], info_dict['id']))
|
|
||||||
else:
|
|
||||||
self.to_screen('[%s] %s: Downloading thumbnail ...' %
|
|
||||||
(info_dict['extractor'], info_dict['id']))
|
|
||||||
try:
|
|
||||||
uf = self.urlopen(info_dict['thumbnail'])
|
|
||||||
with open(thumb_filename, 'wb') as thumbf:
|
|
||||||
shutil.copyfileobj(uf, thumbf)
|
|
||||||
self.to_screen('[%s] %s: Writing thumbnail to: %s' %
|
|
||||||
(info_dict['extractor'], info_dict['id'], thumb_filename))
|
|
||||||
except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
|
|
||||||
self.report_warning('Unable to download thumbnail "%s": %s' %
|
|
||||||
(info_dict['thumbnail'], compat_str(err)))
|
|
||||||
|
|
||||||
if not self.params.get('skip_download', False):
|
if not self.params.get('skip_download', False):
|
||||||
if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(filename)):
|
|
||||||
success = True
|
|
||||||
else:
|
|
||||||
try:
|
try:
|
||||||
def dl(name, info):
|
def dl(name, info):
|
||||||
fd = get_suitable_downloader(info)(self, self.params)
|
fd = get_suitable_downloader(info, self.params)(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'):
|
if self.params.get('verbose'):
|
||||||
self.to_stdout('[debug] Invoking downloader on %r' % info.get('url'))
|
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
|
||||||
@@ -1164,11 +1292,12 @@ class YoutubeDL(object):
|
|||||||
|
|
||||||
if success:
|
if success:
|
||||||
# Fixup content
|
# Fixup content
|
||||||
stretched_ratio = info_dict.get('stretched_ratio')
|
|
||||||
if stretched_ratio is not None and stretched_ratio != 1:
|
|
||||||
fixup_policy = self.params.get('fixup')
|
fixup_policy = self.params.get('fixup')
|
||||||
if fixup_policy is None:
|
if fixup_policy is None:
|
||||||
fixup_policy = 'detect_or_warn'
|
fixup_policy = 'detect_or_warn'
|
||||||
|
|
||||||
|
stretched_ratio = info_dict.get('stretched_ratio')
|
||||||
|
if stretched_ratio is not None and stretched_ratio != 1:
|
||||||
if fixup_policy == 'warn':
|
if fixup_policy == 'warn':
|
||||||
self.report_warning('%s: Non-uniform pixel ratio (%s)' % (
|
self.report_warning('%s: Non-uniform pixel ratio (%s)' % (
|
||||||
info_dict['id'], stretched_ratio))
|
info_dict['id'], stretched_ratio))
|
||||||
@@ -1182,7 +1311,23 @@ class YoutubeDL(object):
|
|||||||
'%s: Non-uniform pixel ratio (%s). Install ffmpeg or avconv to fix this automatically.' % (
|
'%s: Non-uniform pixel ratio (%s). Install ffmpeg or avconv to fix this automatically.' % (
|
||||||
info_dict['id'], stretched_ratio))
|
info_dict['id'], stretched_ratio))
|
||||||
else:
|
else:
|
||||||
assert fixup_policy == 'ignore'
|
assert fixup_policy in ('ignore', 'never')
|
||||||
|
|
||||||
|
if info_dict.get('requested_formats') is None and info_dict.get('container') == 'm4a_dash':
|
||||||
|
if fixup_policy == 'warn':
|
||||||
|
self.report_warning('%s: writing DASH m4a. Only some players support this container.' % (
|
||||||
|
info_dict['id']))
|
||||||
|
elif fixup_policy == 'detect_or_warn':
|
||||||
|
fixup_pp = FFmpegFixupM4aPP(self)
|
||||||
|
if fixup_pp.available:
|
||||||
|
info_dict.setdefault('__postprocessors', [])
|
||||||
|
info_dict['__postprocessors'].append(fixup_pp)
|
||||||
|
else:
|
||||||
|
self.report_warning(
|
||||||
|
'%s: writing DASH m4a. Only some players support this container. Install ffmpeg or avconv to fix this automatically.' % (
|
||||||
|
info_dict['id']))
|
||||||
|
else:
|
||||||
|
assert fixup_policy in ('ignore', 'never')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.post_process(filename, info_dict)
|
self.post_process(filename, info_dict)
|
||||||
@@ -1232,14 +1377,15 @@ class YoutubeDL(object):
|
|||||||
"""Run all the postprocessors on the given file."""
|
"""Run all the postprocessors on the given file."""
|
||||||
info = dict(ie_info)
|
info = dict(ie_info)
|
||||||
info['filepath'] = filename
|
info['filepath'] = filename
|
||||||
keep_video = None
|
|
||||||
pps_chain = []
|
pps_chain = []
|
||||||
if ie_info.get('__postprocessors') is not None:
|
if ie_info.get('__postprocessors') is not None:
|
||||||
pps_chain.extend(ie_info['__postprocessors'])
|
pps_chain.extend(ie_info['__postprocessors'])
|
||||||
pps_chain.extend(self._pps)
|
pps_chain.extend(self._pps)
|
||||||
for pp in pps_chain:
|
for pp in pps_chain:
|
||||||
|
keep_video = None
|
||||||
|
old_filename = info['filepath']
|
||||||
try:
|
try:
|
||||||
keep_video_wish, new_info = pp.run(info)
|
keep_video_wish, info = pp.run(info)
|
||||||
if keep_video_wish is not None:
|
if keep_video_wish is not None:
|
||||||
if keep_video_wish:
|
if keep_video_wish:
|
||||||
keep_video = keep_video_wish
|
keep_video = keep_video_wish
|
||||||
@@ -1250,8 +1396,8 @@ class YoutubeDL(object):
|
|||||||
self.report_error(e.msg)
|
self.report_error(e.msg)
|
||||||
if keep_video is False and not self.params.get('keepvideo', False):
|
if keep_video is False and not self.params.get('keepvideo', False):
|
||||||
try:
|
try:
|
||||||
self.to_screen('Deleting original file %s (pass -k to keep)' % filename)
|
self.to_screen('Deleting original file %s (pass -k to keep)' % old_filename)
|
||||||
os.remove(encodeFilename(filename))
|
os.remove(encodeFilename(old_filename))
|
||||||
except (IOError, OSError):
|
except (IOError, OSError):
|
||||||
self.report_warning('Unable to remove downloaded video file')
|
self.report_warning('Unable to remove downloaded video file')
|
||||||
|
|
||||||
@@ -1383,9 +1529,27 @@ class YoutubeDL(object):
|
|||||||
header_line = line({
|
header_line = line({
|
||||||
'format_id': 'format code', 'ext': 'extension',
|
'format_id': 'format code', 'ext': 'extension',
|
||||||
'resolution': 'resolution', 'format_note': 'note'}, idlen=idlen)
|
'resolution': 'resolution', 'format_note': 'note'}, idlen=idlen)
|
||||||
self.to_screen('[info] Available formats for %s:\n%s\n%s' %
|
self.to_screen(
|
||||||
|
'[info] Available formats for %s:\n%s\n%s' %
|
||||||
(info_dict['id'], header_line, '\n'.join(formats_s)))
|
(info_dict['id'], header_line, '\n'.join(formats_s)))
|
||||||
|
|
||||||
|
def list_thumbnails(self, info_dict):
|
||||||
|
thumbnails = info_dict.get('thumbnails')
|
||||||
|
if not thumbnails:
|
||||||
|
tn_url = info_dict.get('thumbnail')
|
||||||
|
if tn_url:
|
||||||
|
thumbnails = [{'id': '0', 'url': tn_url}]
|
||||||
|
else:
|
||||||
|
self.to_screen(
|
||||||
|
'[info] No thumbnails present for %s' % info_dict['id'])
|
||||||
|
return
|
||||||
|
|
||||||
|
self.to_screen(
|
||||||
|
'[info] Thumbnails for %s:' % info_dict['id'])
|
||||||
|
self.to_screen(render_table(
|
||||||
|
['ID', 'width', 'height', 'URL'],
|
||||||
|
[[t['id'], t.get('width', 'unknown'), t.get('height', 'unknown'), t['url']] for t in thumbnails]))
|
||||||
|
|
||||||
def urlopen(self, req):
|
def urlopen(self, req):
|
||||||
""" Start an HTTP download """
|
""" Start an HTTP download """
|
||||||
|
|
||||||
@@ -1395,7 +1559,7 @@ class YoutubeDL(object):
|
|||||||
# urllib chokes on URLs with non-ASCII characters (see http://bugs.python.org/issue3991)
|
# urllib chokes on URLs with non-ASCII characters (see http://bugs.python.org/issue3991)
|
||||||
# To work around aforementioned issue we will replace request's original URL with
|
# To work around aforementioned issue we will replace request's original URL with
|
||||||
# percent-encoded one
|
# percent-encoded one
|
||||||
req_is_string = isinstance(req, basestring if sys.version_info < (3, 0) else compat_str)
|
req_is_string = isinstance(req, compat_basestring)
|
||||||
url = req if req_is_string else req.get_full_url()
|
url = req if req_is_string else req.get_full_url()
|
||||||
url_escaped = escape_url(url)
|
url_escaped = escape_url(url)
|
||||||
|
|
||||||
@@ -1464,6 +1628,17 @@ class YoutubeDL(object):
|
|||||||
proxy_map.update(handler.proxies)
|
proxy_map.update(handler.proxies)
|
||||||
self._write_string('[debug] Proxy map: ' + compat_str(proxy_map) + '\n')
|
self._write_string('[debug] Proxy map: ' + compat_str(proxy_map) + '\n')
|
||||||
|
|
||||||
|
if self.params.get('call_home', False):
|
||||||
|
ipaddr = self.urlopen('https://yt-dl.org/ip').read().decode('utf-8')
|
||||||
|
self._write_string('[debug] Public IP address: %s\n' % ipaddr)
|
||||||
|
latest_version = self.urlopen(
|
||||||
|
'https://yt-dl.org/latest/version').read().decode('utf-8')
|
||||||
|
if version_tuple(latest_version) > version_tuple(__version__):
|
||||||
|
self.report_warning(
|
||||||
|
'You are using an outdated version (newest version: %s)! '
|
||||||
|
'See https://yt-dl.org/update if you need help updating.' %
|
||||||
|
latest_version)
|
||||||
|
|
||||||
def _setup_opener(self):
|
def _setup_opener(self):
|
||||||
timeout_val = self.params.get('socket_timeout')
|
timeout_val = self.params.get('socket_timeout')
|
||||||
self._socket_timeout = 600 if timeout_val is None else float(timeout_val)
|
self._socket_timeout = 600 if timeout_val is None else float(timeout_val)
|
||||||
@@ -1519,3 +1694,39 @@ class YoutubeDL(object):
|
|||||||
if encoding is None:
|
if encoding is None:
|
||||||
encoding = preferredencoding()
|
encoding = preferredencoding()
|
||||||
return encoding
|
return encoding
|
||||||
|
|
||||||
|
def _write_thumbnails(self, info_dict, filename):
|
||||||
|
if self.params.get('writethumbnail', False):
|
||||||
|
thumbnails = info_dict.get('thumbnails')
|
||||||
|
if thumbnails:
|
||||||
|
thumbnails = [thumbnails[-1]]
|
||||||
|
elif self.params.get('write_all_thumbnails', False):
|
||||||
|
thumbnails = info_dict.get('thumbnails')
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not thumbnails:
|
||||||
|
# No thumbnails present, so return immediately
|
||||||
|
return
|
||||||
|
|
||||||
|
for t in thumbnails:
|
||||||
|
thumb_ext = determine_ext(t['url'], 'jpg')
|
||||||
|
suffix = '_%s' % t['id'] if len(thumbnails) > 1 else ''
|
||||||
|
thumb_display_id = '%s ' % t['id'] if len(thumbnails) > 1 else ''
|
||||||
|
thumb_filename = os.path.splitext(filename)[0] + suffix + '.' + thumb_ext
|
||||||
|
|
||||||
|
if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(thumb_filename)):
|
||||||
|
self.to_screen('[%s] %s: Thumbnail %sis already present' %
|
||||||
|
(info_dict['extractor'], info_dict['id'], thumb_display_id))
|
||||||
|
else:
|
||||||
|
self.to_screen('[%s] %s: Downloading thumbnail %s...' %
|
||||||
|
(info_dict['extractor'], info_dict['id'], thumb_display_id))
|
||||||
|
try:
|
||||||
|
uf = self.urlopen(t['url'])
|
||||||
|
with open(thumb_filename, 'wb') as thumbf:
|
||||||
|
shutil.copyfileobj(uf, thumbf)
|
||||||
|
self.to_screen('[%s] %s: Writing thumbnail %sto: %s' %
|
||||||
|
(info_dict['extractor'], info_dict['id'], thumb_display_id, thumb_filename))
|
||||||
|
except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
|
||||||
|
self.report_warning('Unable to download thumbnail "%s": %s' %
|
||||||
|
(t['url'], compat_str(err)))
|
||||||
|
@@ -143,8 +143,11 @@ def _real_main(argv=None):
|
|||||||
parser.error('invalid max_filesize specified')
|
parser.error('invalid max_filesize specified')
|
||||||
opts.max_filesize = numeric_limit
|
opts.max_filesize = numeric_limit
|
||||||
if opts.retries is not None:
|
if opts.retries is not None:
|
||||||
|
if opts.retries in ('inf', 'infinite'):
|
||||||
|
opts_retries = float('inf')
|
||||||
|
else:
|
||||||
try:
|
try:
|
||||||
opts.retries = int(opts.retries)
|
opts_retries = int(opts.retries)
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
parser.error('invalid retry count specified')
|
parser.error('invalid retry count specified')
|
||||||
if opts.buffersize is not None:
|
if opts.buffersize is not None:
|
||||||
@@ -238,6 +241,12 @@ def _real_main(argv=None):
|
|||||||
'verboseOutput': opts.verbose,
|
'verboseOutput': opts.verbose,
|
||||||
'exec_cmd': opts.exec_cmd,
|
'exec_cmd': opts.exec_cmd,
|
||||||
})
|
})
|
||||||
|
if opts.xattr_set_filesize:
|
||||||
|
try:
|
||||||
|
import xattr
|
||||||
|
xattr # Confuse flake8
|
||||||
|
except ImportError:
|
||||||
|
parser.error('setting filesize xattr requested but python-xattr is not available')
|
||||||
|
|
||||||
ydl_opts = {
|
ydl_opts = {
|
||||||
'usenetrc': opts.usenetrc,
|
'usenetrc': opts.usenetrc,
|
||||||
@@ -268,7 +277,7 @@ def _real_main(argv=None):
|
|||||||
'ignoreerrors': opts.ignoreerrors,
|
'ignoreerrors': opts.ignoreerrors,
|
||||||
'ratelimit': opts.ratelimit,
|
'ratelimit': opts.ratelimit,
|
||||||
'nooverwrites': opts.nooverwrites,
|
'nooverwrites': opts.nooverwrites,
|
||||||
'retries': opts.retries,
|
'retries': opts_retries,
|
||||||
'buffersize': opts.buffersize,
|
'buffersize': opts.buffersize,
|
||||||
'noresizebuffer': opts.noresizebuffer,
|
'noresizebuffer': opts.noresizebuffer,
|
||||||
'continuedl': opts.continue_dl,
|
'continuedl': opts.continue_dl,
|
||||||
@@ -286,6 +295,7 @@ def _real_main(argv=None):
|
|||||||
'writeannotations': opts.writeannotations,
|
'writeannotations': opts.writeannotations,
|
||||||
'writeinfojson': opts.writeinfojson,
|
'writeinfojson': opts.writeinfojson,
|
||||||
'writethumbnail': opts.writethumbnail,
|
'writethumbnail': opts.writethumbnail,
|
||||||
|
'write_all_thumbnails': opts.write_all_thumbnails,
|
||||||
'writesubtitles': opts.writesubtitles,
|
'writesubtitles': opts.writesubtitles,
|
||||||
'writeautomaticsub': opts.writeautomaticsub,
|
'writeautomaticsub': opts.writeautomaticsub,
|
||||||
'allsubtitles': opts.allsubtitles,
|
'allsubtitles': opts.allsubtitles,
|
||||||
@@ -328,6 +338,12 @@ def _real_main(argv=None):
|
|||||||
'postprocessors': postprocessors,
|
'postprocessors': postprocessors,
|
||||||
'fixup': opts.fixup,
|
'fixup': opts.fixup,
|
||||||
'source_address': opts.source_address,
|
'source_address': opts.source_address,
|
||||||
|
'call_home': opts.call_home,
|
||||||
|
'sleep_interval': opts.sleep_interval,
|
||||||
|
'external_downloader': opts.external_downloader,
|
||||||
|
'list_thumbnails': opts.list_thumbnails,
|
||||||
|
'playlist_items': opts.playlist_items,
|
||||||
|
'xattr_set_filesize': opts.xattr_set_filesize,
|
||||||
}
|
}
|
||||||
|
|
||||||
with YoutubeDL(ydl_opts) as ydl:
|
with YoutubeDL(ydl_opts) as ydl:
|
||||||
@@ -345,7 +361,9 @@ def _real_main(argv=None):
|
|||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
ydl.warn_if_short_id(sys.argv[1:] if argv is None else argv)
|
ydl.warn_if_short_id(sys.argv[1:] if argv is None else argv)
|
||||||
parser.error('you must provide at least one URL')
|
parser.error(
|
||||||
|
'You must provide at least one URL.\n'
|
||||||
|
'Type youtube-dl --help to see a list of all options.')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if opts.load_info_filename is not None:
|
if opts.load_info_filename is not None:
|
||||||
|
@@ -71,6 +71,11 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
compat_subprocess_get_DEVNULL = lambda: open(os.path.devnull, 'w')
|
compat_subprocess_get_DEVNULL = lambda: open(os.path.devnull, 'w')
|
||||||
|
|
||||||
|
try:
|
||||||
|
import http.server as compat_http_server
|
||||||
|
except ImportError:
|
||||||
|
import BaseHTTPServer as compat_http_server
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from urllib.parse import unquote as compat_urllib_parse_unquote
|
from urllib.parse import unquote as compat_urllib_parse_unquote
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@@ -109,6 +114,26 @@ except ImportError:
|
|||||||
string += pct_sequence.decode(encoding, errors)
|
string += pct_sequence.decode(encoding, errors)
|
||||||
return string
|
return string
|
||||||
|
|
||||||
|
try:
|
||||||
|
compat_str = unicode # Python 2
|
||||||
|
except NameError:
|
||||||
|
compat_str = str
|
||||||
|
|
||||||
|
try:
|
||||||
|
compat_basestring = basestring # Python 2
|
||||||
|
except NameError:
|
||||||
|
compat_basestring = str
|
||||||
|
|
||||||
|
try:
|
||||||
|
compat_chr = unichr # Python 2
|
||||||
|
except NameError:
|
||||||
|
compat_chr = chr
|
||||||
|
|
||||||
|
try:
|
||||||
|
from xml.etree.ElementTree import ParseError as compat_xml_parse_error
|
||||||
|
except ImportError: # Python 2.6
|
||||||
|
from xml.parsers.expat import ExpatError as compat_xml_parse_error
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from urllib.parse import parse_qs as compat_parse_qs
|
from urllib.parse import parse_qs as compat_parse_qs
|
||||||
@@ -118,7 +143,7 @@ except ImportError: # Python 2
|
|||||||
|
|
||||||
def _parse_qsl(qs, keep_blank_values=False, strict_parsing=False,
|
def _parse_qsl(qs, keep_blank_values=False, strict_parsing=False,
|
||||||
encoding='utf-8', errors='replace'):
|
encoding='utf-8', errors='replace'):
|
||||||
qs, _coerce_result = qs, unicode
|
qs, _coerce_result = qs, compat_str
|
||||||
pairs = [s2 for s1 in qs.split('&') for s2 in s1.split(';')]
|
pairs = [s2 for s1 in qs.split('&') for s2 in s1.split(';')]
|
||||||
r = []
|
r = []
|
||||||
for name_value in pairs:
|
for name_value in pairs:
|
||||||
@@ -157,21 +182,6 @@ except ImportError: # Python 2
|
|||||||
parsed_result[name] = [value]
|
parsed_result[name] = [value]
|
||||||
return parsed_result
|
return parsed_result
|
||||||
|
|
||||||
try:
|
|
||||||
compat_str = unicode # Python 2
|
|
||||||
except NameError:
|
|
||||||
compat_str = str
|
|
||||||
|
|
||||||
try:
|
|
||||||
compat_chr = unichr # Python 2
|
|
||||||
except NameError:
|
|
||||||
compat_chr = chr
|
|
||||||
|
|
||||||
try:
|
|
||||||
from xml.etree.ElementTree import ParseError as compat_xml_parse_error
|
|
||||||
except ImportError: # Python 2.6
|
|
||||||
from xml.parsers.expat import ExpatError as compat_xml_parse_error
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from shlex import quote as shlex_quote
|
from shlex import quote as shlex_quote
|
||||||
except ImportError: # Python < 3.3
|
except ImportError: # Python < 3.3
|
||||||
@@ -329,7 +339,7 @@ if sys.version_info < (2, 7):
|
|||||||
if err is not None:
|
if err is not None:
|
||||||
raise err
|
raise err
|
||||||
else:
|
else:
|
||||||
raise error("getaddrinfo returns an empty list")
|
raise socket.error("getaddrinfo returns an empty list")
|
||||||
else:
|
else:
|
||||||
compat_socket_create_connection = socket.create_connection
|
compat_socket_create_connection = socket.create_connection
|
||||||
|
|
||||||
@@ -357,6 +367,7 @@ def workaround_optparse_bug9161():
|
|||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'compat_HTTPError',
|
'compat_HTTPError',
|
||||||
|
'compat_basestring',
|
||||||
'compat_chr',
|
'compat_chr',
|
||||||
'compat_cookiejar',
|
'compat_cookiejar',
|
||||||
'compat_expanduser',
|
'compat_expanduser',
|
||||||
@@ -365,12 +376,13 @@ __all__ = [
|
|||||||
'compat_html_entities',
|
'compat_html_entities',
|
||||||
'compat_html_parser',
|
'compat_html_parser',
|
||||||
'compat_http_client',
|
'compat_http_client',
|
||||||
|
'compat_http_server',
|
||||||
'compat_kwargs',
|
'compat_kwargs',
|
||||||
'compat_ord',
|
'compat_ord',
|
||||||
'compat_parse_qs',
|
'compat_parse_qs',
|
||||||
'compat_print',
|
'compat_print',
|
||||||
'compat_str',
|
|
||||||
'compat_socket_create_connection',
|
'compat_socket_create_connection',
|
||||||
|
'compat_str',
|
||||||
'compat_subprocess_get_DEVNULL',
|
'compat_subprocess_get_DEVNULL',
|
||||||
'compat_urllib_error',
|
'compat_urllib_error',
|
||||||
'compat_urllib_parse',
|
'compat_urllib_parse',
|
||||||
|
@@ -1,35 +1,41 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .common import FileDownloader
|
from .common import FileDownloader
|
||||||
|
from .external import get_external_downloader
|
||||||
|
from .f4m import F4mFD
|
||||||
from .hls import HlsFD
|
from .hls import HlsFD
|
||||||
from .hls import NativeHlsFD
|
from .hls import NativeHlsFD
|
||||||
from .http import HttpFD
|
from .http import HttpFD
|
||||||
from .mplayer import MplayerFD
|
from .mplayer import MplayerFD
|
||||||
from .rtmp import RtmpFD
|
from .rtmp import RtmpFD
|
||||||
from .f4m import F4mFD
|
|
||||||
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
determine_ext,
|
determine_protocol,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
PROTOCOL_MAP = {
|
||||||
|
'rtmp': RtmpFD,
|
||||||
|
'm3u8_native': NativeHlsFD,
|
||||||
|
'm3u8': HlsFD,
|
||||||
|
'mms': MplayerFD,
|
||||||
|
'rtsp': MplayerFD,
|
||||||
|
'f4m': F4mFD,
|
||||||
|
}
|
||||||
|
|
||||||
def get_suitable_downloader(info_dict):
|
|
||||||
|
def get_suitable_downloader(info_dict, params={}):
|
||||||
"""Get the downloader class that can handle the info dict."""
|
"""Get the downloader class that can handle the info dict."""
|
||||||
url = info_dict['url']
|
protocol = determine_protocol(info_dict)
|
||||||
protocol = info_dict.get('protocol')
|
info_dict['protocol'] = protocol
|
||||||
|
|
||||||
|
external_downloader = params.get('external_downloader')
|
||||||
|
if external_downloader is not None:
|
||||||
|
ed = get_external_downloader(external_downloader)
|
||||||
|
if ed.supports(info_dict):
|
||||||
|
return ed
|
||||||
|
|
||||||
|
return PROTOCOL_MAP.get(protocol, HttpFD)
|
||||||
|
|
||||||
if url.startswith('rtmp'):
|
|
||||||
return RtmpFD
|
|
||||||
if protocol == 'm3u8_native':
|
|
||||||
return NativeHlsFD
|
|
||||||
if (protocol == 'm3u8') or (protocol is None and determine_ext(url) == 'm3u8'):
|
|
||||||
return HlsFD
|
|
||||||
if url.startswith('mms') or url.startswith('rtsp'):
|
|
||||||
return MplayerFD
|
|
||||||
if determine_ext(url) == 'f4m':
|
|
||||||
return F4mFD
|
|
||||||
else:
|
|
||||||
return HttpFD
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'get_suitable_downloader',
|
'get_suitable_downloader',
|
||||||
|
@@ -40,6 +40,8 @@ class FileDownloader(object):
|
|||||||
test: Download only first bytes to test the downloader.
|
test: Download only first bytes to test the downloader.
|
||||||
min_filesize: Skip files smaller than this size
|
min_filesize: Skip files smaller than this size
|
||||||
max_filesize: Skip files larger than this size
|
max_filesize: Skip files larger than this size
|
||||||
|
xattr_set_filesize: Set ytdl.filesize user xattribute with expected size.
|
||||||
|
(experimenatal)
|
||||||
|
|
||||||
Subclasses of this one must re-define the real_download method.
|
Subclasses of this one must re-define the real_download method.
|
||||||
"""
|
"""
|
||||||
@@ -284,8 +286,20 @@ class FileDownloader(object):
|
|||||||
"""Download to a filename using the info from info_dict
|
"""Download to a filename using the info from info_dict
|
||||||
Return True on success and False otherwise
|
Return True on success and False otherwise
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
nooverwrites_and_exists = (
|
||||||
|
self.params.get('nooverwrites', False)
|
||||||
|
and os.path.exists(encodeFilename(filename))
|
||||||
|
)
|
||||||
|
|
||||||
|
continuedl_and_exists = (
|
||||||
|
self.params.get('continuedl', False)
|
||||||
|
and os.path.isfile(encodeFilename(filename))
|
||||||
|
and not self.params.get('nopart', False)
|
||||||
|
)
|
||||||
|
|
||||||
# Check file already present
|
# Check file already present
|
||||||
if filename != '-' and self.params.get('continuedl', False) and os.path.isfile(encodeFilename(filename)) and not self.params.get('nopart', False):
|
if filename != '-' and nooverwrites_and_exists or continuedl_and_exists:
|
||||||
self.report_file_already_downloaded(filename)
|
self.report_file_already_downloaded(filename)
|
||||||
self._hook_progress({
|
self._hook_progress({
|
||||||
'filename': filename,
|
'filename': filename,
|
||||||
@@ -294,6 +308,11 @@ class FileDownloader(object):
|
|||||||
})
|
})
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
sleep_interval = self.params.get('sleep_interval')
|
||||||
|
if sleep_interval:
|
||||||
|
self.to_screen('[download] Sleeping %s seconds...' % sleep_interval)
|
||||||
|
time.sleep(sleep_interval)
|
||||||
|
|
||||||
return self.real_download(filename, info_dict)
|
return self.real_download(filename, info_dict)
|
||||||
|
|
||||||
def real_download(self, filename, info_dict):
|
def real_download(self, filename, info_dict):
|
||||||
@@ -308,3 +327,24 @@ class FileDownloader(object):
|
|||||||
# See YoutubeDl.py (search for progress_hooks) for a description of
|
# See YoutubeDl.py (search for progress_hooks) for a description of
|
||||||
# this interface
|
# this interface
|
||||||
self._progress_hooks.append(ph)
|
self._progress_hooks.append(ph)
|
||||||
|
|
||||||
|
def _debug_cmd(self, args, subprocess_encoding, exe=None):
|
||||||
|
if not self.params.get('verbose', False):
|
||||||
|
return
|
||||||
|
|
||||||
|
if exe is None:
|
||||||
|
exe = os.path.basename(args[0])
|
||||||
|
|
||||||
|
if subprocess_encoding:
|
||||||
|
str_args = [
|
||||||
|
a.decode(subprocess_encoding) if isinstance(a, bytes) else a
|
||||||
|
for a in args]
|
||||||
|
else:
|
||||||
|
str_args = args
|
||||||
|
try:
|
||||||
|
import pipes
|
||||||
|
shell_quote = lambda args: ' '.join(map(pipes.quote, str_args))
|
||||||
|
except ImportError:
|
||||||
|
shell_quote = repr
|
||||||
|
self.to_screen('[debug] %s command line: %s' % (
|
||||||
|
exe, shell_quote(str_args)))
|
||||||
|
117
youtube_dl/downloader/external.py
Normal file
117
youtube_dl/downloader/external.py
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import os.path
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from .common import FileDownloader
|
||||||
|
from ..utils import (
|
||||||
|
encodeFilename,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ExternalFD(FileDownloader):
|
||||||
|
def real_download(self, filename, info_dict):
|
||||||
|
self.report_destination(filename)
|
||||||
|
tmpfilename = self.temp_name(filename)
|
||||||
|
|
||||||
|
retval = self._call_downloader(tmpfilename, info_dict)
|
||||||
|
if retval == 0:
|
||||||
|
fsize = os.path.getsize(encodeFilename(tmpfilename))
|
||||||
|
self.to_screen('\r[%s] Downloaded %s bytes' % (self.get_basename(), fsize))
|
||||||
|
self.try_rename(tmpfilename, filename)
|
||||||
|
self._hook_progress({
|
||||||
|
'downloaded_bytes': fsize,
|
||||||
|
'total_bytes': fsize,
|
||||||
|
'filename': filename,
|
||||||
|
'status': 'finished',
|
||||||
|
})
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
self.to_stderr('\n')
|
||||||
|
self.report_error('%s exited with code %d' % (
|
||||||
|
self.get_basename(), retval))
|
||||||
|
return False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_basename(cls):
|
||||||
|
return cls.__name__[:-2].lower()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def exe(self):
|
||||||
|
return self.params.get('external_downloader')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def supports(cls, info_dict):
|
||||||
|
return info_dict['protocol'] in ('http', 'https', 'ftp', 'ftps')
|
||||||
|
|
||||||
|
def _call_downloader(self, tmpfilename, info_dict):
|
||||||
|
""" Either overwrite this or implement _make_cmd """
|
||||||
|
cmd = self._make_cmd(tmpfilename, info_dict)
|
||||||
|
|
||||||
|
if sys.platform == 'win32' and sys.version_info < (3, 0):
|
||||||
|
# Windows subprocess module does not actually support Unicode
|
||||||
|
# on Python 2.x
|
||||||
|
# See http://stackoverflow.com/a/9951851/35070
|
||||||
|
subprocess_encoding = sys.getfilesystemencoding()
|
||||||
|
cmd = [a.encode(subprocess_encoding, 'ignore') for a in cmd]
|
||||||
|
else:
|
||||||
|
subprocess_encoding = None
|
||||||
|
self._debug_cmd(cmd, subprocess_encoding)
|
||||||
|
|
||||||
|
p = subprocess.Popen(
|
||||||
|
cmd, stderr=subprocess.PIPE)
|
||||||
|
_, stderr = p.communicate()
|
||||||
|
if p.returncode != 0:
|
||||||
|
self.to_stderr(stderr)
|
||||||
|
return p.returncode
|
||||||
|
|
||||||
|
|
||||||
|
class CurlFD(ExternalFD):
|
||||||
|
def _make_cmd(self, tmpfilename, info_dict):
|
||||||
|
cmd = [self.exe, '-o', tmpfilename]
|
||||||
|
for key, val in info_dict['http_headers'].items():
|
||||||
|
cmd += ['--header', '%s: %s' % (key, val)]
|
||||||
|
cmd += ['--', info_dict['url']]
|
||||||
|
return cmd
|
||||||
|
|
||||||
|
|
||||||
|
class WgetFD(ExternalFD):
|
||||||
|
def _make_cmd(self, tmpfilename, info_dict):
|
||||||
|
cmd = [self.exe, '-O', tmpfilename, '-nv', '--no-cookies']
|
||||||
|
for key, val in info_dict['http_headers'].items():
|
||||||
|
cmd += ['--header', '%s: %s' % (key, val)]
|
||||||
|
cmd += ['--', info_dict['url']]
|
||||||
|
return cmd
|
||||||
|
|
||||||
|
|
||||||
|
class Aria2cFD(ExternalFD):
|
||||||
|
def _make_cmd(self, tmpfilename, info_dict):
|
||||||
|
cmd = [
|
||||||
|
self.exe, '-c',
|
||||||
|
'--min-split-size', '1M', '--max-connection-per-server', '4']
|
||||||
|
dn = os.path.dirname(tmpfilename)
|
||||||
|
if dn:
|
||||||
|
cmd += ['--dir', dn]
|
||||||
|
cmd += ['--out', os.path.basename(tmpfilename)]
|
||||||
|
for key, val in info_dict['http_headers'].items():
|
||||||
|
cmd += ['--header', '%s: %s' % (key, val)]
|
||||||
|
cmd += ['--', info_dict['url']]
|
||||||
|
return cmd
|
||||||
|
|
||||||
|
_BY_NAME = dict(
|
||||||
|
(klass.get_basename(), klass)
|
||||||
|
for name, klass in globals().items()
|
||||||
|
if name.endswith('FD') and name != 'ExternalFD'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def list_external_downloaders():
|
||||||
|
return sorted(_BY_NAME.keys())
|
||||||
|
|
||||||
|
|
||||||
|
def get_external_downloader(external_downloader):
|
||||||
|
""" Given the name of the executable, see whether we support the given
|
||||||
|
downloader . """
|
||||||
|
bn = os.path.basename(external_downloader)
|
||||||
|
return _BY_NAME[bn]
|
@@ -177,13 +177,12 @@ def build_fragments_list(boot_info):
|
|||||||
""" Return a list of (segment, fragment) for each fragment in the video """
|
""" Return a list of (segment, fragment) for each fragment in the video """
|
||||||
res = []
|
res = []
|
||||||
segment_run_table = boot_info['segments'][0]
|
segment_run_table = boot_info['segments'][0]
|
||||||
# I've only found videos with one segment
|
|
||||||
segment_run_entry = segment_run_table['segment_run'][0]
|
|
||||||
n_frags = segment_run_entry[1]
|
|
||||||
fragment_run_entry_table = boot_info['fragments'][0]['fragments']
|
fragment_run_entry_table = boot_info['fragments'][0]['fragments']
|
||||||
first_frag_number = fragment_run_entry_table[0]['first']
|
first_frag_number = fragment_run_entry_table[0]['first']
|
||||||
for (i, frag_number) in zip(range(1, n_frags + 1), itertools.count(first_frag_number)):
|
fragments_counter = itertools.count(first_frag_number)
|
||||||
res.append((1, frag_number))
|
for segment, fragments_count in segment_run_table['segment_run']:
|
||||||
|
for _ in range(fragments_count):
|
||||||
|
res.append((segment, next(fragments_counter)))
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
@@ -231,6 +230,23 @@ class F4mFD(FileDownloader):
|
|||||||
A downloader for f4m manifests or AdobeHDS.
|
A downloader for f4m manifests or AdobeHDS.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def _get_unencrypted_media(self, doc):
|
||||||
|
media = doc.findall(_add_ns('media'))
|
||||||
|
if not media:
|
||||||
|
self.report_error('No media found')
|
||||||
|
for e in (doc.findall(_add_ns('drmAdditionalHeader')) +
|
||||||
|
doc.findall(_add_ns('drmAdditionalHeaderSet'))):
|
||||||
|
# If id attribute is missing it's valid for all media nodes
|
||||||
|
# without drmAdditionalHeaderId or drmAdditionalHeaderSetId attribute
|
||||||
|
if 'id' not in e.attrib:
|
||||||
|
self.report_error('Missing ID in f4m DRM')
|
||||||
|
media = list(filter(lambda e: 'drmAdditionalHeaderId' not in e.attrib and
|
||||||
|
'drmAdditionalHeaderSetId' not in e.attrib,
|
||||||
|
media))
|
||||||
|
if not media:
|
||||||
|
self.report_error('Unsupported DRM')
|
||||||
|
return media
|
||||||
|
|
||||||
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')
|
requested_bitrate = info_dict.get('tbr')
|
||||||
@@ -249,7 +265,8 @@ 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 self._get_unencrypted_media(doc)]
|
||||||
if requested_bitrate is None:
|
if requested_bitrate is None:
|
||||||
# get the best format
|
# get the best format
|
||||||
formats = sorted(formats, key=lambda f: f[0])
|
formats = sorted(formats, key=lambda f: f[0])
|
||||||
|
@@ -11,6 +11,7 @@ from ..compat import (
|
|||||||
compat_urllib_request,
|
compat_urllib_request,
|
||||||
)
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
|
encodeArgument,
|
||||||
encodeFilename,
|
encodeFilename,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -21,23 +22,22 @@ class HlsFD(FileDownloader):
|
|||||||
self.report_destination(filename)
|
self.report_destination(filename)
|
||||||
tmpfilename = self.temp_name(filename)
|
tmpfilename = self.temp_name(filename)
|
||||||
|
|
||||||
args = [
|
|
||||||
'-y', '-i', url, '-f', 'mp4', '-c', 'copy',
|
|
||||||
'-bsf:a', 'aac_adtstoasc',
|
|
||||||
encodeFilename(tmpfilename, for_subprocess=True)]
|
|
||||||
|
|
||||||
ffpp = FFmpegPostProcessor(downloader=self)
|
ffpp = FFmpegPostProcessor(downloader=self)
|
||||||
program = ffpp._executable
|
program = ffpp._executable
|
||||||
if program is None:
|
if program is None:
|
||||||
self.report_error('m3u8 download detected but ffmpeg or avconv could not be found. Please install one.')
|
self.report_error('m3u8 download detected but ffmpeg or avconv could not be found. Please install one.')
|
||||||
return False
|
return False
|
||||||
ffpp.check_version()
|
ffpp.check_version()
|
||||||
cmd = [program] + args
|
|
||||||
|
|
||||||
retval = subprocess.call(cmd)
|
args = [
|
||||||
|
encodeArgument(opt)
|
||||||
|
for opt in (program, '-y', '-i', url, '-f', 'mp4', '-c', 'copy', '-bsf:a', 'aac_adtstoasc')]
|
||||||
|
args.append(encodeFilename(tmpfilename, True))
|
||||||
|
|
||||||
|
retval = subprocess.call(args)
|
||||||
if retval == 0:
|
if retval == 0:
|
||||||
fsize = os.path.getsize(encodeFilename(tmpfilename))
|
fsize = os.path.getsize(encodeFilename(tmpfilename))
|
||||||
self.to_screen('\r[%s] %s bytes' % (cmd[0], fsize))
|
self.to_screen('\r[%s] %s bytes' % (args[0], fsize))
|
||||||
self.try_rename(tmpfilename, filename)
|
self.try_rename(tmpfilename, filename)
|
||||||
self._hook_progress({
|
self._hook_progress({
|
||||||
'downloaded_bytes': fsize,
|
'downloaded_bytes': fsize,
|
||||||
|
@@ -24,10 +24,6 @@ class HttpFD(FileDownloader):
|
|||||||
|
|
||||||
# Do not include the Accept-Encoding header
|
# Do not include the Accept-Encoding header
|
||||||
headers = {'Youtubedl-no-compression': 'True'}
|
headers = {'Youtubedl-no-compression': 'True'}
|
||||||
if 'user_agent' in info_dict:
|
|
||||||
headers['Youtubedl-user-agent'] = info_dict['user_agent']
|
|
||||||
if 'http_referer' in info_dict:
|
|
||||||
headers['Referer'] = info_dict['http_referer']
|
|
||||||
add_headers = info_dict.get('http_headers')
|
add_headers = info_dict.get('http_headers')
|
||||||
if add_headers:
|
if add_headers:
|
||||||
headers.update(add_headers)
|
headers.update(add_headers)
|
||||||
@@ -161,6 +157,14 @@ class HttpFD(FileDownloader):
|
|||||||
except (OSError, IOError) as err:
|
except (OSError, IOError) as err:
|
||||||
self.report_error('unable to open for writing: %s' % str(err))
|
self.report_error('unable to open for writing: %s' % str(err))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if self.params.get('xattr_set_filesize', False) and data_len is not None:
|
||||||
|
try:
|
||||||
|
import xattr
|
||||||
|
xattr.setxattr(tmpfilename, 'user.ytdl.filesize', str(data_len))
|
||||||
|
except(OSError, IOError, ImportError) as err:
|
||||||
|
self.report_error('unable to set filesize xattr: %s' % str(err))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
stream.write(data_block)
|
stream.write(data_block)
|
||||||
except (IOError, OSError) as err:
|
except (IOError, OSError) as err:
|
||||||
|
@@ -104,6 +104,9 @@ class RtmpFD(FileDownloader):
|
|||||||
live = info_dict.get('rtmp_live', False)
|
live = info_dict.get('rtmp_live', False)
|
||||||
conn = info_dict.get('rtmp_conn', None)
|
conn = info_dict.get('rtmp_conn', None)
|
||||||
protocol = info_dict.get('rtmp_protocol', None)
|
protocol = info_dict.get('rtmp_protocol', None)
|
||||||
|
real_time = info_dict.get('rtmp_real_time', False)
|
||||||
|
no_resume = info_dict.get('no_resume', False)
|
||||||
|
continue_dl = info_dict.get('continuedl', False)
|
||||||
|
|
||||||
self.report_destination(filename)
|
self.report_destination(filename)
|
||||||
tmpfilename = self.temp_name(filename)
|
tmpfilename = self.temp_name(filename)
|
||||||
@@ -141,7 +144,14 @@ class RtmpFD(FileDownloader):
|
|||||||
basic_args += ['--conn', conn]
|
basic_args += ['--conn', conn]
|
||||||
if protocol is not None:
|
if protocol is not None:
|
||||||
basic_args += ['--protocol', protocol]
|
basic_args += ['--protocol', protocol]
|
||||||
args = basic_args + [[], ['--resume', '--skip', '1']][not live and self.params.get('continuedl', False)]
|
if real_time:
|
||||||
|
basic_args += ['--realtime']
|
||||||
|
|
||||||
|
args = basic_args
|
||||||
|
if not no_resume and continue_dl and not live:
|
||||||
|
args += ['--resume']
|
||||||
|
if not live and continue_dl:
|
||||||
|
args += ['--skip', '1']
|
||||||
|
|
||||||
if sys.platform == 'win32' and sys.version_info < (3, 0):
|
if sys.platform == 'win32' and sys.version_info < (3, 0):
|
||||||
# Windows subprocess module does not actually support Unicode
|
# Windows subprocess module does not actually support Unicode
|
||||||
@@ -152,19 +162,7 @@ class RtmpFD(FileDownloader):
|
|||||||
else:
|
else:
|
||||||
subprocess_encoding = None
|
subprocess_encoding = None
|
||||||
|
|
||||||
if self.params.get('verbose', False):
|
self._debug_cmd(args, subprocess_encoding, exe='rtmpdump')
|
||||||
if subprocess_encoding:
|
|
||||||
str_args = [
|
|
||||||
a.decode(subprocess_encoding) if isinstance(a, bytes) else a
|
|
||||||
for a in args]
|
|
||||||
else:
|
|
||||||
str_args = args
|
|
||||||
try:
|
|
||||||
import pipes
|
|
||||||
shell_quote = lambda args: ' '.join(map(pipes.quote, str_args))
|
|
||||||
except ImportError:
|
|
||||||
shell_quote = repr
|
|
||||||
self.to_screen('[debug] rtmpdump command line: ' + shell_quote(str_args))
|
|
||||||
|
|
||||||
RD_SUCCESS = 0
|
RD_SUCCESS = 0
|
||||||
RD_FAILED = 1
|
RD_FAILED = 1
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .abc import ABCIE
|
from .abc import ABCIE
|
||||||
|
from .abc7news import Abc7NewsIE
|
||||||
from .academicearth import AcademicEarthCourseIE
|
from .academicearth import AcademicEarthCourseIE
|
||||||
from .addanime import AddAnimeIE
|
from .addanime import AddAnimeIE
|
||||||
from .adobetv import AdobeTVIE
|
from .adobetv import AdobeTVIE
|
||||||
@@ -28,7 +29,6 @@ from .arte import (
|
|||||||
from .atresplayer import AtresPlayerIE
|
from .atresplayer import AtresPlayerIE
|
||||||
from .atttechchannel import ATTTechChannelIE
|
from .atttechchannel import ATTTechChannelIE
|
||||||
from .audiomack import AudiomackIE, AudiomackAlbumIE
|
from .audiomack import AudiomackIE, AudiomackAlbumIE
|
||||||
from .auengine import AUEngineIE
|
|
||||||
from .azubu import AzubuIE
|
from .azubu import AzubuIE
|
||||||
from .bambuser import BambuserIE, BambuserChannelIE
|
from .bambuser import BambuserIE, BambuserChannelIE
|
||||||
from .bandcamp import BandcampIE, BandcampAlbumIE
|
from .bandcamp import BandcampIE, BandcampAlbumIE
|
||||||
@@ -82,6 +82,7 @@ from .crunchyroll import (
|
|||||||
CrunchyrollShowPlaylistIE
|
CrunchyrollShowPlaylistIE
|
||||||
)
|
)
|
||||||
from .cspan import CSpanIE
|
from .cspan import CSpanIE
|
||||||
|
from .ctsnews import CtsNewsIE
|
||||||
from .dailymotion import (
|
from .dailymotion import (
|
||||||
DailymotionIE,
|
DailymotionIE,
|
||||||
DailymotionPlaylistIE,
|
DailymotionPlaylistIE,
|
||||||
@@ -89,10 +90,12 @@ from .dailymotion import (
|
|||||||
)
|
)
|
||||||
from .daum import DaumIE
|
from .daum import DaumIE
|
||||||
from .dbtv import DBTVIE
|
from .dbtv import DBTVIE
|
||||||
|
from .dctp import DctpTvIE
|
||||||
from .deezer import DeezerPlaylistIE
|
from .deezer import DeezerPlaylistIE
|
||||||
from .dfb import DFBIE
|
from .dfb import DFBIE
|
||||||
from .dotsub import DotsubIE
|
from .dotsub import DotsubIE
|
||||||
from .dreisat import DreiSatIE
|
from .dreisat import DreiSatIE
|
||||||
|
from .drbonanza import DRBonanzaIE
|
||||||
from .drtuber import DrTuberIE
|
from .drtuber import DrTuberIE
|
||||||
from .drtv import DRTVIE
|
from .drtv import DRTVIE
|
||||||
from .dvtv import DVTVIE
|
from .dvtv import DVTVIE
|
||||||
@@ -174,6 +177,7 @@ from .goshgay import GoshgayIE
|
|||||||
from .grooveshark import GroovesharkIE
|
from .grooveshark import GroovesharkIE
|
||||||
from .groupon import GrouponIE
|
from .groupon import GrouponIE
|
||||||
from .hark import HarkIE
|
from .hark import HarkIE
|
||||||
|
from .hearthisat import HearThisAtIE
|
||||||
from .heise import HeiseIE
|
from .heise import HeiseIE
|
||||||
from .hellporno import HellPornoIE
|
from .hellporno import HellPornoIE
|
||||||
from .helsinki import HelsinkiIE
|
from .helsinki import HelsinkiIE
|
||||||
@@ -225,6 +229,7 @@ from .livestream import (
|
|||||||
LivestreamOriginalIE,
|
LivestreamOriginalIE,
|
||||||
LivestreamShortenerIE,
|
LivestreamShortenerIE,
|
||||||
)
|
)
|
||||||
|
from .lnkgo import LnkGoIE
|
||||||
from .lrt import LRTIE
|
from .lrt import LRTIE
|
||||||
from .lynda import (
|
from .lynda import (
|
||||||
LyndaIE,
|
LyndaIE,
|
||||||
@@ -281,9 +286,19 @@ from .netzkino import NetzkinoIE
|
|||||||
from .nerdcubed import NerdCubedFeedIE
|
from .nerdcubed import NerdCubedFeedIE
|
||||||
from .newgrounds import NewgroundsIE
|
from .newgrounds import NewgroundsIE
|
||||||
from .newstube import NewstubeIE
|
from .newstube import NewstubeIE
|
||||||
|
from .nextmedia import (
|
||||||
|
NextMediaIE,
|
||||||
|
NextMediaActionNewsIE,
|
||||||
|
AppleDailyRealtimeNewsIE,
|
||||||
|
AppleDailyAnimationNewsIE
|
||||||
|
)
|
||||||
from .nfb import NFBIE
|
from .nfb import NFBIE
|
||||||
from .nfl import NFLIE
|
from .nfl import NFLIE
|
||||||
from .nhl import NHLIE, NHLVideocenterIE
|
from .nhl import (
|
||||||
|
NHLIE,
|
||||||
|
NHLNewsIE,
|
||||||
|
NHLVideocenterIE,
|
||||||
|
)
|
||||||
from .niconico import NiconicoIE, NiconicoPlaylistIE
|
from .niconico import NiconicoIE, NiconicoPlaylistIE
|
||||||
from .ninegag import NineGagIE
|
from .ninegag import NineGagIE
|
||||||
from .noco import NocoIE
|
from .noco import NocoIE
|
||||||
@@ -294,6 +309,7 @@ from .nowness import NownessIE
|
|||||||
from .nowvideo import NowVideoIE
|
from .nowvideo import NowVideoIE
|
||||||
from .npo import (
|
from .npo import (
|
||||||
NPOIE,
|
NPOIE,
|
||||||
|
NPOLiveIE,
|
||||||
TegenlichtVproIE,
|
TegenlichtVproIE,
|
||||||
)
|
)
|
||||||
from .nrk import (
|
from .nrk import (
|
||||||
@@ -345,6 +361,7 @@ from .rtbf import RTBFIE
|
|||||||
from .rte import RteIE
|
from .rte import RteIE
|
||||||
from .rtlnl import RtlXlIE
|
from .rtlnl import RtlXlIE
|
||||||
from .rtlnow import RTLnowIE
|
from .rtlnow import RTLnowIE
|
||||||
|
from .rtl2 import RTL2IE
|
||||||
from .rtp import RTPIE
|
from .rtp import RTPIE
|
||||||
from .rts import RTSIE
|
from .rts import RTSIE
|
||||||
from .rtve import RTVEALaCartaIE, RTVELiveIE
|
from .rtve import RTVEALaCartaIE, RTVELiveIE
|
||||||
@@ -405,6 +422,7 @@ from .stanfordoc import StanfordOpenClassroomIE
|
|||||||
from .steam import SteamIE
|
from .steam import SteamIE
|
||||||
from .streamcloud import StreamcloudIE
|
from .streamcloud import StreamcloudIE
|
||||||
from .streamcz import StreamCZIE
|
from .streamcz import StreamCZIE
|
||||||
|
from .streetvoice import StreetVoiceIE
|
||||||
from .sunporno import SunPornoIE
|
from .sunporno import SunPornoIE
|
||||||
from .swrmediathek import SWRMediathekIE
|
from .swrmediathek import SWRMediathekIE
|
||||||
from .syfy import SyfyIE
|
from .syfy import SyfyIE
|
||||||
@@ -426,6 +444,7 @@ from .telemb import TeleMBIE
|
|||||||
from .teletask import TeleTaskIE
|
from .teletask import TeleTaskIE
|
||||||
from .tenplay import TenPlayIE
|
from .tenplay import TenPlayIE
|
||||||
from .testurl import TestURLIE
|
from .testurl import TestURLIE
|
||||||
|
from .testtube import TestTubeIE
|
||||||
from .tf1 import TF1IE
|
from .tf1 import TF1IE
|
||||||
from .theonion import TheOnionIE
|
from .theonion import TheOnionIE
|
||||||
from .theplatform import ThePlatformIE
|
from .theplatform import ThePlatformIE
|
||||||
@@ -454,7 +473,15 @@ from .tvigle import TvigleIE
|
|||||||
from .tvp import TvpIE, TvpSeriesIE
|
from .tvp import TvpIE, TvpSeriesIE
|
||||||
from .tvplay import TVPlayIE
|
from .tvplay import TVPlayIE
|
||||||
from .twentyfourvideo import TwentyFourVideoIE
|
from .twentyfourvideo import TwentyFourVideoIE
|
||||||
from .twitch import TwitchIE
|
from .twitch import (
|
||||||
|
TwitchVideoIE,
|
||||||
|
TwitchChapterIE,
|
||||||
|
TwitchVodIE,
|
||||||
|
TwitchProfileIE,
|
||||||
|
TwitchPastBroadcastsIE,
|
||||||
|
TwitchBookmarksIE,
|
||||||
|
TwitchStreamIE,
|
||||||
|
)
|
||||||
from .ubu import UbuIE
|
from .ubu import UbuIE
|
||||||
from .udemy import (
|
from .udemy import (
|
||||||
UdemyIE,
|
UdemyIE,
|
||||||
@@ -531,6 +558,7 @@ from .xminus import XMinusIE
|
|||||||
from .xnxx import XNXXIE
|
from .xnxx import XNXXIE
|
||||||
from .xvideos import XVideosIE
|
from .xvideos import XVideosIE
|
||||||
from .xtube import XTubeUserIE, XTubeIE
|
from .xtube import XTubeUserIE, XTubeIE
|
||||||
|
from .xuite import XuiteIE
|
||||||
from .xxxymovies import XXXYMoviesIE
|
from .xxxymovies import XXXYMoviesIE
|
||||||
from .yahoo import (
|
from .yahoo import (
|
||||||
YahooIE,
|
YahooIE,
|
||||||
|
68
youtube_dl/extractor/abc7news.py
Normal file
68
youtube_dl/extractor/abc7news.py
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import parse_iso8601
|
||||||
|
|
||||||
|
|
||||||
|
class Abc7NewsIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://abc7news\.com(?:/[^/]+/(?P<display_id>[^/]+))?/(?P<id>\d+)'
|
||||||
|
_TESTS = [
|
||||||
|
{
|
||||||
|
'url': 'http://abc7news.com/entertainment/east-bay-museum-celebrates-vintage-synthesizers/472581/',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '472581',
|
||||||
|
'display_id': 'east-bay-museum-celebrates-vintage-synthesizers',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'East Bay museum celebrates history of synthesized music',
|
||||||
|
'description': 'md5:a4f10fb2f2a02565c1749d4adbab4b10',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
'timestamp': 1421123075,
|
||||||
|
'upload_date': '20150113',
|
||||||
|
'uploader': 'Jonathan Bloom',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# m3u8 download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://abc7news.com/472581',
|
||||||
|
'only_matching': True,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
display_id = mobj.group('display_id') or video_id
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
|
||||||
|
m3u8 = self._html_search_meta(
|
||||||
|
'contentURL', webpage, 'm3u8 url', fatal=True)
|
||||||
|
|
||||||
|
formats = self._extract_m3u8_formats(m3u8, display_id, 'mp4')
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
title = self._og_search_title(webpage).strip()
|
||||||
|
description = self._og_search_description(webpage).strip()
|
||||||
|
thumbnail = self._og_search_thumbnail(webpage)
|
||||||
|
timestamp = parse_iso8601(self._search_regex(
|
||||||
|
r'<div class="meta">\s*<time class="timeago" datetime="([^"]+)">',
|
||||||
|
webpage, 'upload date', fatal=False))
|
||||||
|
uploader = self._search_regex(
|
||||||
|
r'rel="author">([^<]+)</a>',
|
||||||
|
webpage, 'uploader', default=None)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'display_id': display_id,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'timestamp': timestamp,
|
||||||
|
'uploader': uploader,
|
||||||
|
'formats': formats,
|
||||||
|
}
|
@@ -122,14 +122,15 @@ class AppleTrailersIE(InfoExtractor):
|
|||||||
playlist.append({
|
playlist.append({
|
||||||
'_type': 'video',
|
'_type': 'video',
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': title,
|
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
'title': title,
|
'title': title,
|
||||||
'duration': duration,
|
'duration': duration,
|
||||||
'thumbnail': thumbnail,
|
'thumbnail': thumbnail,
|
||||||
'upload_date': upload_date,
|
'upload_date': upload_date,
|
||||||
'uploader_id': uploader_id,
|
'uploader_id': uploader_id,
|
||||||
'user_agent': 'QuickTime compatible (youtube-dl)',
|
'http_headers': {
|
||||||
|
'User-Agent': 'QuickTime compatible (youtube-dl)',
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@@ -23,13 +23,7 @@ class ARDMediathekIE(InfoExtractor):
|
|||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://mediathek.daserste.de/sendungen_a-z/328454_anne-will/22429276_vertrauen-ist-gut-spionieren-ist-besser-geht',
|
'url': 'http://mediathek.daserste.de/sendungen_a-z/328454_anne-will/22429276_vertrauen-ist-gut-spionieren-ist-besser-geht',
|
||||||
'file': '22429276.mp4',
|
'only_matching': True,
|
||||||
'md5': '469751912f1de0816a9fc9df8336476c',
|
|
||||||
'info_dict': {
|
|
||||||
'title': 'Vertrauen ist gut, Spionieren ist besser - Geht so deutsch-amerikanische Freundschaft?',
|
|
||||||
'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',
|
|
||||||
},
|
|
||||||
'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',
|
'url': 'http://www.ardmediathek.de/tv/Tatort/Das-Wunder-von-Wolbeck-Video-tgl-ab-20/Das-Erste/Video?documentId=22490580&bcastId=602916',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
@@ -3,10 +3,13 @@ from __future__ import unicode_literals
|
|||||||
import time
|
import time
|
||||||
import hmac
|
import hmac
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .subtitles import SubtitlesInfoExtractor
|
||||||
from ..utils import (
|
from ..compat import (
|
||||||
compat_str,
|
compat_str,
|
||||||
|
compat_urllib_parse,
|
||||||
compat_urllib_request,
|
compat_urllib_request,
|
||||||
|
)
|
||||||
|
from ..utils import (
|
||||||
int_or_none,
|
int_or_none,
|
||||||
float_or_none,
|
float_or_none,
|
||||||
xpath_text,
|
xpath_text,
|
||||||
@@ -14,7 +17,7 @@ from ..utils import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class AtresPlayerIE(InfoExtractor):
|
class AtresPlayerIE(SubtitlesInfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?atresplayer\.com/television/[^/]+/[^/]+/[^/]+/(?P<id>.+?)_\d+\.html'
|
_VALID_URL = r'https?://(?:www\.)?atresplayer\.com/television/[^/]+/[^/]+/[^/]+/(?P<id>.+?)_\d+\.html'
|
||||||
_TESTS = [
|
_TESTS = [
|
||||||
{
|
{
|
||||||
@@ -44,6 +47,33 @@ class AtresPlayerIE(InfoExtractor):
|
|||||||
_PLAYER_URL_TEMPLATE = 'https://servicios.atresplayer.com/episode/getplayer.json?episodePk=%s'
|
_PLAYER_URL_TEMPLATE = 'https://servicios.atresplayer.com/episode/getplayer.json?episodePk=%s'
|
||||||
_EPISODE_URL_TEMPLATE = 'http://www.atresplayer.com/episodexml/%s'
|
_EPISODE_URL_TEMPLATE = 'http://www.atresplayer.com/episodexml/%s'
|
||||||
|
|
||||||
|
_LOGIN_URL = 'https://servicios.atresplayer.com/j_spring_security_check'
|
||||||
|
|
||||||
|
def _real_initialize(self):
|
||||||
|
self._login()
|
||||||
|
|
||||||
|
def _login(self):
|
||||||
|
(username, password) = self._get_login_info()
|
||||||
|
if username is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
login_form = {
|
||||||
|
'j_username': username,
|
||||||
|
'j_password': password,
|
||||||
|
}
|
||||||
|
|
||||||
|
request = compat_urllib_request.Request(
|
||||||
|
self._LOGIN_URL, compat_urllib_parse.urlencode(login_form).encode('utf-8'))
|
||||||
|
request.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
||||||
|
response = self._download_webpage(
|
||||||
|
request, None, 'Logging in as %s' % username)
|
||||||
|
|
||||||
|
error = self._html_search_regex(
|
||||||
|
r'(?s)<ul class="list_error">(.+?)</ul>', response, 'error', default=None)
|
||||||
|
if error:
|
||||||
|
raise ExtractorError(
|
||||||
|
'Unable to login: %s' % error, expected=True)
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
@@ -65,7 +95,7 @@ class AtresPlayerIE(InfoExtractor):
|
|||||||
for fmt in ['windows', 'android_tablet']:
|
for fmt in ['windows', 'android_tablet']:
|
||||||
request = compat_urllib_request.Request(
|
request = compat_urllib_request.Request(
|
||||||
self._URL_VIDEO_TEMPLATE.format(fmt, episode_id, timestamp_shifted, token))
|
self._URL_VIDEO_TEMPLATE.format(fmt, episode_id, timestamp_shifted, token))
|
||||||
request.add_header('Youtubedl-user-agent', self._USER_AGENT)
|
request.add_header('User-Agent', self._USER_AGENT)
|
||||||
|
|
||||||
fmt_json = self._download_json(
|
fmt_json = self._download_json(
|
||||||
request, video_id, 'Downloading %s video JSON' % fmt)
|
request, video_id, 'Downloading %s video JSON' % fmt)
|
||||||
@@ -75,13 +105,22 @@ class AtresPlayerIE(InfoExtractor):
|
|||||||
raise ExtractorError(
|
raise ExtractorError(
|
||||||
'%s returned error: %s' % (self.IE_NAME, result), expected=True)
|
'%s returned error: %s' % (self.IE_NAME, result), expected=True)
|
||||||
|
|
||||||
for _, video_url in fmt_json['resultObject'].items():
|
for format_id, video_url in fmt_json['resultObject'].items():
|
||||||
|
if format_id == 'token' or not video_url.startswith('http'):
|
||||||
|
continue
|
||||||
if video_url.endswith('/Manifest'):
|
if video_url.endswith('/Manifest'):
|
||||||
formats.extend(self._extract_f4m_formats(video_url[:-9] + '/manifest.f4m', video_id))
|
if 'geodeswowsmpra3player' in video_url:
|
||||||
|
f4m_path = video_url.split('smil:', 1)[-1].split('free_', 1)[0]
|
||||||
|
f4m_url = 'http://drg.antena3.com/{0}hds/es/sd.f4m'.format(f4m_path)
|
||||||
|
# this videos are protected by DRM, the f4m downloader doesn't support them
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
f4m_url = video_url[:-9] + '/manifest.f4m'
|
||||||
|
formats.extend(self._extract_f4m_formats(f4m_url, video_id))
|
||||||
else:
|
else:
|
||||||
formats.append({
|
formats.append({
|
||||||
'url': video_url,
|
'url': video_url,
|
||||||
'format_id': 'android',
|
'format_id': 'android-%s' % format_id,
|
||||||
'preference': 1,
|
'preference': 1,
|
||||||
})
|
})
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
@@ -104,6 +143,15 @@ class AtresPlayerIE(InfoExtractor):
|
|||||||
description = xpath_text(art, './description', 'description')
|
description = xpath_text(art, './description', 'description')
|
||||||
thumbnail = xpath_text(episode, './media/asset/files/background', 'thumbnail')
|
thumbnail = xpath_text(episode, './media/asset/files/background', 'thumbnail')
|
||||||
|
|
||||||
|
subtitles = {}
|
||||||
|
subtitle = xpath_text(episode, './media/asset/files/subtitle', 'subtitle')
|
||||||
|
if subtitle:
|
||||||
|
subtitles['es'] = subtitle
|
||||||
|
|
||||||
|
if self._downloader.params.get('listsubtitles', False):
|
||||||
|
self._list_available_subtitles(video_id, subtitles)
|
||||||
|
return
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': title,
|
'title': title,
|
||||||
@@ -111,4 +159,5 @@ class AtresPlayerIE(InfoExtractor):
|
|||||||
'thumbnail': thumbnail,
|
'thumbnail': thumbnail,
|
||||||
'duration': duration,
|
'duration': duration,
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
|
'subtitles': self.extract_subtitles(video_id, subtitles),
|
||||||
}
|
}
|
||||||
|
@@ -88,16 +88,21 @@ class AudiomackAlbumIE(InfoExtractor):
|
|||||||
# Album playlist ripped from fakeshoredrive with no metadata
|
# Album playlist ripped from fakeshoredrive with no metadata
|
||||||
{
|
{
|
||||||
'url': 'http://www.audiomack.com/album/fakeshoredrive/ppp-pistol-p-project',
|
'url': 'http://www.audiomack.com/album/fakeshoredrive/ppp-pistol-p-project',
|
||||||
|
'info_dict': {
|
||||||
|
'title': 'PPP (Pistol P Project)',
|
||||||
|
'id': '837572',
|
||||||
|
},
|
||||||
'playlist': [{
|
'playlist': [{
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'title': '9.-heaven-or-hell-chimaca-ft-zuse-prod-by-dj-fu',
|
'title': 'PPP (Pistol P Project) - 9. Heaven or Hell (CHIMACA) ft Zuse (prod by DJ FU)',
|
||||||
'id': '9.-heaven-or-hell-chimaca-ft-zuse-prod-by-dj-fu',
|
'id': '837577',
|
||||||
'ext': 'mp3',
|
'ext': 'mp3',
|
||||||
|
'uploader': 'Lil Herb a.k.a. G Herbo',
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
'params': {
|
'params': {
|
||||||
'playliststart': 8,
|
'playliststart': 9,
|
||||||
'playlistend': 8,
|
'playlistend': 9,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@@ -1,50 +0,0 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
|
||||||
from ..compat import compat_urllib_parse
|
|
||||||
from ..utils import (
|
|
||||||
determine_ext,
|
|
||||||
ExtractorError,
|
|
||||||
remove_end,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class AUEngineIE(InfoExtractor):
|
|
||||||
_VALID_URL = r'http://(?:www\.)?auengine\.com/embed\.php\?.*?file=(?P<id>[^&]+).*?'
|
|
||||||
|
|
||||||
_TEST = {
|
|
||||||
'url': 'http://auengine.com/embed.php?file=lfvlytY6&w=650&h=370',
|
|
||||||
'md5': '48972bdbcf1a3a2f5533e62425b41d4f',
|
|
||||||
'info_dict': {
|
|
||||||
'id': 'lfvlytY6',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': '[Commie]The Legend of the Legendary Heroes - 03 - Replication Eye (Alpha Stigma)[F9410F5A]'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
|
||||||
video_id = self._match_id(url)
|
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
|
||||||
title = self._html_search_regex(
|
|
||||||
r'<title>\s*(?P<title>.+?)\s*</title>', webpage, 'title')
|
|
||||||
video_urls = re.findall(r'http://\w+.auengine.com/vod/.*[^\W]', webpage)
|
|
||||||
video_url = compat_urllib_parse.unquote(video_urls[0])
|
|
||||||
thumbnails = re.findall(r'http://\w+.auengine.com/thumb/.*[^\W]', webpage)
|
|
||||||
thumbnail = compat_urllib_parse.unquote(thumbnails[0])
|
|
||||||
|
|
||||||
if not video_url:
|
|
||||||
raise ExtractorError('Could not find video URL')
|
|
||||||
|
|
||||||
ext = '.' + determine_ext(video_url)
|
|
||||||
title = remove_end(title, ext)
|
|
||||||
|
|
||||||
return {
|
|
||||||
'id': video_id,
|
|
||||||
'url': video_url,
|
|
||||||
'title': title,
|
|
||||||
'thumbnail': thumbnail,
|
|
||||||
'http_referer': 'http://www.auengine.com/flowplayer/flowplayer.commercial-3.2.14.swf',
|
|
||||||
}
|
|
@@ -161,7 +161,8 @@ class BandcampAlbumIE(InfoExtractor):
|
|||||||
entries = [
|
entries = [
|
||||||
self.url_result(compat_urlparse.urljoin(url, t_path), ie=BandcampIE.ie_key())
|
self.url_result(compat_urlparse.urljoin(url, t_path), ie=BandcampIE.ie_key())
|
||||||
for t_path in tracks_paths]
|
for t_path in tracks_paths]
|
||||||
title = self._search_regex(r'album_title : "(.*?)"', webpage, 'title')
|
title = self._search_regex(
|
||||||
|
r'album_title\s*:\s*"(.*?)"', webpage, 'title', fatal=False)
|
||||||
return {
|
return {
|
||||||
'_type': 'playlist',
|
'_type': 'playlist',
|
||||||
'id': playlist_id,
|
'id': playlist_id,
|
||||||
|
@@ -10,7 +10,7 @@ from ..compat import compat_HTTPError
|
|||||||
class BBCCoUkIE(SubtitlesInfoExtractor):
|
class BBCCoUkIE(SubtitlesInfoExtractor):
|
||||||
IE_NAME = 'bbc.co.uk'
|
IE_NAME = 'bbc.co.uk'
|
||||||
IE_DESC = 'BBC iPlayer'
|
IE_DESC = 'BBC iPlayer'
|
||||||
_VALID_URL = r'https?://(?:www\.)?bbc\.co\.uk/(?:(?:(?:programmes|iplayer/(?:episode|playlist))/)|music/clips[/#])(?P<id>[\da-z]{8})'
|
_VALID_URL = r'https?://(?:www\.)?bbc\.co\.uk/(?:(?:(?:programmes|iplayer(?:/[^/]+)?/(?:episode|playlist))/)|music/clips[/#])(?P<id>[\da-z]{8})'
|
||||||
|
|
||||||
_TESTS = [
|
_TESTS = [
|
||||||
{
|
{
|
||||||
@@ -118,6 +118,9 @@ class BBCCoUkIE(SubtitlesInfoExtractor):
|
|||||||
}, {
|
}, {
|
||||||
'url': 'http://www.bbc.co.uk/music/clips#p02frcc3',
|
'url': 'http://www.bbc.co.uk/music/clips#p02frcc3',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.bbc.co.uk/iplayer/cbeebies/episode/b0480276/bing-14-atchoo',
|
||||||
|
'only_matching': True,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@@ -199,7 +199,7 @@ class BlipTVIE(SubtitlesInfoExtractor):
|
|||||||
# For some weird reason, blip.tv serves a video instead of subtitles
|
# For some weird reason, blip.tv serves a video instead of subtitles
|
||||||
# when we request with a common UA
|
# when we request with a common UA
|
||||||
req = compat_urllib_request.Request(url)
|
req = compat_urllib_request.Request(url)
|
||||||
req.add_header('Youtubedl-user-agent', 'youtube-dl')
|
req.add_header('User-Agent', 'youtube-dl')
|
||||||
return self._download_webpage(req, None, note=False)
|
return self._download_webpage(req, None, note=False)
|
||||||
|
|
||||||
|
|
||||||
|
@@ -28,12 +28,10 @@ class CinchcastIE(InfoExtractor):
|
|||||||
item, './{http://developer.longtailvideo.com/trac/}date')
|
item, './{http://developer.longtailvideo.com/trac/}date')
|
||||||
upload_date = unified_strdate(date_str, day_first=False)
|
upload_date = unified_strdate(date_str, day_first=False)
|
||||||
# duration is present but wrong
|
# duration is present but wrong
|
||||||
formats = []
|
formats = [{
|
||||||
formats.append({
|
|
||||||
'format_id': 'main',
|
'format_id': 'main',
|
||||||
'url': item.find(
|
'url': item.find('./{http://search.yahoo.com/mrss/}content').attrib['url'],
|
||||||
'./{http://search.yahoo.com/mrss/}content').attrib['url'],
|
}]
|
||||||
})
|
|
||||||
backup_url = xpath_text(
|
backup_url = xpath_text(
|
||||||
item, './{http://developer.longtailvideo.com/trac/}backupContent')
|
item, './{http://developer.longtailvideo.com/trac/}backupContent')
|
||||||
if backup_url:
|
if backup_url:
|
||||||
|
@@ -1,9 +1,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import json
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from ..utils import determine_ext
|
||||||
|
|
||||||
|
|
||||||
_translation_table = {
|
_translation_table = {
|
||||||
@@ -27,10 +25,10 @@ class CliphunterIE(InfoExtractor):
|
|||||||
'''
|
'''
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.cliphunter.com/w/1012420/Fun_Jynx_Maze_solo',
|
'url': 'http://www.cliphunter.com/w/1012420/Fun_Jynx_Maze_solo',
|
||||||
'md5': 'a2ba71eebf523859fe527a61018f723e',
|
'md5': 'b7c9bbd4eb3a226ab91093714dcaa480',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '1012420',
|
'id': '1012420',
|
||||||
'ext': 'mp4',
|
'ext': 'flv',
|
||||||
'title': 'Fun Jynx Maze solo',
|
'title': 'Fun Jynx Maze solo',
|
||||||
'thumbnail': 're:^https?://.*\.jpg$',
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
'age_limit': 18,
|
'age_limit': 18,
|
||||||
@@ -44,39 +42,31 @@ class CliphunterIE(InfoExtractor):
|
|||||||
video_title = self._search_regex(
|
video_title = self._search_regex(
|
||||||
r'mediaTitle = "([^"]+)"', webpage, 'title')
|
r'mediaTitle = "([^"]+)"', webpage, 'title')
|
||||||
|
|
||||||
pl_fiji = self._search_regex(
|
fmts = {}
|
||||||
r'pl_fiji = \'([^\']+)\'', webpage, 'video data')
|
for fmt in ('mp4', 'flv'):
|
||||||
pl_c_qual = self._search_regex(
|
fmt_list = self._parse_json(self._search_regex(
|
||||||
r'pl_c_qual = "(.)"', webpage, 'video quality')
|
r'var %sjson\s*=\s*(\[.*?\]);' % fmt, webpage, '%s formats' % fmt), video_id)
|
||||||
video_url = _decode(pl_fiji)
|
for f in fmt_list:
|
||||||
formats = [{
|
fmts[f['fname']] = _decode(f['sUrl'])
|
||||||
'url': video_url,
|
|
||||||
'format_id': 'default-%s' % pl_c_qual,
|
|
||||||
}]
|
|
||||||
|
|
||||||
qualities_json = self._search_regex(
|
qualities = self._parse_json(self._search_regex(
|
||||||
r'var pl_qualities\s*=\s*(.*?);\n', webpage, 'quality info')
|
r'var player_btns\s*=\s*(.*?);\n', webpage, 'quality info'), video_id)
|
||||||
qualities_data = json.loads(qualities_json)
|
|
||||||
|
|
||||||
for i, t in enumerate(
|
formats = []
|
||||||
re.findall(r"pl_fiji_([a-z0-9]+)\s*=\s*'([^']+')", webpage)):
|
for fname, url in fmts.items():
|
||||||
quality_id, crypted_url = t
|
|
||||||
video_url = _decode(crypted_url)
|
|
||||||
f = {
|
f = {
|
||||||
'format_id': quality_id,
|
'url': url,
|
||||||
'url': video_url,
|
|
||||||
'quality': i,
|
|
||||||
}
|
}
|
||||||
if quality_id in qualities_data:
|
if fname in qualities:
|
||||||
qd = qualities_data[quality_id]
|
qual = qualities[fname]
|
||||||
m = re.match(
|
f.update({
|
||||||
r'''(?x)<b>(?P<width>[0-9]+)x(?P<height>[0-9]+)<\\/b>
|
'format_id': '%s_%sp' % (determine_ext(url), qual['h']),
|
||||||
\s*\(\s*(?P<tbr>[0-9]+)\s*kb\\/s''', qd)
|
'width': qual['w'],
|
||||||
if m:
|
'height': qual['h'],
|
||||||
f['width'] = int(m.group('width'))
|
'tbr': qual['br'],
|
||||||
f['height'] = int(m.group('height'))
|
})
|
||||||
f['tbr'] = int(m.group('tbr'))
|
|
||||||
formats.append(f)
|
formats.append(f)
|
||||||
|
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
thumbnail = self._search_regex(
|
thumbnail = self._search_regex(
|
||||||
|
@@ -51,7 +51,7 @@ class CNNIE(InfoExtractor):
|
|||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
path = mobj.group('path')
|
path = mobj.group('path')
|
||||||
page_title = mobj.group('title')
|
page_title = mobj.group('title')
|
||||||
info_url = 'http://cnn.com/video/data/3.0/%s/index.xml' % path
|
info_url = 'http://edition.cnn.com/video/data/3.0/%s/index.xml' % path
|
||||||
info = self._download_xml(info_url, page_title)
|
info = self._download_xml(info_url, page_title)
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
@@ -143,13 +143,13 @@ class CNNArticleIE(InfoExtractor):
|
|||||||
_VALID_URL = r'https?://(?:(?:edition|www)\.)?cnn\.com/(?!video/)'
|
_VALID_URL = r'https?://(?:(?:edition|www)\.)?cnn\.com/(?!video/)'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.cnn.com/2014/12/21/politics/obama-north-koreas-hack-not-war-but-cyber-vandalism/',
|
'url': 'http://www.cnn.com/2014/12/21/politics/obama-north-koreas-hack-not-war-but-cyber-vandalism/',
|
||||||
'md5': '275b326f85d80dff7592a9820f5dc887',
|
'md5': '689034c2a3d9c6dc4aa72d65a81efd01',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'bestoftv/2014/12/21/sotu-crowley-president-obama-north-korea-not-going-to-be-intimidated.cnn',
|
'id': 'bestoftv/2014/12/21/ip-north-korea-obama.cnn',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Obama: We\'re not going to be intimidated',
|
'title': 'Obama: Cyberattack not an act of war',
|
||||||
'description': 'md5:e735586f3dc936075fa654a4d91b21f9',
|
'description': 'md5:51ce6750450603795cad0cdfbd7d05c5',
|
||||||
'upload_date': '20141220',
|
'upload_date': '20141221',
|
||||||
},
|
},
|
||||||
'add_ie': ['CNN'],
|
'add_ie': ['CNN'],
|
||||||
}
|
}
|
||||||
|
@@ -34,12 +34,12 @@ class ComedyCentralIE(MTVServicesInfoExtractor):
|
|||||||
|
|
||||||
class ComedyCentralShowsIE(MTVServicesInfoExtractor):
|
class ComedyCentralShowsIE(MTVServicesInfoExtractor):
|
||||||
IE_DESC = 'The Daily Show / The Colbert Report'
|
IE_DESC = 'The Daily Show / The Colbert Report'
|
||||||
# urls can be abbreviations like :thedailyshow or :colbert
|
# urls can be abbreviations like :thedailyshow
|
||||||
# urls for episodes like:
|
# urls for episodes like:
|
||||||
# or urls for clips like: http://www.thedailyshow.com/watch/mon-december-10-2012/any-given-gun-day
|
# or urls for clips like: http://www.thedailyshow.com/watch/mon-december-10-2012/any-given-gun-day
|
||||||
# or: http://www.colbertnation.com/the-colbert-report-videos/421667/november-29-2012/moon-shattering-news
|
# or: http://www.colbertnation.com/the-colbert-report-videos/421667/november-29-2012/moon-shattering-news
|
||||||
# or: http://www.colbertnation.com/the-colbert-report-collections/422008/festival-of-lights/79524
|
# or: http://www.colbertnation.com/the-colbert-report-collections/422008/festival-of-lights/79524
|
||||||
_VALID_URL = r'''(?x)^(:(?P<shortname>tds|thedailyshow|cr|colbert|colbertnation|colbertreport)
|
_VALID_URL = r'''(?x)^(:(?P<shortname>tds|thedailyshow)
|
||||||
|https?://(:www\.)?
|
|https?://(:www\.)?
|
||||||
(?P<showname>thedailyshow|thecolbertreport)\.(?:cc\.)?com/
|
(?P<showname>thedailyshow|thecolbertreport)\.(?:cc\.)?com/
|
||||||
((?:full-)?episodes/(?:[0-9a-z]{6}/)?(?P<episode>.*)|
|
((?:full-)?episodes/(?:[0-9a-z]{6}/)?(?P<episode>.*)|
|
||||||
@@ -49,7 +49,9 @@ class ComedyCentralShowsIE(MTVServicesInfoExtractor):
|
|||||||
|(watch/(?P<date>[^/]*)/(?P<tdstitle>.*))
|
|(watch/(?P<date>[^/]*)/(?P<tdstitle>.*))
|
||||||
)|
|
)|
|
||||||
(?P<interview>
|
(?P<interview>
|
||||||
extended-interviews/(?P<interID>[0-9a-z]+)/(?:playlist_tds_extended_)?(?P<interview_title>.*?)(/.*?)?)))
|
extended-interviews/(?P<interID>[0-9a-z]+)/
|
||||||
|
(?:playlist_tds_extended_)?(?P<interview_title>[^/?#]*?)
|
||||||
|
(?:/[^/?#]?|[?#]|$))))
|
||||||
'''
|
'''
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://thedailyshow.cc.com/watch/thu-december-13-2012/kristen-stewart',
|
'url': 'http://thedailyshow.cc.com/watch/thu-december-13-2012/kristen-stewart',
|
||||||
@@ -62,6 +64,38 @@ class ComedyCentralShowsIE(MTVServicesInfoExtractor):
|
|||||||
'uploader': 'thedailyshow',
|
'uploader': 'thedailyshow',
|
||||||
'title': 'thedailyshow kristen-stewart part 1',
|
'title': 'thedailyshow kristen-stewart part 1',
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
'url': 'http://thedailyshow.cc.com/extended-interviews/b6364d/sarah-chayes-extended-interview',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'sarah-chayes-extended-interview',
|
||||||
|
'description': 'Carnegie Endowment Senior Associate Sarah Chayes discusses how corrupt institutions function throughout the world in her book "Thieves of State: Why Corruption Threatens Global Security."',
|
||||||
|
'title': 'thedailyshow Sarah Chayes Extended Interview',
|
||||||
|
},
|
||||||
|
'playlist': [
|
||||||
|
{
|
||||||
|
'info_dict': {
|
||||||
|
'id': '0baad492-cbec-4ec1-9e50-ad91c291127f',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'upload_date': '20150129',
|
||||||
|
'description': 'Carnegie Endowment Senior Associate Sarah Chayes discusses how corrupt institutions function throughout the world in her book "Thieves of State: Why Corruption Threatens Global Security."',
|
||||||
|
'uploader': 'thedailyshow',
|
||||||
|
'title': 'thedailyshow sarah-chayes-extended-interview part 1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'info_dict': {
|
||||||
|
'id': '1e4fb91b-8ce7-4277-bd7c-98c9f1bbd283',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'upload_date': '20150129',
|
||||||
|
'description': 'Carnegie Endowment Senior Associate Sarah Chayes discusses how corrupt institutions function throughout the world in her book "Thieves of State: Why Corruption Threatens Global Security."',
|
||||||
|
'uploader': 'thedailyshow',
|
||||||
|
'title': 'thedailyshow sarah-chayes-extended-interview part 2',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'params': {
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://thedailyshow.cc.com/extended-interviews/xm3fnq/andrew-napolitano-extended-interview',
|
'url': 'http://thedailyshow.cc.com/extended-interviews/xm3fnq/andrew-napolitano-extended-interview',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
@@ -230,6 +264,7 @@ class ComedyCentralShowsIE(MTVServicesInfoExtractor):
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
'_type': 'playlist',
|
'_type': 'playlist',
|
||||||
|
'id': epTitle,
|
||||||
'entries': entries,
|
'entries': entries,
|
||||||
'title': show_name + ' ' + title,
|
'title': show_name + ' ' + title,
|
||||||
'description': description,
|
'description': description,
|
||||||
|
@@ -14,6 +14,7 @@ import xml.etree.ElementTree
|
|||||||
|
|
||||||
from ..compat import (
|
from ..compat import (
|
||||||
compat_cookiejar,
|
compat_cookiejar,
|
||||||
|
compat_HTTPError,
|
||||||
compat_http_client,
|
compat_http_client,
|
||||||
compat_urllib_error,
|
compat_urllib_error,
|
||||||
compat_urllib_parse_urlparse,
|
compat_urllib_parse_urlparse,
|
||||||
@@ -26,6 +27,7 @@ from ..utils import (
|
|||||||
compiled_regex_type,
|
compiled_regex_type,
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
float_or_none,
|
float_or_none,
|
||||||
|
HEADRequest,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
RegexNotFoundError,
|
RegexNotFoundError,
|
||||||
sanitize_filename,
|
sanitize_filename,
|
||||||
@@ -87,7 +89,8 @@ class InfoExtractor(object):
|
|||||||
* 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.
|
||||||
"http", "https", "rtsp", "rtmp", "m3u8" or so.
|
"http", "https", "rtsp", "rtmp", "rtmpe",
|
||||||
|
"m3u8", or "m3u8_native".
|
||||||
* preference Order number of this format. If this field is
|
* preference Order number of this format. If this field is
|
||||||
present and not None, the formats get sorted
|
present and not None, the formats get sorted
|
||||||
by this field, regardless of all other values.
|
by this field, regardless of all other values.
|
||||||
@@ -108,7 +111,6 @@ class InfoExtractor(object):
|
|||||||
(quality takes higher priority)
|
(quality takes higher priority)
|
||||||
-1 for default (order by other properties),
|
-1 for default (order by other properties),
|
||||||
-2 or smaller for less than default.
|
-2 or smaller for less than default.
|
||||||
* http_referer HTTP Referer header value to set.
|
|
||||||
* http_method HTTP method to use for the download.
|
* http_method HTTP method to use for the download.
|
||||||
* http_headers A dictionary of additional HTTP headers
|
* http_headers A dictionary of additional HTTP headers
|
||||||
to add to the request.
|
to add to the request.
|
||||||
@@ -117,6 +119,9 @@ class InfoExtractor(object):
|
|||||||
* stretched_ratio If given and not 1, indicates that the
|
* stretched_ratio If given and not 1, indicates that the
|
||||||
video's pixels are not square.
|
video's pixels are not square.
|
||||||
width : height ratio as float.
|
width : height ratio as float.
|
||||||
|
* no_resume The server does not support resuming the
|
||||||
|
(HTTP or RTMP) download. Boolean.
|
||||||
|
|
||||||
url: Final video URL.
|
url: Final video URL.
|
||||||
ext: Video filename extension.
|
ext: Video filename extension.
|
||||||
format: The video format, defaults to ext (used for --get-format)
|
format: The video format, defaults to ext (used for --get-format)
|
||||||
@@ -130,7 +135,9 @@ class InfoExtractor(object):
|
|||||||
something like "4234987", title "Dancing naked mole rats",
|
something like "4234987", title "Dancing naked mole rats",
|
||||||
and display_id "dancing-naked-mole-rats"
|
and display_id "dancing-naked-mole-rats"
|
||||||
thumbnails: A list of dictionaries, with the following entries:
|
thumbnails: A list of dictionaries, with the following entries:
|
||||||
|
* "id" (optional, string) - Thumbnail format ID
|
||||||
* "url"
|
* "url"
|
||||||
|
* "preference" (optional, int) - quality of the image
|
||||||
* "width" (optional, int)
|
* "width" (optional, int)
|
||||||
* "height" (optional, int)
|
* "height" (optional, int)
|
||||||
* "resolution" (optional, string "{width}x{height"},
|
* "resolution" (optional, string "{width}x{height"},
|
||||||
@@ -712,6 +719,27 @@ class InfoExtractor(object):
|
|||||||
)
|
)
|
||||||
formats.sort(key=_formats_key)
|
formats.sort(key=_formats_key)
|
||||||
|
|
||||||
|
def _check_formats(self, formats, video_id):
|
||||||
|
if formats:
|
||||||
|
formats[:] = filter(
|
||||||
|
lambda f: self._is_valid_url(
|
||||||
|
f['url'], video_id,
|
||||||
|
item='%s video format' % f.get('format_id') if f.get('format_id') else 'video'),
|
||||||
|
formats)
|
||||||
|
|
||||||
|
def _is_valid_url(self, url, video_id, item='video'):
|
||||||
|
try:
|
||||||
|
self._request_webpage(
|
||||||
|
HEADRequest(url), video_id,
|
||||||
|
'Checking %s URL' % item)
|
||||||
|
return True
|
||||||
|
except ExtractorError as e:
|
||||||
|
if isinstance(e.cause, compat_HTTPError):
|
||||||
|
self.report_warning(
|
||||||
|
'%s URL is invalid, skipping' % item, video_id)
|
||||||
|
return False
|
||||||
|
raise
|
||||||
|
|
||||||
def http_scheme(self):
|
def http_scheme(self):
|
||||||
""" Either "http:" or "https:", depending on the user's preferences """
|
""" Either "http:" or "https:", depending on the user's preferences """
|
||||||
return (
|
return (
|
||||||
|
93
youtube_dl/extractor/ctsnews.py
Normal file
93
youtube_dl/extractor/ctsnews.py
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import parse_iso8601, ExtractorError
|
||||||
|
|
||||||
|
|
||||||
|
class CtsNewsIE(InfoExtractor):
|
||||||
|
# https connection failed (Connection reset)
|
||||||
|
_VALID_URL = r'http://news\.cts\.com\.tw/[a-z]+/[a-z]+/\d+/(?P<id>\d+)\.html'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://news.cts.com.tw/cts/international/201501/201501291578109.html',
|
||||||
|
'md5': 'a9875cb790252b08431186d741beaabe',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '201501291578109',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': '以色列.真主黨交火 3人死亡',
|
||||||
|
'description': 'md5:95e9b295c898b7ff294f09d450178d7d',
|
||||||
|
'timestamp': 1422528540,
|
||||||
|
'upload_date': '20150129',
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
# News count not appear on page but still available in database
|
||||||
|
'url': 'http://news.cts.com.tw/cts/international/201309/201309031304098.html',
|
||||||
|
'md5': '3aee7e0df7cdff94e43581f54c22619e',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '201309031304098',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': '韓國31歲童顏男 貌如十多歲小孩',
|
||||||
|
'description': 'md5:f183feeba3752b683827aab71adad584',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
'timestamp': 1378205880,
|
||||||
|
'upload_date': '20130903',
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
# With Youtube embedded video
|
||||||
|
'url': 'http://news.cts.com.tw/cts/money/201501/201501291578003.html',
|
||||||
|
'md5': '1d842c771dc94c8c3bca5af2cc1db9c5',
|
||||||
|
'add_ie': ['Youtube'],
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'OVbfO7d0_hQ',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'iPhone6熱銷 蘋果財報亮眼',
|
||||||
|
'description': 'md5:f395d4f485487bb0f992ed2c4b07aa7d',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
'upload_date': '20150128',
|
||||||
|
'uploader_id': 'TBSCTS',
|
||||||
|
'uploader': '中華電視公司',
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
news_id = self._match_id(url)
|
||||||
|
page = self._download_webpage(url, news_id)
|
||||||
|
|
||||||
|
if self._search_regex(r'(CTSPlayer2)', page, 'CTSPlayer2 identifier', default=None):
|
||||||
|
feed_url = self._html_search_regex(
|
||||||
|
r'(http://news\.cts\.com\.tw/action/mp4feed\.php\?news_id=\d+)',
|
||||||
|
page, 'feed url')
|
||||||
|
video_url = self._download_webpage(
|
||||||
|
feed_url, news_id, note='Fetching feed')
|
||||||
|
else:
|
||||||
|
self.to_screen('Not CTSPlayer video, trying Youtube...')
|
||||||
|
youtube_url = self._search_regex(
|
||||||
|
r'src="(//www\.youtube\.com/embed/[^"]+)"', page, 'youtube url',
|
||||||
|
default=None)
|
||||||
|
if not youtube_url:
|
||||||
|
raise ExtractorError('The news includes no videos!', expected=True)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'_type': 'url',
|
||||||
|
'url': youtube_url,
|
||||||
|
'ie_key': 'Youtube',
|
||||||
|
}
|
||||||
|
|
||||||
|
description = self._html_search_meta('description', page)
|
||||||
|
title = self._html_search_meta('title', page)
|
||||||
|
thumbnail = self._html_search_meta('image', page)
|
||||||
|
|
||||||
|
datetime_str = self._html_search_regex(
|
||||||
|
r'(\d{4}/\d{2}/\d{2} \d{2}:\d{2})', page, 'date and time')
|
||||||
|
# Transform into ISO 8601 format with timezone info
|
||||||
|
datetime_str = datetime_str.replace('/', '-') + ':00+0800'
|
||||||
|
timestamp = parse_iso8601(datetime_str, delimiter=' ')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': news_id,
|
||||||
|
'url': video_url,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'timestamp': timestamp,
|
||||||
|
}
|
57
youtube_dl/extractor/dctp.py
Normal file
57
youtube_dl/extractor/dctp.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# encoding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..compat import compat_str
|
||||||
|
|
||||||
|
|
||||||
|
class DctpTvIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'http://www.dctp.tv/(#/)?filme/(?P<id>.+?)/$'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.dctp.tv/filme/videoinstallation-fuer-eine-kaufhausfassade/',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '1324',
|
||||||
|
'display_id': 'videoinstallation-fuer-eine-kaufhausfassade',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Videoinstallation für eine Kaufhausfassade'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
base_url = 'http://dctp-ivms2-restapi.s3.amazonaws.com/'
|
||||||
|
version_json = self._download_json(
|
||||||
|
base_url + 'version.json',
|
||||||
|
video_id, note='Determining file version')
|
||||||
|
version = version_json['version_name']
|
||||||
|
info_json = self._download_json(
|
||||||
|
'{0}{1}/restapi/slugs/{2}.json'.format(base_url, version, video_id),
|
||||||
|
video_id, note='Fetching object ID')
|
||||||
|
object_id = compat_str(info_json['object_id'])
|
||||||
|
meta_json = self._download_json(
|
||||||
|
'{0}{1}/restapi/media/{2}.json'.format(base_url, version, object_id),
|
||||||
|
video_id, note='Downloading metadata')
|
||||||
|
uuid = meta_json['uuid']
|
||||||
|
title = meta_json['title']
|
||||||
|
wide = meta_json['is_wide']
|
||||||
|
if wide:
|
||||||
|
ratio = '16x9'
|
||||||
|
else:
|
||||||
|
ratio = '4x3'
|
||||||
|
play_path = 'mp4:{0}_dctp_0500_{1}.m4v'.format(uuid, ratio)
|
||||||
|
|
||||||
|
servers_json = self._download_json(
|
||||||
|
'http://www.dctp.tv/streaming_servers/',
|
||||||
|
video_id, note='Downloading server list')
|
||||||
|
url = servers_json[0]['endpoint']
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': object_id,
|
||||||
|
'title': title,
|
||||||
|
'format': 'rtmp',
|
||||||
|
'url': url,
|
||||||
|
'play_path': play_path,
|
||||||
|
'rtmp_real_time': True,
|
||||||
|
'ext': 'flv',
|
||||||
|
'display_id': video_id
|
||||||
|
}
|
@@ -1,39 +1,37 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
|
||||||
import json
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
class DefenseGouvFrIE(InfoExtractor):
|
class DefenseGouvFrIE(InfoExtractor):
|
||||||
IE_NAME = 'defense.gouv.fr'
|
IE_NAME = 'defense.gouv.fr'
|
||||||
_VALID_URL = (r'http://.*?\.defense\.gouv\.fr/layout/set/'
|
_VALID_URL = r'http://.*?\.defense\.gouv\.fr/layout/set/ligthboxvideo/base-de-medias/webtv/(?P<id>[^/?#]*)'
|
||||||
r'ligthboxvideo/base-de-medias/webtv/(.*)')
|
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.defense.gouv.fr/layout/set/ligthboxvideo/base-de-medias/webtv/attaque-chimique-syrienne-du-21-aout-2013-1',
|
'url': 'http://www.defense.gouv.fr/layout/set/ligthboxvideo/base-de-medias/webtv/attaque-chimique-syrienne-du-21-aout-2013-1',
|
||||||
'file': '11213.mp4',
|
|
||||||
'md5': '75bba6124da7e63d2d60b5244ec9430c',
|
'md5': '75bba6124da7e63d2d60b5244ec9430c',
|
||||||
"info_dict": {
|
'info_dict': {
|
||||||
"title": "attaque-chimique-syrienne-du-21-aout-2013-1"
|
'id': '11213',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'attaque-chimique-syrienne-du-21-aout-2013-1'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
title = re.match(self._VALID_URL, url).group(1)
|
title = self._match_id(url)
|
||||||
webpage = self._download_webpage(url, title)
|
webpage = self._download_webpage(url, title)
|
||||||
|
|
||||||
video_id = self._search_regex(
|
video_id = self._search_regex(
|
||||||
r"flashvars.pvg_id=\"(\d+)\";",
|
r"flashvars.pvg_id=\"(\d+)\";",
|
||||||
webpage, 'ID')
|
webpage, 'ID')
|
||||||
|
|
||||||
json_url = ('http://static.videos.gouv.fr/brightcovehub/export/json/'
|
json_url = ('http://static.videos.gouv.fr/brightcovehub/export/json/'
|
||||||
+ video_id)
|
+ video_id)
|
||||||
info = self._download_webpage(json_url, title,
|
info = self._download_json(json_url, title, 'Downloading JSON config')
|
||||||
'Downloading JSON config')
|
video_url = info['renditions'][0]['url']
|
||||||
video_url = json.loads(info)['renditions'][0]['url']
|
|
||||||
|
|
||||||
return {'id': video_id,
|
return {
|
||||||
|
'id': video_id,
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'url': video_url,
|
'url': video_url,
|
||||||
'title': title,
|
'title': title,
|
||||||
|
131
youtube_dl/extractor/drbonanza.py
Normal file
131
youtube_dl/extractor/drbonanza.py
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
int_or_none,
|
||||||
|
parse_iso8601,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DRBonanzaIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?dr\.dk/bonanza/(?:[^/]+/)+(?:[^/])+?(?:assetId=(?P<id>\d+))?(?:[#&]|$)'
|
||||||
|
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://www.dr.dk/bonanza/serie/portraetter/Talkshowet.htm?assetId=65517',
|
||||||
|
'md5': 'fe330252ddea607635cf2eb2c99a0af3',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '65517',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Talkshowet - Leonard Cohen',
|
||||||
|
'description': 'md5:8f34194fb30cd8c8a30ad8b27b70c0ca',
|
||||||
|
'thumbnail': 're:^https?://.*\.(?:gif|jpg)$',
|
||||||
|
'timestamp': 1295537932,
|
||||||
|
'upload_date': '20110120',
|
||||||
|
'duration': 3664,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.dr.dk/bonanza/radio/serie/sport/fodbold.htm?assetId=59410',
|
||||||
|
'md5': '6dfe039417e76795fb783c52da3de11d',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '59410',
|
||||||
|
'ext': 'mp3',
|
||||||
|
'title': 'EM fodbold 1992 Danmark - Tyskland finale Transmission',
|
||||||
|
'description': 'md5:501e5a195749480552e214fbbed16c4e',
|
||||||
|
'thumbnail': 're:^https?://.*\.(?:gif|jpg)$',
|
||||||
|
'timestamp': 1223274900,
|
||||||
|
'upload_date': '20081006',
|
||||||
|
'duration': 7369,
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
url_id = self._match_id(url)
|
||||||
|
webpage = self._download_webpage(url, url_id)
|
||||||
|
|
||||||
|
if url_id:
|
||||||
|
info = json.loads(self._html_search_regex(r'({.*?%s.*})' % url_id, webpage, 'json'))
|
||||||
|
else:
|
||||||
|
# Just fetch the first video on that page
|
||||||
|
info = json.loads(self._html_search_regex(r'bonanzaFunctions.newPlaylist\(({.*})\)', webpage, 'json'))
|
||||||
|
|
||||||
|
asset_id = str(info['AssetId'])
|
||||||
|
title = info['Title'].rstrip(' \'\"-,.:;!?')
|
||||||
|
duration = int_or_none(info.get('Duration'), scale=1000)
|
||||||
|
# First published online. "FirstPublished" contains the date for original airing.
|
||||||
|
timestamp = parse_iso8601(
|
||||||
|
re.sub(r'\.\d+$', '', info['Created']))
|
||||||
|
|
||||||
|
def parse_filename_info(url):
|
||||||
|
match = re.search(r'/\d+_(?P<width>\d+)x(?P<height>\d+)x(?P<bitrate>\d+)K\.(?P<ext>\w+)$', url)
|
||||||
|
if match:
|
||||||
|
return {
|
||||||
|
'width': int(match.group('width')),
|
||||||
|
'height': int(match.group('height')),
|
||||||
|
'vbr': int(match.group('bitrate')),
|
||||||
|
'ext': match.group('ext')
|
||||||
|
}
|
||||||
|
match = re.search(r'/\d+_(?P<bitrate>\d+)K\.(?P<ext>\w+)$', url)
|
||||||
|
if match:
|
||||||
|
return {
|
||||||
|
'vbr': int(match.group('bitrate')),
|
||||||
|
'ext': match.group(2)
|
||||||
|
}
|
||||||
|
return {}
|
||||||
|
|
||||||
|
video_types = ['VideoHigh', 'VideoMid', 'VideoLow']
|
||||||
|
preferencemap = {
|
||||||
|
'VideoHigh': -1,
|
||||||
|
'VideoMid': -2,
|
||||||
|
'VideoLow': -3,
|
||||||
|
'Audio': -4,
|
||||||
|
}
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
for file in info['Files']:
|
||||||
|
if info['Type'] == "Video":
|
||||||
|
if file['Type'] in video_types:
|
||||||
|
format = parse_filename_info(file['Location'])
|
||||||
|
format.update({
|
||||||
|
'url': file['Location'],
|
||||||
|
'format_id': file['Type'].replace('Video', ''),
|
||||||
|
'preference': preferencemap.get(file['Type'], -10),
|
||||||
|
})
|
||||||
|
formats.append(format)
|
||||||
|
elif file['Type'] == "Thumb":
|
||||||
|
thumbnail = file['Location']
|
||||||
|
elif info['Type'] == "Audio":
|
||||||
|
if file['Type'] == "Audio":
|
||||||
|
format = parse_filename_info(file['Location'])
|
||||||
|
format.update({
|
||||||
|
'url': file['Location'],
|
||||||
|
'format_id': file['Type'],
|
||||||
|
'vcodec': 'none',
|
||||||
|
})
|
||||||
|
formats.append(format)
|
||||||
|
elif file['Type'] == "Thumb":
|
||||||
|
thumbnail = file['Location']
|
||||||
|
|
||||||
|
description = '%s\n%s\n%s\n' % (
|
||||||
|
info['Description'], info['Actors'], info['Colophon'])
|
||||||
|
|
||||||
|
for f in formats:
|
||||||
|
f['url'] = f['url'].replace('rtmp://vod-bonanza.gss.dr.dk/bonanza/', 'http://vodfiles.dr.dk/')
|
||||||
|
f['url'] = f['url'].replace('mp4:bonanza', 'bonanza')
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
display_id = re.sub(r'[^\w\d-]', '', re.sub(r' ', '-', title.lower())) + '-' + asset_id
|
||||||
|
display_id = re.sub(r'-+', '-', display_id)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': asset_id,
|
||||||
|
'display_id': display_id,
|
||||||
|
'title': title,
|
||||||
|
'formats': formats,
|
||||||
|
'description': description,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'timestamp': timestamp,
|
||||||
|
'duration': duration,
|
||||||
|
}
|
@@ -6,7 +6,7 @@ from ..utils import parse_iso8601
|
|||||||
|
|
||||||
|
|
||||||
class DRTVIE(SubtitlesInfoExtractor):
|
class DRTVIE(SubtitlesInfoExtractor):
|
||||||
_VALID_URL = r'http://(?:www\.)?dr\.dk/tv/se/(?:[^/]+/)+(?P<id>[\da-z-]+)(?:[/#?]|$)'
|
_VALID_URL = r'https?://(?:www\.)?dr\.dk/tv/se/(?:[^/]+/)*(?P<id>[\da-z-]+)(?:[/#?]|$)'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.dr.dk/tv/se/partiets-mand/partiets-mand-7-8',
|
'url': 'http://www.dr.dk/tv/se/partiets-mand/partiets-mand-7-8',
|
||||||
@@ -48,14 +48,20 @@ class DRTVIE(SubtitlesInfoExtractor):
|
|||||||
elif asset['Kind'] == 'VideoResource':
|
elif asset['Kind'] == 'VideoResource':
|
||||||
duration = asset['DurationInMilliseconds'] / 1000.0
|
duration = asset['DurationInMilliseconds'] / 1000.0
|
||||||
restricted_to_denmark = asset['RestrictedToDenmark']
|
restricted_to_denmark = asset['RestrictedToDenmark']
|
||||||
|
spoken_subtitles = asset['Target'] == 'SpokenSubtitles'
|
||||||
for link in asset['Links']:
|
for link in asset['Links']:
|
||||||
target = link['Target']
|
target = link['Target']
|
||||||
uri = link['Uri']
|
uri = link['Uri']
|
||||||
|
format_id = target
|
||||||
|
preference = -1 if target == 'HDS' else -2
|
||||||
|
if spoken_subtitles:
|
||||||
|
preference -= 2
|
||||||
|
format_id += '-spoken-subtitles'
|
||||||
formats.append({
|
formats.append({
|
||||||
'url': uri + '?hdcore=3.3.0&plugin=aasp-3.3.0.99.43' if target == 'HDS' else uri,
|
'url': uri + '?hdcore=3.3.0&plugin=aasp-3.3.0.99.43' if target == 'HDS' else uri,
|
||||||
'format_id': target,
|
'format_id': format_id,
|
||||||
'ext': link['FileFormat'],
|
'ext': link['FileFormat'],
|
||||||
'preference': -1 if target == 'HDS' else -2,
|
'preference': preference,
|
||||||
})
|
})
|
||||||
subtitles_list = asset.get('SubtitlesList')
|
subtitles_list = asset.get('SubtitlesList')
|
||||||
if isinstance(subtitles_list, list):
|
if isinstance(subtitles_list, list):
|
||||||
|
@@ -5,6 +5,7 @@ import hashlib
|
|||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import (
|
||||||
|
compat_urllib_parse,
|
||||||
compat_urllib_request,
|
compat_urllib_request,
|
||||||
compat_urlparse,
|
compat_urlparse,
|
||||||
)
|
)
|
||||||
@@ -16,7 +17,8 @@ from ..utils import (
|
|||||||
class FC2IE(InfoExtractor):
|
class FC2IE(InfoExtractor):
|
||||||
_VALID_URL = r'^http://video\.fc2\.com/(?:[^/]+/)?content/(?P<id>[^/]+)'
|
_VALID_URL = r'^http://video\.fc2\.com/(?:[^/]+/)?content/(?P<id>[^/]+)'
|
||||||
IE_NAME = 'fc2'
|
IE_NAME = 'fc2'
|
||||||
_TEST = {
|
_NETRC_MACHINE = 'fc2'
|
||||||
|
_TESTS = [{
|
||||||
'url': 'http://video.fc2.com/en/content/20121103kUan1KHs',
|
'url': 'http://video.fc2.com/en/content/20121103kUan1KHs',
|
||||||
'md5': 'a6ebe8ebe0396518689d963774a54eb7',
|
'md5': 'a6ebe8ebe0396518689d963774a54eb7',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@@ -24,12 +26,57 @@ class FC2IE(InfoExtractor):
|
|||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'title': 'Boxing again with Puff',
|
'title': 'Boxing again with Puff',
|
||||||
},
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://video.fc2.com/en/content/20150125cEva0hDn/',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '20150125cEva0hDn',
|
||||||
|
'ext': 'mp4',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'username': 'ytdl@yt-dl.org',
|
||||||
|
'password': '(snip)',
|
||||||
|
'skip': 'requires actual password'
|
||||||
}
|
}
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _login(self):
|
||||||
|
(username, password) = self._get_login_info()
|
||||||
|
if username is None or password is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Log in
|
||||||
|
login_form_strs = {
|
||||||
|
'email': username,
|
||||||
|
'password': password,
|
||||||
|
'done': 'video',
|
||||||
|
'Submit': ' Login ',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Convert to UTF-8 *before* urlencode because Python 2.x's urlencode
|
||||||
|
# chokes on unicode
|
||||||
|
login_form = dict((k.encode('utf-8'), v.encode('utf-8')) for k, v in login_form_strs.items())
|
||||||
|
login_data = compat_urllib_parse.urlencode(login_form).encode('utf-8')
|
||||||
|
request = compat_urllib_request.Request(
|
||||||
|
'https://secure.id.fc2.com/index.php?mode=login&switch_language=en', login_data)
|
||||||
|
|
||||||
|
login_results = self._download_webpage(request, None, note='Logging in', errnote='Unable to log in')
|
||||||
|
if 'mode=redirect&login=done' not in login_results:
|
||||||
|
self.report_warning('unable to log in: bad username or password')
|
||||||
|
return False
|
||||||
|
|
||||||
|
# this is also needed
|
||||||
|
login_redir = compat_urllib_request.Request('http://id.fc2.com/?mode=redirect&login=done')
|
||||||
|
self._download_webpage(
|
||||||
|
login_redir, None, note='Login redirect', errnote='Login redirect failed')
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
|
self._login()
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
self._downloader.cookiejar.clear_session_cookies() # must clear
|
self._downloader.cookiejar.clear_session_cookies() # must clear
|
||||||
|
self._login()
|
||||||
|
|
||||||
title = self._og_search_title(webpage)
|
title = self._og_search_title(webpage)
|
||||||
thumbnail = self._og_search_thumbnail(webpage)
|
thumbnail = self._og_search_thumbnail(webpage)
|
||||||
@@ -46,7 +93,12 @@ class FC2IE(InfoExtractor):
|
|||||||
info = compat_urlparse.parse_qs(info_webpage)
|
info = compat_urlparse.parse_qs(info_webpage)
|
||||||
|
|
||||||
if 'err_code' in info:
|
if 'err_code' in info:
|
||||||
raise ExtractorError('Error code: %s' % info['err_code'][0])
|
# most of the time we can still download wideo even if err_code is 403 or 602
|
||||||
|
self.report_warning(
|
||||||
|
'Error code was: %s... but still trying' % info['err_code'][0])
|
||||||
|
|
||||||
|
if 'filepath' not in info:
|
||||||
|
raise ExtractorError('Cannot download file. Are you logged in?')
|
||||||
|
|
||||||
video_url = info['filepath'][0] + '?mid=' + info['mid'][0]
|
video_url = info['filepath'][0] + '?mid=' + info['mid'][0]
|
||||||
title_info = info.get('title')
|
title_info = info.get('title')
|
||||||
|
@@ -16,6 +16,7 @@ class FolketingetIE(InfoExtractor):
|
|||||||
_VALID_URL = r'https?://(?:www\.)?ft\.dk/webtv/video/[^?#]*?\.(?P<id>[0-9]+)\.aspx'
|
_VALID_URL = r'https?://(?:www\.)?ft\.dk/webtv/video/[^?#]*?\.(?P<id>[0-9]+)\.aspx'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.ft.dk/webtv/video/20141/eru/td.1165642.aspx?as=1#player',
|
'url': 'http://www.ft.dk/webtv/video/20141/eru/td.1165642.aspx?as=1#player',
|
||||||
|
'md5': '6269e8626fa1a891bf5369b386ae996a',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '1165642',
|
'id': '1165642',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
@@ -29,9 +30,6 @@ class FolketingetIE(InfoExtractor):
|
|||||||
'upload_date': '20141120',
|
'upload_date': '20141120',
|
||||||
'duration': 3960,
|
'duration': 3960,
|
||||||
},
|
},
|
||||||
'params': {
|
|
||||||
'skip_download': 'rtmpdump required',
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
|
@@ -7,10 +7,9 @@ from ..compat import (
|
|||||||
compat_urllib_request,
|
compat_urllib_request,
|
||||||
)
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
clean_html,
|
|
||||||
parse_duration,
|
parse_duration,
|
||||||
|
parse_iso8601,
|
||||||
str_to_int,
|
str_to_int,
|
||||||
unified_strdate,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -28,68 +27,81 @@ class FourTubeIE(InfoExtractor):
|
|||||||
'uploader': 'WCP Club',
|
'uploader': 'WCP Club',
|
||||||
'uploader_id': 'wcp-club',
|
'uploader_id': 'wcp-club',
|
||||||
'upload_date': '20131031',
|
'upload_date': '20131031',
|
||||||
|
'timestamp': 1383263892,
|
||||||
'duration': 583,
|
'duration': 583,
|
||||||
|
'view_count': int,
|
||||||
|
'like_count': int,
|
||||||
|
'categories': list,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
webpage_url = 'http://www.4tube.com/videos/' + video_id
|
webpage = self._download_webpage(url, video_id)
|
||||||
webpage = self._download_webpage(webpage_url, video_id)
|
|
||||||
|
|
||||||
self.report_extraction(video_id)
|
title = self._html_search_meta('name', webpage)
|
||||||
|
timestamp = parse_iso8601(self._html_search_meta(
|
||||||
|
'uploadDate', webpage))
|
||||||
|
thumbnail = self._html_search_meta('thumbnailUrl', webpage)
|
||||||
|
uploader_id = self._html_search_regex(
|
||||||
|
r'<a class="img-avatar" href="[^"]+/channels/([^/"]+)" title="Go to [^"]+ page">',
|
||||||
|
webpage, 'uploader id')
|
||||||
|
uploader = self._html_search_regex(
|
||||||
|
r'<a class="img-avatar" href="[^"]+/channels/[^/"]+" title="Go to ([^"]+) page">',
|
||||||
|
webpage, 'uploader')
|
||||||
|
|
||||||
playlist_json = self._html_search_regex(r'var playerConfigPlaylist\s+=\s+([^;]+)', webpage, 'Playlist')
|
categories_html = self._search_regex(
|
||||||
media_id = self._search_regex(r'idMedia:\s*(\d+)', playlist_json, 'Media Id')
|
r'(?s)><i class="icon icon-tag"></i>\s*Categories / Tags\s*.*?<ul class="list">(.*?)</ul>',
|
||||||
sources = self._search_regex(r'sources:\s*\[([^\]]*)\]', playlist_json, 'Sources').split(',')
|
webpage, 'categories', fatal=False)
|
||||||
title = self._search_regex(r'title:\s*"([^"]*)', playlist_json, 'Title')
|
categories = None
|
||||||
thumbnail_url = self._search_regex(r'image:\s*"([^"]*)', playlist_json, 'Thumbnail', fatal=False)
|
if categories_html:
|
||||||
|
categories = [
|
||||||
|
c.strip() for c in re.findall(
|
||||||
|
r'(?s)<li><a.*?>(.*?)</a>', categories_html)]
|
||||||
|
|
||||||
uploader_str = self._search_regex(r'<span>Uploaded by</span>(.*?)<span>', webpage, 'uploader', fatal=False)
|
view_count = str_to_int(self._search_regex(
|
||||||
mobj = re.search(r'<a href="/sites/(?P<id>[^"]+)"><strong>(?P<name>[^<]+)</strong></a>', uploader_str)
|
r'<meta itemprop="interactionCount" content="UserPlays:([0-9,]+)">',
|
||||||
(uploader, uploader_id) = (mobj.group('name'), mobj.group('id')) if mobj else (clean_html(uploader_str), None)
|
webpage, 'view count', fatal=False))
|
||||||
|
like_count = str_to_int(self._search_regex(
|
||||||
|
r'<meta itemprop="interactionCount" content="UserLikes:([0-9,]+)">',
|
||||||
|
webpage, 'like count', fatal=False))
|
||||||
|
duration = parse_duration(self._html_search_meta('duration', webpage))
|
||||||
|
|
||||||
upload_date = None
|
params_js = self._search_regex(
|
||||||
view_count = None
|
r'\$\.ajax\(url,\ opts\);\s*\}\s*\}\)\(([0-9,\[\] ]+)\)',
|
||||||
duration = None
|
webpage, 'initialization parameters'
|
||||||
description = self._html_search_meta('description', webpage, 'description')
|
)
|
||||||
if description:
|
params = self._parse_json('[%s]' % params_js, video_id)
|
||||||
upload_date = self._search_regex(r'Published Date: (\d{2} [a-zA-Z]{3} \d{4})', description, 'upload date',
|
media_id = params[0]
|
||||||
fatal=False)
|
sources = ['%s' % p for p in params[2]]
|
||||||
if upload_date:
|
|
||||||
upload_date = unified_strdate(upload_date)
|
|
||||||
view_count = self._search_regex(r'Views: ([\d,\.]+)', description, 'view count', fatal=False)
|
|
||||||
if view_count:
|
|
||||||
view_count = str_to_int(view_count)
|
|
||||||
duration = parse_duration(self._search_regex(r'Length: (\d+m\d+s)', description, 'duration', fatal=False))
|
|
||||||
|
|
||||||
token_url = "http://tkn.4tube.com/{0}/desktop/{1}".format(media_id, "+".join(sources))
|
token_url = 'http://tkn.4tube.com/{0}/desktop/{1}'.format(
|
||||||
|
media_id, '+'.join(sources))
|
||||||
headers = {
|
headers = {
|
||||||
b'Content-Type': b'application/x-www-form-urlencoded',
|
b'Content-Type': b'application/x-www-form-urlencoded',
|
||||||
b'Origin': b'http://www.4tube.com',
|
b'Origin': b'http://www.4tube.com',
|
||||||
}
|
}
|
||||||
token_req = compat_urllib_request.Request(token_url, b'{}', headers)
|
token_req = compat_urllib_request.Request(token_url, b'{}', headers)
|
||||||
tokens = self._download_json(token_req, video_id)
|
tokens = self._download_json(token_req, video_id)
|
||||||
|
|
||||||
formats = [{
|
formats = [{
|
||||||
'url': tokens[format]['token'],
|
'url': tokens[format]['token'],
|
||||||
'format_id': format + 'p',
|
'format_id': format + 'p',
|
||||||
'resolution': format + 'p',
|
'resolution': format + 'p',
|
||||||
'quality': int(format),
|
'quality': int(format),
|
||||||
} for format in sources]
|
} for format in sources]
|
||||||
|
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': title,
|
'title': title,
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
'thumbnail': thumbnail_url,
|
'categories': categories,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
'uploader': uploader,
|
'uploader': uploader,
|
||||||
'uploader_id': uploader_id,
|
'uploader_id': uploader_id,
|
||||||
'upload_date': upload_date,
|
'timestamp': timestamp,
|
||||||
|
'like_count': like_count,
|
||||||
'view_count': view_count,
|
'view_count': view_count,
|
||||||
'duration': duration,
|
'duration': duration,
|
||||||
'age_limit': 18,
|
'age_limit': 18,
|
||||||
'webpage_url': webpage_url,
|
|
||||||
}
|
}
|
||||||
|
@@ -230,12 +230,13 @@ class FranceTVIE(FranceTVBaseInfoExtractor):
|
|||||||
|
|
||||||
class GenerationQuoiIE(InfoExtractor):
|
class GenerationQuoiIE(InfoExtractor):
|
||||||
IE_NAME = 'france2.fr:generation-quoi'
|
IE_NAME = 'france2.fr:generation-quoi'
|
||||||
_VALID_URL = r'https?://generation-quoi\.france2\.fr/portrait/(?P<name>.*)(\?|$)'
|
_VALID_URL = r'https?://generation-quoi\.france2\.fr/portrait/(?P<id>[^/?#]+)'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://generation-quoi.france2.fr/portrait/garde-a-vous',
|
'url': 'http://generation-quoi.france2.fr/portrait/garde-a-vous',
|
||||||
'file': 'k7FJX8VBcvvLmX4wA5Q.mp4',
|
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'id': 'k7FJX8VBcvvLmX4wA5Q',
|
||||||
|
'ext': 'mp4',
|
||||||
'title': 'Génération Quoi - Garde à Vous',
|
'title': 'Génération Quoi - Garde à Vous',
|
||||||
'uploader': 'Génération Quoi',
|
'uploader': 'Génération Quoi',
|
||||||
},
|
},
|
||||||
@@ -243,14 +244,12 @@ class GenerationQuoiIE(InfoExtractor):
|
|||||||
# It uses Dailymotion
|
# It uses Dailymotion
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
},
|
},
|
||||||
'skip': 'Only available from France',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
display_id = self._match_id(url)
|
||||||
name = mobj.group('name')
|
info_url = compat_urlparse.urljoin(url, '/medias/video/%s.json' % display_id)
|
||||||
info_url = compat_urlparse.urljoin(url, '/medias/video/%s.json' % name)
|
info_json = self._download_webpage(info_url, display_id)
|
||||||
info_json = self._download_webpage(info_url, name)
|
|
||||||
info = json.loads(info_json)
|
info = json.loads(info_json)
|
||||||
return self.url_result('http://www.dailymotion.com/video/%s' % info['id'],
|
return self.url_result('http://www.dailymotion.com/video/%s' % info['id'],
|
||||||
ie='Dailymotion')
|
ie='Dailymotion')
|
||||||
|
@@ -1,8 +1,6 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
int_or_none,
|
int_or_none,
|
||||||
@@ -29,9 +27,7 @@ class GameStarIE(InfoExtractor):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = mobj.group('id')
|
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
og_title = self._og_search_title(webpage)
|
og_title = self._og_search_title(webpage)
|
||||||
|
@@ -17,6 +17,7 @@ from ..utils import (
|
|||||||
ExtractorError,
|
ExtractorError,
|
||||||
float_or_none,
|
float_or_none,
|
||||||
HEADRequest,
|
HEADRequest,
|
||||||
|
is_html,
|
||||||
orderedSet,
|
orderedSet,
|
||||||
parse_xml,
|
parse_xml,
|
||||||
smuggle_url,
|
smuggle_url,
|
||||||
@@ -361,7 +362,7 @@ class GenericIE(InfoExtractor):
|
|||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'http://phihag.de/2014/youtube-dl/rss2.xml',
|
'id': 'http://phihag.de/2014/youtube-dl/rss2.xml',
|
||||||
'title': 'Zero Punctuation',
|
'title': 'Zero Punctuation',
|
||||||
'description': 're:'
|
'description': 're:.*groundbreaking video review series.*'
|
||||||
},
|
},
|
||||||
'playlist_mincount': 11,
|
'playlist_mincount': 11,
|
||||||
},
|
},
|
||||||
@@ -488,6 +489,29 @@ class GenericIE(InfoExtractor):
|
|||||||
'title': 'Jack Tips: 5 Steps to Permanent Gut Healing',
|
'title': 'Jack Tips: 5 Steps to Permanent Gut Healing',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
# Cinerama player
|
||||||
|
{
|
||||||
|
'url': 'http://www.abc.net.au/7.30/content/2015/s4164797.htm',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '730m_DandD_1901_512k',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'uploader': 'www.abc.net.au',
|
||||||
|
'title': 'Game of Thrones with dice - Dungeons and Dragons fantasy role-playing game gets new life - 19/01/2015',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
# embedded viddler video
|
||||||
|
{
|
||||||
|
'url': 'http://deadspin.com/i-cant-stop-watching-john-wall-chop-the-nuggets-with-th-1681801597',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '4d03aad9',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'uploader': 'deadspin',
|
||||||
|
'title': 'WALL-TO-GORTAT',
|
||||||
|
'timestamp': 1422285291,
|
||||||
|
'upload_date': '20150126',
|
||||||
|
},
|
||||||
|
'add_ie': ['Viddler'],
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
def report_following_redirect(self, new_url):
|
def report_following_redirect(self, new_url):
|
||||||
@@ -647,7 +671,7 @@ class GenericIE(InfoExtractor):
|
|||||||
# Maybe it's a direct link to a video?
|
# Maybe it's a direct link to a video?
|
||||||
# Be careful not to download the whole thing!
|
# Be careful not to download the whole thing!
|
||||||
first_bytes = full_response.read(512)
|
first_bytes = full_response.read(512)
|
||||||
if not re.match(r'^\s*<', first_bytes.decode('utf-8', 'replace')):
|
if not is_html(first_bytes):
|
||||||
self._downloader.report_warning(
|
self._downloader.report_warning(
|
||||||
'URL could be a direct video link, returning it as such.')
|
'URL could be a direct video link, returning it as such.')
|
||||||
upload_date = unified_strdate(
|
upload_date = unified_strdate(
|
||||||
@@ -849,9 +873,16 @@ class GenericIE(InfoExtractor):
|
|||||||
if mobj is not None:
|
if mobj is not None:
|
||||||
return self.url_result(mobj.group('url'))
|
return self.url_result(mobj.group('url'))
|
||||||
|
|
||||||
|
# Look for embedded Viddler player
|
||||||
|
mobj = re.search(
|
||||||
|
r'<(?:iframe[^>]+?src|param[^>]+?value)=(["\'])(?P<url>(?:https?:)?//(?:www\.)?viddler\.com/(?:embed|player)/.+?)\1',
|
||||||
|
webpage)
|
||||||
|
if mobj is not None:
|
||||||
|
return self.url_result(mobj.group('url'))
|
||||||
|
|
||||||
# Look for Ooyala videos
|
# Look for Ooyala videos
|
||||||
mobj = (re.search(r'player.ooyala.com/[^"?]+\?[^"]*?(?:embedCode|ec)=(?P<ec>[^"&]+)', webpage) or
|
mobj = (re.search(r'player\.ooyala\.com/[^"?]+\?[^"]*?(?:embedCode|ec)=(?P<ec>[^"&]+)', webpage) or
|
||||||
re.search(r'OO.Player.create\([\'"].*?[\'"],\s*[\'"](?P<ec>.{32})[\'"]', webpage))
|
re.search(r'OO\.Player\.create\([\'"].*?[\'"],\s*[\'"](?P<ec>.{32})[\'"]', webpage))
|
||||||
if mobj is not None:
|
if mobj is not None:
|
||||||
return OoyalaIE._build_url_result(mobj.group('ec'))
|
return OoyalaIE._build_url_result(mobj.group('ec'))
|
||||||
|
|
||||||
@@ -1042,9 +1073,13 @@ class GenericIE(InfoExtractor):
|
|||||||
found = filter_video(re.findall(r'''(?xs)
|
found = filter_video(re.findall(r'''(?xs)
|
||||||
flowplayer\("[^"]+",\s*
|
flowplayer\("[^"]+",\s*
|
||||||
\{[^}]+?\}\s*,
|
\{[^}]+?\}\s*,
|
||||||
\s*{[^}]+? ["']?clip["']?\s*:\s*\{\s*
|
\s*\{[^}]+? ["']?clip["']?\s*:\s*\{\s*
|
||||||
["']?url["']?\s*:\s*["']([^"']+)["']
|
["']?url["']?\s*:\s*["']([^"']+)["']
|
||||||
''', webpage))
|
''', webpage))
|
||||||
|
if not found:
|
||||||
|
# Cinerama player
|
||||||
|
found = re.findall(
|
||||||
|
r"cinerama\.embedPlayer\(\s*\'[^']+\',\s*'([^']+)'", webpage)
|
||||||
if not found:
|
if not found:
|
||||||
# Try to find twitter cards info
|
# Try to find twitter cards info
|
||||||
found = filter_video(re.findall(
|
found = filter_video(re.findall(
|
||||||
|
@@ -70,6 +70,19 @@ class GloboIE(InfoExtractor):
|
|||||||
'like_count': int,
|
'like_count': int,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://globotv.globo.com/canal-brasil/sangue-latino/t/todos-os-videos/v/ator-e-diretor-argentino-ricado-darin-fala-sobre-utopias-e-suas-perdas/3928201/',
|
||||||
|
'md5': 'c1defca721ce25b2354e927d3e4b3dec',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '3928201',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Ator e diretor argentino, Ricado Darín fala sobre utopias e suas perdas',
|
||||||
|
'duration': 1472.906,
|
||||||
|
'uploader': 'Canal Brasil',
|
||||||
|
'uploader_id': 705,
|
||||||
|
'like_count': int,
|
||||||
|
}
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
class MD5():
|
class MD5():
|
||||||
@@ -381,10 +394,15 @@ class GloboIE(InfoExtractor):
|
|||||||
signed_md5 = self.MD5.b64_md5(received_md5 + compat_str(sign_time) + padding)
|
signed_md5 = self.MD5.b64_md5(received_md5 + compat_str(sign_time) + padding)
|
||||||
signed_hash = hash_code + compat_str(received_time) + received_random + compat_str(sign_time) + padding + signed_md5
|
signed_hash = hash_code + compat_str(received_time) + received_random + compat_str(sign_time) + padding + signed_md5
|
||||||
|
|
||||||
|
resource_url = resource['url']
|
||||||
|
signed_url = '%s?h=%s&k=%s' % (resource_url, signed_hash, 'flash')
|
||||||
|
if resource_id.endswith('m3u8') or resource_url.endswith('.m3u8'):
|
||||||
|
formats.extend(self._extract_m3u8_formats(signed_url, resource_id, 'mp4'))
|
||||||
|
else:
|
||||||
formats.append({
|
formats.append({
|
||||||
'url': '%s?h=%s&k=%s' % (resource['url'], signed_hash, 'flash'),
|
'url': signed_url,
|
||||||
'format_id': resource_id,
|
'format_id': resource_id,
|
||||||
'height': resource['height']
|
'height': resource.get('height'),
|
||||||
})
|
})
|
||||||
|
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
@@ -83,7 +83,7 @@ class GroovesharkIE(InfoExtractor):
|
|||||||
return compat_urlparse.urlunparse((uri.scheme, uri.netloc, obj['attrs']['data'], None, None, None))
|
return compat_urlparse.urlunparse((uri.scheme, uri.netloc, obj['attrs']['data'], None, None, None))
|
||||||
|
|
||||||
def _transform_bootstrap(self, js):
|
def _transform_bootstrap(self, js):
|
||||||
return re.split('(?m)^\s*try\s*{', js)[0] \
|
return re.split('(?m)^\s*try\s*\{', js)[0] \
|
||||||
.split(' = ', 1)[1].strip().rstrip(';')
|
.split(' = ', 1)[1].strip().rstrip(';')
|
||||||
|
|
||||||
def _transform_meta(self, js):
|
def _transform_meta(self, js):
|
||||||
|
117
youtube_dl/extractor/hearthisat.py
Normal file
117
youtube_dl/extractor/hearthisat.py
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..compat import (
|
||||||
|
compat_urllib_request,
|
||||||
|
compat_urlparse,
|
||||||
|
)
|
||||||
|
from ..utils import (
|
||||||
|
HEADRequest,
|
||||||
|
str_to_int,
|
||||||
|
urlencode_postdata,
|
||||||
|
urlhandle_detect_ext,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class HearThisAtIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?hearthis\.at/(?P<artist>[^/]+)/(?P<title>[A-Za-z0-9\-]+)/?$'
|
||||||
|
_PLAYLIST_URL = 'https://hearthis.at/playlist.php'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'https://hearthis.at/moofi/dr-kreep',
|
||||||
|
'md5': 'ab6ec33c8fed6556029337c7885eb4e0',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '150939',
|
||||||
|
'ext': 'wav',
|
||||||
|
'title': 'Moofi - Dr. Kreep',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
'timestamp': 1421564134,
|
||||||
|
'description': 'Creepy Patch. Mutable Instruments Braids Vowel + Formant Mode.',
|
||||||
|
'upload_date': '20150118',
|
||||||
|
'comment_count': int,
|
||||||
|
'view_count': int,
|
||||||
|
'like_count': int,
|
||||||
|
'duration': 71,
|
||||||
|
'categories': ['Experimental'],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
m = re.match(self._VALID_URL, url)
|
||||||
|
display_id = '{artist:s} - {title:s}'.format(**m.groupdict())
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
track_id = self._search_regex(
|
||||||
|
r'intTrackId\s*=\s*(\d+)', webpage, 'track ID')
|
||||||
|
|
||||||
|
payload = urlencode_postdata({'tracks[]': track_id})
|
||||||
|
req = compat_urllib_request.Request(self._PLAYLIST_URL, payload)
|
||||||
|
req.add_header('Content-type', 'application/x-www-form-urlencoded')
|
||||||
|
|
||||||
|
track = self._download_json(req, track_id, 'Downloading playlist')[0]
|
||||||
|
title = '{artist:s} - {title:s}'.format(**track)
|
||||||
|
|
||||||
|
categories = None
|
||||||
|
if track.get('category'):
|
||||||
|
categories = [track['category']]
|
||||||
|
|
||||||
|
description = self._og_search_description(webpage)
|
||||||
|
thumbnail = self._og_search_thumbnail(webpage)
|
||||||
|
|
||||||
|
meta_span = r'<span[^>]+class="%s".*?</i>([^<]+)</span>'
|
||||||
|
view_count = str_to_int(self._search_regex(
|
||||||
|
meta_span % 'plays_count', webpage, 'view count', fatal=False))
|
||||||
|
like_count = str_to_int(self._search_regex(
|
||||||
|
meta_span % 'likes_count', webpage, 'like count', fatal=False))
|
||||||
|
comment_count = str_to_int(self._search_regex(
|
||||||
|
meta_span % 'comment_count', webpage, 'comment count', fatal=False))
|
||||||
|
duration = str_to_int(self._search_regex(
|
||||||
|
r'data-length="(\d+)', webpage, 'duration', fatal=False))
|
||||||
|
timestamp = str_to_int(self._search_regex(
|
||||||
|
r'<span[^>]+class="calctime"[^>]+data-time="(\d+)', webpage, 'timestamp', fatal=False))
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
mp3_url = self._search_regex(
|
||||||
|
r'(?s)<a class="player-link"\s+(?:[a-zA-Z0-9_:-]+="[^"]+"\s+)*?data-mp3="([^"]+)"',
|
||||||
|
webpage, 'mp3 URL', fatal=False)
|
||||||
|
if mp3_url:
|
||||||
|
formats.append({
|
||||||
|
'format_id': 'mp3',
|
||||||
|
'vcodec': 'none',
|
||||||
|
'acodec': 'mp3',
|
||||||
|
'url': mp3_url,
|
||||||
|
})
|
||||||
|
download_path = self._search_regex(
|
||||||
|
r'<a class="[^"]*download_fct[^"]*"\s+href="([^"]+)"',
|
||||||
|
webpage, 'download URL', default=None)
|
||||||
|
if download_path:
|
||||||
|
download_url = compat_urlparse.urljoin(url, download_path)
|
||||||
|
ext_req = HEADRequest(download_url)
|
||||||
|
ext_handle = self._request_webpage(
|
||||||
|
ext_req, display_id, note='Determining extension')
|
||||||
|
ext = urlhandle_detect_ext(ext_handle)
|
||||||
|
formats.append({
|
||||||
|
'format_id': 'download',
|
||||||
|
'vcodec': 'none',
|
||||||
|
'ext': ext,
|
||||||
|
'url': download_url,
|
||||||
|
'preference': 2, # Usually better quality
|
||||||
|
})
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': track_id,
|
||||||
|
'display_id': display_id,
|
||||||
|
'title': title,
|
||||||
|
'formats': formats,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'description': description,
|
||||||
|
'duration': duration,
|
||||||
|
'timestamp': timestamp,
|
||||||
|
'view_count': view_count,
|
||||||
|
'comment_count': comment_count,
|
||||||
|
'like_count': like_count,
|
||||||
|
'categories': categories,
|
||||||
|
}
|
@@ -16,7 +16,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/(?:[^/]+/)?|video/player\?.*?videoId=)(?P<videoid>\d+)'
|
_VALID_URL = r'https?://(?:www\.)?ivi\.ru/(?:watch/(?:[^/]+/)?|video/player\?.*?videoId=)(?P<id>\d+)'
|
||||||
|
|
||||||
_TESTS = [
|
_TESTS = [
|
||||||
# Single movie
|
# Single movie
|
||||||
@@ -63,13 +63,15 @@ class IviIE(InfoExtractor):
|
|||||||
return int(m.group('commentcount')) if m is not None else 0
|
return int(m.group('commentcount')) if m is not None else 0
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = mobj.group('videoid')
|
|
||||||
|
|
||||||
api_url = 'http://api.digitalaccess.ru/api/json/'
|
api_url = 'http://api.digitalaccess.ru/api/json/'
|
||||||
|
|
||||||
data = {'method': 'da.content.get',
|
data = {
|
||||||
'params': [video_id, {'site': 's183',
|
'method': 'da.content.get',
|
||||||
|
'params': [
|
||||||
|
video_id, {
|
||||||
|
'site': 's183',
|
||||||
'referrer': 'http://www.ivi.ru/watch/%s' % video_id,
|
'referrer': 'http://www.ivi.ru/watch/%s' % video_id,
|
||||||
'contentid': video_id
|
'contentid': video_id
|
||||||
}
|
}
|
||||||
@@ -78,14 +80,17 @@ class IviIE(InfoExtractor):
|
|||||||
|
|
||||||
request = compat_urllib_request.Request(api_url, json.dumps(data))
|
request = compat_urllib_request.Request(api_url, json.dumps(data))
|
||||||
|
|
||||||
video_json_page = self._download_webpage(request, video_id, 'Downloading video JSON')
|
video_json_page = self._download_webpage(
|
||||||
|
request, video_id, 'Downloading video JSON')
|
||||||
video_json = json.loads(video_json_page)
|
video_json = json.loads(video_json_page)
|
||||||
|
|
||||||
if 'error' in video_json:
|
if 'error' in video_json:
|
||||||
error = video_json['error']
|
error = video_json['error']
|
||||||
if error['origin'] == 'NoRedisValidData':
|
if error['origin'] == 'NoRedisValidData':
|
||||||
raise ExtractorError('Video %s does not exist' % video_id, expected=True)
|
raise ExtractorError('Video %s does not exist' % video_id, expected=True)
|
||||||
raise ExtractorError('Unable to download video %s: %s' % (video_id, error['message']), expected=True)
|
raise ExtractorError(
|
||||||
|
'Unable to download video %s: %s' % (video_id, error['message']),
|
||||||
|
expected=True)
|
||||||
|
|
||||||
result = video_json['result']
|
result = video_json['result']
|
||||||
|
|
||||||
|
@@ -13,17 +13,17 @@ class KankanIE(InfoExtractor):
|
|||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://yinyue.kankan.com/vod/48/48863.shtml',
|
'url': 'http://yinyue.kankan.com/vod/48/48863.shtml',
|
||||||
'file': '48863.flv',
|
|
||||||
'md5': '29aca1e47ae68fc28804aca89f29507e',
|
'md5': '29aca1e47ae68fc28804aca89f29507e',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'id': '48863',
|
||||||
|
'ext': 'flv',
|
||||||
'title': 'Ready To Go',
|
'title': 'Ready To Go',
|
||||||
},
|
},
|
||||||
'skip': 'Only available from China',
|
'skip': 'Only available from China',
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = mobj.group('id')
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
title = self._search_regex(r'(?:G_TITLE=|G_MOVIE_TITLE = )[\'"](.+?)[\'"]', webpage, 'video title')
|
title = self._search_regex(r'(?:G_TITLE=|G_MOVIE_TITLE = )[\'"](.+?)[\'"]', webpage, 'video title')
|
||||||
|
@@ -4,7 +4,6 @@ from __future__ import unicode_literals
|
|||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import compat_urllib_parse
|
from ..compat import compat_urllib_parse
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
|
||||||
js_to_json,
|
js_to_json,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@@ -7,10 +7,6 @@ from .common import InfoExtractor
|
|||||||
from ..compat import (
|
from ..compat import (
|
||||||
compat_urllib_parse_urlparse,
|
compat_urllib_parse_urlparse,
|
||||||
compat_urllib_request,
|
compat_urllib_request,
|
||||||
compat_urllib_parse,
|
|
||||||
)
|
|
||||||
from ..aes import (
|
|
||||||
aes_decrypt_text
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -18,9 +14,10 @@ class KeezMoviesIE(InfoExtractor):
|
|||||||
_VALID_URL = r'https?://(?:www\.)?keezmovies\.com/video/.+?(?P<id>[0-9]+)(?:[/?&]|$)'
|
_VALID_URL = r'https?://(?:www\.)?keezmovies\.com/video/.+?(?P<id>[0-9]+)(?:[/?&]|$)'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.keezmovies.com/video/petite-asian-lady-mai-playing-in-bathtub-1214711',
|
'url': 'http://www.keezmovies.com/video/petite-asian-lady-mai-playing-in-bathtub-1214711',
|
||||||
'file': '1214711.mp4',
|
|
||||||
'md5': '6e297b7e789329923fcf83abb67c9289',
|
'md5': '6e297b7e789329923fcf83abb67c9289',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'id': '1214711',
|
||||||
|
'ext': 'mp4',
|
||||||
'title': 'Petite Asian Lady Mai Playing In Bathtub',
|
'title': 'Petite Asian Lady Mai Playing In Bathtub',
|
||||||
'age_limit': 18,
|
'age_limit': 18,
|
||||||
}
|
}
|
||||||
@@ -39,11 +36,10 @@ class KeezMoviesIE(InfoExtractor):
|
|||||||
embedded_url = mobj.group(1)
|
embedded_url = mobj.group(1)
|
||||||
return self.url_result(embedded_url)
|
return self.url_result(embedded_url)
|
||||||
|
|
||||||
video_title = self._html_search_regex(r'<h1 [^>]*>([^<]+)', webpage, 'title')
|
video_title = self._html_search_regex(
|
||||||
video_url = compat_urllib_parse.unquote(self._html_search_regex(r'video_url=(.+?)&', webpage, 'video_url'))
|
r'<h1 [^>]*>([^<]+)', webpage, 'title')
|
||||||
if 'encrypted=true' in webpage:
|
video_url = self._html_search_regex(
|
||||||
password = self._html_search_regex(r'video_title=(.+?)&', webpage, 'password')
|
r'(?s)html5VideoPlayer = .*?src="([^"]+)"', webpage, 'video URL')
|
||||||
video_url = aes_decrypt_text(video_url, password, 32).decode('utf-8')
|
|
||||||
path = compat_urllib_parse_urlparse(video_url).path
|
path = compat_urllib_parse_urlparse(video_url).path
|
||||||
extension = os.path.splitext(path)[1][1:]
|
extension = os.path.splitext(path)[1][1:]
|
||||||
format = path.split('/')[4].split('_')[:2]
|
format = path.split('/')[4].split('_')[:2]
|
||||||
|
@@ -2,18 +2,17 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import re
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
int_or_none,
|
int_or_none,
|
||||||
unescapeHTML,
|
js_to_json,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class KrasViewIE(InfoExtractor):
|
class KrasViewIE(InfoExtractor):
|
||||||
IE_DESC = 'Красвью'
|
IE_DESC = 'Красвью'
|
||||||
_VALID_URL = r'https?://krasview\.ru/video/(?P<id>\d+)'
|
_VALID_URL = r'https?://krasview\.ru/(?:video|embed)/(?P<id>\d+)'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://krasview.ru/video/512228',
|
'url': 'http://krasview.ru/video/512228',
|
||||||
@@ -29,20 +28,18 @@ class KrasViewIE(InfoExtractor):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = mobj.group('id')
|
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
flashvars = json.loads(self._search_regex(
|
flashvars = json.loads(js_to_json(self._search_regex(
|
||||||
r'flashvars\s*:\s*({.+?})\s*}\);', webpage, 'flashvars'))
|
r'video_Init\(({.+?})', webpage, 'flashvars')))
|
||||||
|
|
||||||
video_url = flashvars['url']
|
video_url = flashvars['url']
|
||||||
title = unescapeHTML(flashvars['title'])
|
title = self._og_search_title(webpage)
|
||||||
description = unescapeHTML(flashvars.get('subtitle') or self._og_search_description(webpage, default=None))
|
description = self._og_search_description(webpage, default=None)
|
||||||
thumbnail = flashvars['image']
|
thumbnail = flashvars.get('image') or self._og_search_thumbnail(webpage)
|
||||||
duration = int(flashvars['duration'])
|
duration = int_or_none(flashvars.get('duration'))
|
||||||
filesize = int(flashvars['size'])
|
|
||||||
width = int_or_none(self._og_search_property('video:width', webpage, 'video width'))
|
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'))
|
height = int_or_none(self._og_search_property('video:height', webpage, 'video height'))
|
||||||
|
|
||||||
@@ -53,7 +50,6 @@ class KrasViewIE(InfoExtractor):
|
|||||||
'description': description,
|
'description': description,
|
||||||
'thumbnail': thumbnail,
|
'thumbnail': thumbnail,
|
||||||
'duration': duration,
|
'duration': duration,
|
||||||
'filesize': filesize,
|
|
||||||
'width': width,
|
'width': width,
|
||||||
'height': height,
|
'height': height,
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,5 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
parse_duration,
|
parse_duration,
|
||||||
@@ -20,9 +18,10 @@ class LA7IE(InfoExtractor):
|
|||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.la7.tv/richplayer/?assetid=50355319',
|
'url': 'http://www.la7.tv/richplayer/?assetid=50355319',
|
||||||
'file': '50355319.mp4',
|
|
||||||
'md5': 'ec7d1f0224d20ba293ab56cf2259651f',
|
'md5': 'ec7d1f0224d20ba293ab56cf2259651f',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'id': '50355319',
|
||||||
|
'ext': 'mp4',
|
||||||
'title': 'IL DIVO',
|
'title': 'IL DIVO',
|
||||||
'description': 'Un film di Paolo Sorrentino con Toni Servillo, Anna Bonaiuto, Giulio Bosetti e Flavio Bucci',
|
'description': 'Un film di Paolo Sorrentino con Toni Servillo, Anna Bonaiuto, Giulio Bosetti e Flavio Bucci',
|
||||||
'duration': 6254,
|
'duration': 6254,
|
||||||
@@ -31,9 +30,7 @@ class LA7IE(InfoExtractor):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = mobj.group('id')
|
|
||||||
|
|
||||||
xml_url = 'http://www.la7.tv/repliche/content/index.php?contentId=%s' % video_id
|
xml_url = 'http://www.la7.tv/repliche/content/index.php?contentId=%s' % video_id
|
||||||
doc = self._download_xml(xml_url, video_id)
|
doc = self._download_xml(xml_url, video_id)
|
||||||
|
|
||||||
|
@@ -8,20 +8,20 @@ from ..utils import int_or_none
|
|||||||
|
|
||||||
|
|
||||||
class LiveLeakIE(InfoExtractor):
|
class LiveLeakIE(InfoExtractor):
|
||||||
_VALID_URL = r'^(?:http://)?(?:\w+\.)?liveleak\.com/view\?(?:.*?)i=(?P<video_id>[\w_]+)(?:.*)'
|
_VALID_URL = r'https?://(?:\w+\.)?liveleak\.com/view\?(?:.*?)i=(?P<id>[\w_]+)(?:.*)'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.liveleak.com/view?i=757_1364311680',
|
'url': 'http://www.liveleak.com/view?i=757_1364311680',
|
||||||
'md5': '0813c2430bea7a46bf13acf3406992f4',
|
'md5': '50f79e05ba149149c1b4ea961223d5b3',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '757_1364311680',
|
'id': '757_1364311680',
|
||||||
'ext': 'mp4',
|
'ext': 'flv',
|
||||||
'description': 'extremely bad day for this guy..!',
|
'description': 'extremely bad day for this guy..!',
|
||||||
'uploader': 'ljfriel2',
|
'uploader': 'ljfriel2',
|
||||||
'title': 'Most unlucky car accident'
|
'title': 'Most unlucky car accident'
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.liveleak.com/view?i=f93_1390833151',
|
'url': 'http://www.liveleak.com/view?i=f93_1390833151',
|
||||||
'md5': 'd3f1367d14cc3c15bf24fbfbe04b9abf',
|
'md5': 'b13a29626183c9d33944e6a04f41aafc',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'f93_1390833151',
|
'id': 'f93_1390833151',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
@@ -43,8 +43,7 @@ class LiveLeakIE(InfoExtractor):
|
|||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = mobj.group('video_id')
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
video_title = self._og_search_title(webpage).replace('LiveLeak.com -', '').strip()
|
video_title = self._og_search_title(webpage).replace('LiveLeak.com -', '').strip()
|
||||||
@@ -81,9 +80,19 @@ class LiveLeakIE(InfoExtractor):
|
|||||||
sources = json.loads(sources_json)
|
sources = json.loads(sources_json)
|
||||||
|
|
||||||
formats = [{
|
formats = [{
|
||||||
|
'format_id': '%s' % i,
|
||||||
'format_note': s.get('label'),
|
'format_note': s.get('label'),
|
||||||
'url': s['file'],
|
'url': s['file'],
|
||||||
} for s in sources]
|
} for i, s in enumerate(sources)]
|
||||||
|
for i, s in enumerate(sources):
|
||||||
|
orig_url = s['file'].replace('.h264_base.mp4', '')
|
||||||
|
if s['file'] != orig_url:
|
||||||
|
formats.append({
|
||||||
|
'format_id': 'original-%s' % i,
|
||||||
|
'format_note': s.get('label'),
|
||||||
|
'url': orig_url,
|
||||||
|
'preference': 1,
|
||||||
|
})
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
113
youtube_dl/extractor/lnkgo.py
Normal file
113
youtube_dl/extractor/lnkgo.py
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
int_or_none,
|
||||||
|
unified_strdate,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class LnkGoIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?lnkgo\.alfa\.lt/visi-video/(?P<show>[^/]+)/ziurek-(?P<id>[A-Za-z0-9-]+)'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://lnkgo.alfa.lt/visi-video/yra-kaip-yra/ziurek-yra-kaip-yra-162',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '46712',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Yra kaip yra',
|
||||||
|
'upload_date': '20150107',
|
||||||
|
'description': 'md5:d82a5e36b775b7048617f263a0e3475e',
|
||||||
|
'age_limit': 7,
|
||||||
|
'duration': 3019,
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$'
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True, # HLS download
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://lnkgo.alfa.lt/visi-video/aktualai-pratesimas/ziurek-nerdas-taiso-kompiuteri-2',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '47289',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Nėrdas: Kompiuterio Valymas',
|
||||||
|
'upload_date': '20150113',
|
||||||
|
'description': 'md5:7352d113a242a808676ff17e69db6a69',
|
||||||
|
'age_limit': 18,
|
||||||
|
'duration': 346,
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$'
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True, # HLS download
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
_AGE_LIMITS = {
|
||||||
|
'N-7': 7,
|
||||||
|
'N-14': 14,
|
||||||
|
'S': 18,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
display_id = self._match_id(url)
|
||||||
|
|
||||||
|
webpage = self._download_webpage(
|
||||||
|
url, display_id, 'Downloading player webpage')
|
||||||
|
|
||||||
|
video_id = self._search_regex(
|
||||||
|
r'data-ep="([^"]+)"', webpage, 'video ID')
|
||||||
|
title = self._og_search_title(webpage)
|
||||||
|
description = self._og_search_description(webpage)
|
||||||
|
upload_date = unified_strdate(self._search_regex(
|
||||||
|
r'class="[^"]*meta-item[^"]*air-time[^"]*">.*?<strong>([^<]+)</strong>', webpage, 'upload date', fatal=False))
|
||||||
|
|
||||||
|
thumbnail_w = int_or_none(
|
||||||
|
self._og_search_property('image:width', webpage, 'thumbnail width', fatal=False))
|
||||||
|
thumbnail_h = int_or_none(
|
||||||
|
self._og_search_property('image:height', webpage, 'thumbnail height', fatal=False))
|
||||||
|
thumbnail = {
|
||||||
|
'url': self._og_search_thumbnail(webpage),
|
||||||
|
}
|
||||||
|
if thumbnail_w and thumbnail_h:
|
||||||
|
thumbnail.update({
|
||||||
|
'width': thumbnail_w,
|
||||||
|
'height': thumbnail_h,
|
||||||
|
})
|
||||||
|
|
||||||
|
config = self._parse_json(self._search_regex(
|
||||||
|
r'episodePlayer\((\{.*?\}),\s*\{', webpage, 'sources'), video_id)
|
||||||
|
|
||||||
|
if config.get('pGeo'):
|
||||||
|
self.report_warning(
|
||||||
|
'This content might not be available in your country due to copyright reasons')
|
||||||
|
|
||||||
|
formats = [{
|
||||||
|
'format_id': 'hls',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'url': config['EpisodeVideoLink_HLS'],
|
||||||
|
}]
|
||||||
|
|
||||||
|
m = re.search(r'^(?P<url>rtmp://[^/]+/(?P<app>[^/]+))/(?P<play_path>.+)$', config['EpisodeVideoLink'])
|
||||||
|
if m:
|
||||||
|
formats.append({
|
||||||
|
'format_id': 'rtmp',
|
||||||
|
'ext': 'flv',
|
||||||
|
'url': m.group('url'),
|
||||||
|
'play_path': m.group('play_path'),
|
||||||
|
'page_url': url,
|
||||||
|
})
|
||||||
|
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'display_id': display_id,
|
||||||
|
'title': title,
|
||||||
|
'formats': formats,
|
||||||
|
'thumbnails': [thumbnail],
|
||||||
|
'duration': int_or_none(config.get('VideoTime')),
|
||||||
|
'description': description,
|
||||||
|
'age_limit': self._AGE_LIMITS.get(config.get('PGRating'), 0),
|
||||||
|
'upload_date': upload_date,
|
||||||
|
}
|
@@ -85,6 +85,7 @@ class LyndaIE(SubtitlesInfoExtractor):
|
|||||||
} for format_id, video_url in prioritized_streams['0'].items()
|
} for format_id, video_url in prioritized_streams['0'].items()
|
||||||
])
|
])
|
||||||
|
|
||||||
|
self._check_formats(formats, video_id)
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
if self._downloader.params.get('listsubtitles', False):
|
if self._downloader.params.get('listsubtitles', False):
|
||||||
|
@@ -1,7 +1,5 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import ExtractorError
|
from ..utils import ExtractorError
|
||||||
|
|
||||||
@@ -13,21 +11,22 @@ class MacGameStoreIE(InfoExtractor):
|
|||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.macgamestore.com/mediaviewer.php?trailer=2450',
|
'url': 'http://www.macgamestore.com/mediaviewer.php?trailer=2450',
|
||||||
'file': '2450.m4v',
|
|
||||||
'md5': '8649b8ea684b6666b4c5be736ecddc61',
|
'md5': '8649b8ea684b6666b4c5be736ecddc61',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'id': '2450',
|
||||||
|
'ext': 'm4v',
|
||||||
'title': 'Crow',
|
'title': 'Crow',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = mobj.group('id')
|
webpage = self._download_webpage(
|
||||||
|
url, video_id, 'Downloading trailer page')
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id, 'Downloading trailer page')
|
if '>Missing Media<' in webpage:
|
||||||
|
raise ExtractorError(
|
||||||
if re.search(r'>Missing Media<', webpage) is not None:
|
'Trailer %s does not exist' % video_id, expected=True)
|
||||||
raise ExtractorError('Trailer %s does not exist' % video_id, expected=True)
|
|
||||||
|
|
||||||
video_title = self._html_search_regex(
|
video_title = self._html_search_regex(
|
||||||
r'<title>MacGameStore: (.*?) Trailer</title>', webpage, 'title')
|
r'<title>MacGameStore: (.*?) Trailer</title>', webpage, 'title')
|
||||||
|
@@ -9,7 +9,7 @@ from ..compat import (
|
|||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
HEADRequest,
|
HEADRequest,
|
||||||
int_or_none,
|
str_to_int,
|
||||||
parse_iso8601,
|
parse_iso8601,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -85,15 +85,17 @@ class MixcloudIE(InfoExtractor):
|
|||||||
uploader_id = self._search_regex(
|
uploader_id = self._search_regex(
|
||||||
r'\s+"profile": "([^"]+)",', webpage, 'uploader id', fatal=False)
|
r'\s+"profile": "([^"]+)",', webpage, 'uploader id', fatal=False)
|
||||||
description = self._og_search_description(webpage)
|
description = self._og_search_description(webpage)
|
||||||
like_count = int_or_none(self._search_regex(
|
like_count = str_to_int(self._search_regex(
|
||||||
r'<meta itemprop="interactionCount" content="UserLikes:([0-9]+)"',
|
[r'<meta itemprop="interactionCount" content="UserLikes:([0-9]+)"',
|
||||||
|
r'/favorites/?">([0-9]+)<'],
|
||||||
webpage, 'like count', fatal=False))
|
webpage, 'like count', fatal=False))
|
||||||
view_count = int_or_none(self._search_regex(
|
view_count = str_to_int(self._search_regex(
|
||||||
r'<meta itemprop="interactionCount" content="UserPlays:([0-9]+)"',
|
[r'<meta itemprop="interactionCount" content="UserPlays:([0-9]+)"',
|
||||||
|
r'/listeners/?">([0-9,.]+)</a>'],
|
||||||
webpage, 'play count', fatal=False))
|
webpage, 'play count', fatal=False))
|
||||||
timestamp = parse_iso8601(self._search_regex(
|
timestamp = parse_iso8601(self._search_regex(
|
||||||
r'<time itemprop="dateCreated" datetime="([^"]+)">',
|
r'<time itemprop="dateCreated" datetime="([^"]+)">',
|
||||||
webpage, 'upload date'))
|
webpage, 'upload date', default=None))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': track_id,
|
'id': track_id,
|
||||||
|
@@ -1,21 +1,19 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import json
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import int_or_none
|
from ..utils import int_or_none
|
||||||
|
|
||||||
|
|
||||||
class MporaIE(InfoExtractor):
|
class MporaIE(InfoExtractor):
|
||||||
_VALID_URL = r'^https?://(www\.)?mpora\.(?:com|de)/videos/(?P<id>[^?#/]+)'
|
_VALID_URL = r'https?://(www\.)?mpora\.(?:com|de)/videos/(?P<id>[^?#/]+)'
|
||||||
IE_NAME = 'MPORA'
|
IE_NAME = 'MPORA'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://mpora.de/videos/AAdo8okx4wiz/embed?locale=de',
|
'url': 'http://mpora.de/videos/AAdo8okx4wiz/embed?locale=de',
|
||||||
'file': 'AAdo8okx4wiz.mp4',
|
|
||||||
'md5': 'a7a228473eedd3be741397cf452932eb',
|
'md5': 'a7a228473eedd3be741397cf452932eb',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'id': 'AAdo8okx4wiz',
|
||||||
|
'ext': 'mp4',
|
||||||
'title': 'Katy Curd - Winter in the Forest',
|
'title': 'Katy Curd - Winter in the Forest',
|
||||||
'duration': 416,
|
'duration': 416,
|
||||||
'uploader': 'Peter Newman Media',
|
'uploader': 'Peter Newman Media',
|
||||||
@@ -23,14 +21,12 @@ class MporaIE(InfoExtractor):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
m = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = m.group('id')
|
|
||||||
|
|
||||||
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*(\{.*?)\).player;", webpage, 'json')
|
r"new FM\.Player\('[^']+',\s*(\{.*?)\).player;", webpage, 'json')
|
||||||
|
data = self._parse_json(data_json, video_id)
|
||||||
data = json.loads(data_json)
|
|
||||||
|
|
||||||
uploader = data['info_overlay'].get('username')
|
uploader = data['info_overlay'].get('username')
|
||||||
duration = data['video']['duration'] // 1000
|
duration = data['video']['duration'] // 1000
|
||||||
|
@@ -2,10 +2,11 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .subtitles import SubtitlesInfoExtractor
|
||||||
from ..compat import (
|
from ..compat import (
|
||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
compat_urllib_request,
|
compat_urllib_request,
|
||||||
|
compat_str,
|
||||||
)
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
@@ -22,7 +23,7 @@ def _media_xml_tag(tag):
|
|||||||
return '{http://search.yahoo.com/mrss/}%s' % tag
|
return '{http://search.yahoo.com/mrss/}%s' % tag
|
||||||
|
|
||||||
|
|
||||||
class MTVServicesInfoExtractor(InfoExtractor):
|
class MTVServicesInfoExtractor(SubtitlesInfoExtractor):
|
||||||
_MOBILE_TEMPLATE = None
|
_MOBILE_TEMPLATE = None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -53,7 +54,7 @@ class MTVServicesInfoExtractor(InfoExtractor):
|
|||||||
webpage_url = self._MOBILE_TEMPLATE % mtvn_id
|
webpage_url = self._MOBILE_TEMPLATE % mtvn_id
|
||||||
req = compat_urllib_request.Request(webpage_url)
|
req = compat_urllib_request.Request(webpage_url)
|
||||||
# Otherwise we get a webpage that would execute some javascript
|
# Otherwise we get a webpage that would execute some javascript
|
||||||
req.add_header('Youtubedl-user-agent', 'curl/7')
|
req.add_header('User-Agent', 'curl/7')
|
||||||
webpage = self._download_webpage(req, mtvn_id,
|
webpage = self._download_webpage(req, mtvn_id,
|
||||||
'Downloading mobile page')
|
'Downloading mobile page')
|
||||||
metrics_url = unescapeHTML(self._search_regex(r'<a href="(http://metrics.+?)"', webpage, 'url'))
|
metrics_url = unescapeHTML(self._search_regex(r'<a href="(http://metrics.+?)"', webpage, 'url'))
|
||||||
@@ -78,7 +79,10 @@ class MTVServicesInfoExtractor(InfoExtractor):
|
|||||||
try:
|
try:
|
||||||
_, _, ext = rendition.attrib['type'].partition('/')
|
_, _, ext = rendition.attrib['type'].partition('/')
|
||||||
rtmp_video_url = rendition.find('./src').text
|
rtmp_video_url = rendition.find('./src').text
|
||||||
formats.append({'ext': ext,
|
if rtmp_video_url.endswith('siteunavail.png'):
|
||||||
|
continue
|
||||||
|
formats.append({
|
||||||
|
'ext': ext,
|
||||||
'url': self._transform_rtmp_url(rtmp_video_url),
|
'url': self._transform_rtmp_url(rtmp_video_url),
|
||||||
'format_id': rendition.get('bitrate'),
|
'format_id': rendition.get('bitrate'),
|
||||||
'width': int(rendition.get('width')),
|
'width': int(rendition.get('width')),
|
||||||
@@ -89,6 +93,28 @@ class MTVServicesInfoExtractor(InfoExtractor):
|
|||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
return formats
|
return formats
|
||||||
|
|
||||||
|
def _extract_subtitles(self, mdoc, mtvn_id):
|
||||||
|
subtitles = {}
|
||||||
|
FORMATS = {
|
||||||
|
'scc': 'cea-608',
|
||||||
|
'eia-608': 'cea-608',
|
||||||
|
'xml': 'ttml',
|
||||||
|
}
|
||||||
|
subtitles_format = FORMATS.get(
|
||||||
|
self._downloader.params.get('subtitlesformat'), 'ttml')
|
||||||
|
for transcript in mdoc.findall('.//transcript'):
|
||||||
|
if transcript.get('kind') != 'captions':
|
||||||
|
continue
|
||||||
|
lang = transcript.get('srclang')
|
||||||
|
for typographic in transcript.findall('./typographic'):
|
||||||
|
captions_format = typographic.get('format')
|
||||||
|
if captions_format == subtitles_format:
|
||||||
|
subtitles[lang] = compat_str(typographic.get('src'))
|
||||||
|
break
|
||||||
|
if self._downloader.params.get('listsubtitles', False):
|
||||||
|
self._list_available_subtitles(mtvn_id, subtitles)
|
||||||
|
return self.extract_subtitles(mtvn_id, subtitles)
|
||||||
|
|
||||||
def _get_video_info(self, itemdoc):
|
def _get_video_info(self, itemdoc):
|
||||||
uri = itemdoc.find('guid').text
|
uri = itemdoc.find('guid').text
|
||||||
video_id = self._id_from_uri(uri)
|
video_id = self._id_from_uri(uri)
|
||||||
@@ -135,6 +161,7 @@ class MTVServicesInfoExtractor(InfoExtractor):
|
|||||||
return {
|
return {
|
||||||
'title': title,
|
'title': title,
|
||||||
'formats': self._extract_video_formats(mediagen_doc, mtvn_id),
|
'formats': self._extract_video_formats(mediagen_doc, mtvn_id),
|
||||||
|
'subtitles': self._extract_subtitles(mediagen_doc, mtvn_id),
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'thumbnail': self._get_thumbnail_url(uri, itemdoc),
|
'thumbnail': self._get_thumbnail_url(uri, itemdoc),
|
||||||
'description': description,
|
'description': description,
|
||||||
@@ -167,7 +194,11 @@ class MTVServicesInfoExtractor(InfoExtractor):
|
|||||||
mgid = self._search_regex(
|
mgid = self._search_regex(
|
||||||
[r'data-mgid="(.*?)"', r'swfobject.embedSWF\(".*?(mgid:.*?)"'],
|
[r'data-mgid="(.*?)"', r'swfobject.embedSWF\(".*?(mgid:.*?)"'],
|
||||||
webpage, 'mgid')
|
webpage, 'mgid')
|
||||||
return self._get_videos_info(mgid)
|
|
||||||
|
videos_info = self._get_videos_info(mgid)
|
||||||
|
if self._downloader.params.get('listsubtitles', False):
|
||||||
|
return
|
||||||
|
return videos_info
|
||||||
|
|
||||||
|
|
||||||
class MTVServicesEmbeddedIE(MTVServicesInfoExtractor):
|
class MTVServicesEmbeddedIE(MTVServicesInfoExtractor):
|
||||||
@@ -212,25 +243,14 @@ class MTVIE(MTVServicesInfoExtractor):
|
|||||||
_TESTS = [
|
_TESTS = [
|
||||||
{
|
{
|
||||||
'url': 'http://www.mtv.com/videos/misc/853555/ours-vh1-storytellers.jhtml',
|
'url': 'http://www.mtv.com/videos/misc/853555/ours-vh1-storytellers.jhtml',
|
||||||
'file': '853555.mp4',
|
|
||||||
'md5': '850f3f143316b1e71fa56a4edfd6e0f8',
|
'md5': '850f3f143316b1e71fa56a4edfd6e0f8',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'id': '853555',
|
||||||
|
'ext': 'mp4',
|
||||||
'title': 'Taylor Swift - "Ours (VH1 Storytellers)"',
|
'title': 'Taylor Swift - "Ours (VH1 Storytellers)"',
|
||||||
'description': 'Album: Taylor Swift performs "Ours" for VH1 Storytellers at Harvey Mudd College.',
|
'description': 'Album: Taylor Swift performs "Ours" for VH1 Storytellers at Harvey Mudd College.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
'add_ie': ['Vevo'],
|
|
||||||
'url': 'http://www.mtv.com/videos/taylor-swift/916187/everything-has-changed-ft-ed-sheeran.jhtml',
|
|
||||||
'file': 'USCJY1331283.mp4',
|
|
||||||
'md5': '73b4e7fcadd88929292fe52c3ced8caf',
|
|
||||||
'info_dict': {
|
|
||||||
'title': 'Everything Has Changed',
|
|
||||||
'upload_date': '20130606',
|
|
||||||
'uploader': 'Taylor Swift',
|
|
||||||
},
|
|
||||||
'skip': 'VEVO is only available in some countries',
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
def _get_thumbnail_url(self, uri, itemdoc):
|
def _get_thumbnail_url(self, uri, itemdoc):
|
||||||
@@ -244,8 +264,8 @@ class MTVIE(MTVServicesInfoExtractor):
|
|||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
# Some videos come from Vevo.com
|
# Some videos come from Vevo.com
|
||||||
m_vevo = re.search(r'isVevoVideo = true;.*?vevoVideoId = "(.*?)";',
|
m_vevo = re.search(
|
||||||
webpage, re.DOTALL)
|
r'(?s)isVevoVideo = true;.*?vevoVideoId = "(.*?)";', webpage)
|
||||||
if m_vevo:
|
if m_vevo:
|
||||||
vevo_id = m_vevo.group(1)
|
vevo_id = m_vevo.group(1)
|
||||||
self.to_screen('Vevo video detected: %s' % vevo_id)
|
self.to_screen('Vevo video detected: %s' % vevo_id)
|
||||||
|
@@ -6,6 +6,7 @@ import json
|
|||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import (
|
||||||
compat_str,
|
compat_str,
|
||||||
|
compat_HTTPError,
|
||||||
)
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
@@ -78,6 +79,16 @@ class NBCNewsIE(InfoExtractor):
|
|||||||
},
|
},
|
||||||
'add_ie': ['ThePlatform'],
|
'add_ie': ['ThePlatform'],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://www.nbcnews.com/feature/dateline-full-episodes/full-episode-family-business-n285156',
|
||||||
|
'md5': 'fdbf39ab73a72df5896b6234ff98518a',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'Wjf9EDR3A_60',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'FULL EPISODE: Family Business',
|
||||||
|
'description': 'md5:757988edbaae9d7be1d585eb5d55cc04',
|
||||||
|
},
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
@@ -115,10 +126,19 @@ class NBCNewsIE(InfoExtractor):
|
|||||||
if not base_url:
|
if not base_url:
|
||||||
continue
|
continue
|
||||||
playlist_url = base_url + '?form=MPXNBCNewsAPI'
|
playlist_url = base_url + '?form=MPXNBCNewsAPI'
|
||||||
all_videos = self._download_json(playlist_url, title)['videos']
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
info = next(v for v in all_videos if v['mpxId'] == mpxid)
|
all_videos = self._download_json(playlist_url, title)
|
||||||
|
except ExtractorError as ee:
|
||||||
|
if isinstance(ee.cause, compat_HTTPError):
|
||||||
|
continue
|
||||||
|
raise
|
||||||
|
|
||||||
|
if not all_videos or 'videos' not in all_videos:
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
info = next(v for v in all_videos['videos'] if v['mpxId'] == mpxid)
|
||||||
break
|
break
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
continue
|
continue
|
||||||
|
@@ -27,9 +27,7 @@ class NDTVIE(InfoExtractor):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = mobj.group('id')
|
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
filename = self._search_regex(
|
filename = self._search_regex(
|
||||||
|
@@ -11,6 +11,7 @@ class NerdCubedFeedIE(InfoExtractor):
|
|||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.nerdcubed.co.uk/feed.json',
|
'url': 'http://www.nerdcubed.co.uk/feed.json',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'id': 'nerdcubed-feed',
|
||||||
'title': 'nerdcubed.co.uk feed',
|
'title': 'nerdcubed.co.uk feed',
|
||||||
},
|
},
|
||||||
'playlist_mincount': 1300,
|
'playlist_mincount': 1300,
|
||||||
|
163
youtube_dl/extractor/nextmedia.py
Normal file
163
youtube_dl/extractor/nextmedia.py
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import parse_iso8601
|
||||||
|
|
||||||
|
|
||||||
|
class NextMediaIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'http://hk.apple.nextmedia.com/[^/]+/[^/]+/(?P<date>\d+)/(?P<id>\d+)'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://hk.apple.nextmedia.com/realtime/news/20141108/53109199',
|
||||||
|
'md5': 'dff9fad7009311c421176d1ac90bfe4f',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '53109199',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': '【佔領金鐘】50外國領事議員撐場 讚學生勇敢香港有希望',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
'description': 'md5:28222b9912b6665a21011b034c70fcc7',
|
||||||
|
'timestamp': 1415456273,
|
||||||
|
'upload_date': '20141108',
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
|
||||||
|
_URL_PATTERN = r'\{ url: \'(.+)\' \}'
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
news_id = self._match_id(url)
|
||||||
|
page = self._download_webpage(url, news_id)
|
||||||
|
return self._extract_from_nextmedia_page(news_id, url, page)
|
||||||
|
|
||||||
|
def _extract_from_nextmedia_page(self, news_id, url, page):
|
||||||
|
title = self._fetch_title(page)
|
||||||
|
video_url = self._search_regex(self._URL_PATTERN, page, 'video url')
|
||||||
|
|
||||||
|
attrs = {
|
||||||
|
'id': news_id,
|
||||||
|
'title': title,
|
||||||
|
'url': video_url, # ext can be inferred from url
|
||||||
|
'thumbnail': self._fetch_thumbnail(page),
|
||||||
|
'description': self._fetch_description(page),
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamp = self._fetch_timestamp(page)
|
||||||
|
if timestamp:
|
||||||
|
attrs['timestamp'] = timestamp
|
||||||
|
else:
|
||||||
|
attrs['upload_date'] = self._fetch_upload_date(url)
|
||||||
|
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
def _fetch_title(self, page):
|
||||||
|
return self._og_search_title(page)
|
||||||
|
|
||||||
|
def _fetch_thumbnail(self, page):
|
||||||
|
return self._og_search_thumbnail(page)
|
||||||
|
|
||||||
|
def _fetch_timestamp(self, page):
|
||||||
|
dateCreated = self._search_regex('"dateCreated":"([^"]+)"', page, 'created time')
|
||||||
|
return parse_iso8601(dateCreated)
|
||||||
|
|
||||||
|
def _fetch_upload_date(self, url):
|
||||||
|
return self._search_regex(self._VALID_URL, url, 'upload date', group='date')
|
||||||
|
|
||||||
|
def _fetch_description(self, page):
|
||||||
|
return self._og_search_property('description', page)
|
||||||
|
|
||||||
|
|
||||||
|
class NextMediaActionNewsIE(NextMediaIE):
|
||||||
|
_VALID_URL = r'http://hk.dv.nextmedia.com/actionnews/[^/]+/(?P<date>\d+)/(?P<id>\d+)/\d+'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://hk.dv.nextmedia.com/actionnews/hit/20150121/19009428/20061460',
|
||||||
|
'md5': '05fce8ffeed7a5e00665d4b7cf0f9201',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '19009428',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': '【壹週刊】細10年男友偷食 50歲邵美琪再失戀',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
'description': 'md5:cd802fad1f40fd9ea178c1e2af02d659',
|
||||||
|
'timestamp': 1421791200,
|
||||||
|
'upload_date': '20150120',
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
news_id = self._match_id(url)
|
||||||
|
actionnews_page = self._download_webpage(url, news_id)
|
||||||
|
article_url = self._og_search_url(actionnews_page)
|
||||||
|
article_page = self._download_webpage(article_url, news_id)
|
||||||
|
return self._extract_from_nextmedia_page(news_id, url, article_page)
|
||||||
|
|
||||||
|
|
||||||
|
class AppleDailyRealtimeNewsIE(NextMediaIE):
|
||||||
|
_VALID_URL = r'http://(www|ent).appledaily.com.tw/(realtimenews|enews)/[^/]+/[^/]+/(?P<date>\d+)/(?P<id>\d+)(/.*)?'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://ent.appledaily.com.tw/enews/article/entertainment/20150128/36354694',
|
||||||
|
'md5': 'a843ab23d150977cc55ef94f1e2c1e4d',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '36354694',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': '周亭羽走過摩鐵陰霾2男陪吃 九把刀孤寒看醫生',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
'description': 'md5:b23787119933404ce515c6356a8c355c',
|
||||||
|
'upload_date': '20150128',
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.appledaily.com.tw/realtimenews/article/strange/20150128/550549/%E4%B8%8D%E6%BB%BF%E8%A2%AB%E8%B8%A9%E8%85%B3%E3%80%80%E5%B1%B1%E6%9D%B1%E5%85%A9%E5%A4%A7%E5%AA%BD%E4%B8%80%E8%B7%AF%E6%89%93%E4%B8%8B%E8%BB%8A',
|
||||||
|
'md5': '86b4e9132d158279c7883822d94ccc49',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '550549',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': '不滿被踩腳 山東兩大媽一路打下車',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
'description': 'md5:2648aaf6fc4f401f6de35a91d111aa1d',
|
||||||
|
'upload_date': '20150128',
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
|
||||||
|
_URL_PATTERN = r'\{url: \'(.+)\'\}'
|
||||||
|
|
||||||
|
def _fetch_title(self, page):
|
||||||
|
return self._html_search_regex(r'<h1 id="h1">([^<>]+)</h1>', page, 'news title')
|
||||||
|
|
||||||
|
def _fetch_thumbnail(self, page):
|
||||||
|
return self._html_search_regex(r"setInitialImage\(\'([^']+)'\)", page, 'video thumbnail', fatal=False)
|
||||||
|
|
||||||
|
def _fetch_timestamp(self, page):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class AppleDailyAnimationNewsIE(AppleDailyRealtimeNewsIE):
|
||||||
|
_VALID_URL = 'http://www.appledaily.com.tw/animation/[^/]+/[^/]+/(?P<date>\d+)/(?P<id>\d+)(/.*)?'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://www.appledaily.com.tw/animation/realtimenews/new/20150128/5003671',
|
||||||
|
'md5': '03df296d95dedc2d5886debbb80cb43f',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '5003671',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': '20正妹熱舞 《刀龍傳說Online》火辣上市',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
'description': 'md5:23c0aac567dc08c9c16a3161a2c2e3cd',
|
||||||
|
'upload_date': '20150128',
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
# No thumbnail
|
||||||
|
'url': 'http://www.appledaily.com.tw/animation/realtimenews/new/20150128/5003673/',
|
||||||
|
'md5': 'b06182cd386ea7bc6115ec7ff0f72aeb',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '5003673',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': '半夜尿尿 好像會看到___',
|
||||||
|
'description': 'md5:61d2da7fe117fede148706cdb85ac066',
|
||||||
|
'upload_date': '20150128',
|
||||||
|
},
|
||||||
|
'expected_warnings': [
|
||||||
|
'video thumbnail',
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _fetch_title(self, page):
|
||||||
|
return self._html_search_meta('description', page, 'news title')
|
||||||
|
|
||||||
|
def _fetch_description(self, page):
|
||||||
|
return self._html_search_meta('description', page, 'news description')
|
@@ -20,6 +20,12 @@ class NHLBaseInfoExtractor(InfoExtractor):
|
|||||||
def _fix_json(json_string):
|
def _fix_json(json_string):
|
||||||
return json_string.replace('\\\'', '\'')
|
return json_string.replace('\\\'', '\'')
|
||||||
|
|
||||||
|
def _real_extract_video(self, video_id):
|
||||||
|
json_url = 'http://video.nhl.com/videocenter/servlets/playlist?ids=%s&format=json' % video_id
|
||||||
|
data = self._download_json(
|
||||||
|
json_url, video_id, transform_source=self._fix_json)
|
||||||
|
return self._extract_video(data[0])
|
||||||
|
|
||||||
def _extract_video(self, info):
|
def _extract_video(self, info):
|
||||||
video_id = info['id']
|
video_id = info['id']
|
||||||
self.report_extraction(video_id)
|
self.report_extraction(video_id)
|
||||||
@@ -54,7 +60,7 @@ class NHLBaseInfoExtractor(InfoExtractor):
|
|||||||
|
|
||||||
class NHLIE(NHLBaseInfoExtractor):
|
class NHLIE(NHLBaseInfoExtractor):
|
||||||
IE_NAME = 'nhl.com'
|
IE_NAME = 'nhl.com'
|
||||||
_VALID_URL = r'https?://video(?P<team>\.[^.]*)?\.nhl\.com/videocenter/console(?:\?(?:.*?[?&])?)id=(?P<id>[-0-9a-zA-Z]+)'
|
_VALID_URL = r'https?://video(?P<team>\.[^.]*)?\.nhl\.com/videocenter/(?:console)?(?:\?(?:.*?[?&])?)id=(?P<id>[-0-9a-zA-Z]+)'
|
||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://video.canucks.nhl.com/videocenter/console?catid=6?id=453614',
|
'url': 'http://video.canucks.nhl.com/videocenter/console?catid=6?id=453614',
|
||||||
@@ -92,15 +98,41 @@ class NHLIE(NHLBaseInfoExtractor):
|
|||||||
}, {
|
}, {
|
||||||
'url': 'http://video.flames.nhl.com/videocenter/console?id=630616',
|
'url': 'http://video.flames.nhl.com/videocenter/console?id=630616',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://video.nhl.com/videocenter/?id=736722',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = mobj.group('id')
|
return self._real_extract_video(video_id)
|
||||||
json_url = 'http://video.nhl.com/videocenter/servlets/playlist?ids=%s&format=json' % video_id
|
|
||||||
data = self._download_json(
|
|
||||||
json_url, video_id, transform_source=self._fix_json)
|
class NHLNewsIE(NHLBaseInfoExtractor):
|
||||||
return self._extract_video(data[0])
|
IE_NAME = 'nhl.com:news'
|
||||||
|
IE_DESC = 'NHL news'
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?nhl\.com/ice/news\.html?(?:\?(?:.*?[?&])?)id=(?P<id>[-0-9a-zA-Z]+)'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.nhl.com/ice/news.htm?id=750727',
|
||||||
|
'md5': '4b3d1262e177687a3009937bd9ec0be8',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '736722',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Cal Clutterbuck has been fined $2,000',
|
||||||
|
'description': 'md5:45fe547d30edab88b23e0dd0ab1ed9e6',
|
||||||
|
'duration': 37,
|
||||||
|
'upload_date': '20150128',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
news_id = self._match_id(url)
|
||||||
|
webpage = self._download_webpage(url, news_id)
|
||||||
|
video_id = self._search_regex(
|
||||||
|
[r'pVid(\d+)', r"nlid\s*:\s*'(\d+)'"],
|
||||||
|
webpage, 'video id')
|
||||||
|
return self._real_extract_video(video_id)
|
||||||
|
|
||||||
|
|
||||||
class NHLVideocenterIE(NHLBaseInfoExtractor):
|
class NHLVideocenterIE(NHLBaseInfoExtractor):
|
||||||
|
@@ -1,19 +1,26 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
unified_strdate,
|
fix_xml_ampersands,
|
||||||
parse_duration,
|
parse_duration,
|
||||||
qualities,
|
qualities,
|
||||||
strip_jsonp,
|
strip_jsonp,
|
||||||
|
unified_strdate,
|
||||||
url_basename,
|
url_basename,
|
||||||
fix_xml_ampersands,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class NPOIE(InfoExtractor):
|
class NPOBaseIE(InfoExtractor):
|
||||||
|
def _get_token(self, video_id):
|
||||||
|
token_page = self._download_webpage(
|
||||||
|
'http://ida.omroep.nl/npoplayer/i.js',
|
||||||
|
video_id, note='Downloading token')
|
||||||
|
return self._search_regex(
|
||||||
|
r'npoplayer\.token = "(.+?)"', token_page, 'token')
|
||||||
|
|
||||||
|
|
||||||
|
class NPOIE(NPOBaseIE):
|
||||||
IE_NAME = 'npo.nl'
|
IE_NAME = 'npo.nl'
|
||||||
_VALID_URL = r'https?://www\.npo\.nl/[^/]+/[^/]+/(?P<id>[^/?]+)'
|
_VALID_URL = r'https?://www\.npo\.nl/[^/]+/[^/]+/(?P<id>[^/?]+)'
|
||||||
|
|
||||||
@@ -67,11 +74,20 @@ class NPOIE(InfoExtractor):
|
|||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
# non asf in streams
|
||||||
|
{
|
||||||
|
'url': 'http://www.npo.nl/hoe-gaat-europa-verder-na-parijs/10-01-2015/WO_NOS_762771',
|
||||||
|
'md5': 'b3da13de374cbe2d5332a7e910bef97f',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'WO_NOS_762771',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Hoe gaat Europa verder na Parijs?',
|
||||||
|
},
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = mobj.group('id')
|
|
||||||
return self._get_info(video_id)
|
return self._get_info(video_id)
|
||||||
|
|
||||||
def _get_info(self, video_id):
|
def _get_info(self, video_id):
|
||||||
@@ -81,12 +97,8 @@ class NPOIE(InfoExtractor):
|
|||||||
# We have to remove the javascript callback
|
# We have to remove the javascript callback
|
||||||
transform_source=strip_jsonp,
|
transform_source=strip_jsonp,
|
||||||
)
|
)
|
||||||
token_page = self._download_webpage(
|
|
||||||
'http://ida.omroep.nl/npoplayer/i.js',
|
token = self._get_token(video_id)
|
||||||
video_id,
|
|
||||||
note='Downloading token'
|
|
||||||
)
|
|
||||||
token = self._search_regex(r'npoplayer\.token = "(.+?)"', token_page, 'token')
|
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
|
|
||||||
@@ -125,6 +137,12 @@ class NPOIE(InfoExtractor):
|
|||||||
stream_url = stream.get('url')
|
stream_url = stream.get('url')
|
||||||
if not stream_url:
|
if not stream_url:
|
||||||
continue
|
continue
|
||||||
|
if '.asf' not in stream_url:
|
||||||
|
formats.append({
|
||||||
|
'url': stream_url,
|
||||||
|
'quality': stream.get('kwaliteit'),
|
||||||
|
})
|
||||||
|
continue
|
||||||
asx = self._download_xml(
|
asx = self._download_xml(
|
||||||
stream_url, video_id,
|
stream_url, video_id,
|
||||||
'Downloading stream %d ASX playlist' % i,
|
'Downloading stream %d ASX playlist' % i,
|
||||||
@@ -154,6 +172,83 @@ class NPOIE(InfoExtractor):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class NPOLiveIE(NPOBaseIE):
|
||||||
|
IE_NAME = 'npo.nl:live'
|
||||||
|
_VALID_URL = r'https?://www\.npo\.nl/live/(?P<id>.+)'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.npo.nl/live/npo-1',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'LI_NEDERLAND1_136692',
|
||||||
|
'display_id': 'npo-1',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 're:^Nederland 1 [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$',
|
||||||
|
'description': 'Livestream',
|
||||||
|
'is_live': True,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
display_id = self._match_id(url)
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
|
||||||
|
live_id = self._search_regex(
|
||||||
|
r'data-prid="([^"]+)"', webpage, 'live id')
|
||||||
|
|
||||||
|
metadata = self._download_json(
|
||||||
|
'http://e.omroep.nl/metadata/%s' % live_id,
|
||||||
|
display_id, transform_source=strip_jsonp)
|
||||||
|
|
||||||
|
token = self._get_token(display_id)
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
|
||||||
|
streams = metadata.get('streams')
|
||||||
|
if streams:
|
||||||
|
for stream in streams:
|
||||||
|
stream_type = stream.get('type').lower()
|
||||||
|
if stream_type == 'ss':
|
||||||
|
continue
|
||||||
|
stream_info = self._download_json(
|
||||||
|
'http://ida.omroep.nl/aapi/?stream=%s&token=%s&type=jsonp'
|
||||||
|
% (stream.get('url'), token),
|
||||||
|
display_id, 'Downloading %s JSON' % stream_type)
|
||||||
|
if stream_info.get('error_code', 0) or stream_info.get('errorcode', 0):
|
||||||
|
continue
|
||||||
|
stream_url = self._download_json(
|
||||||
|
stream_info['stream'], display_id,
|
||||||
|
'Downloading %s URL' % stream_type,
|
||||||
|
transform_source=strip_jsonp)
|
||||||
|
if stream_type == 'hds':
|
||||||
|
f4m_formats = self._extract_f4m_formats(stream_url, display_id)
|
||||||
|
# f4m downloader downloads only piece of live stream
|
||||||
|
for f4m_format in f4m_formats:
|
||||||
|
f4m_format['preference'] = -1
|
||||||
|
formats.extend(f4m_formats)
|
||||||
|
elif stream_type == 'hls':
|
||||||
|
formats.extend(self._extract_m3u8_formats(stream_url, display_id, 'mp4'))
|
||||||
|
else:
|
||||||
|
formats.append({
|
||||||
|
'url': stream_url,
|
||||||
|
})
|
||||||
|
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': live_id,
|
||||||
|
'display_id': display_id,
|
||||||
|
'title': self._live_title(metadata['titel']),
|
||||||
|
'description': metadata['info'],
|
||||||
|
'thumbnail': metadata.get('images', [{'url': None}])[-1]['url'],
|
||||||
|
'formats': formats,
|
||||||
|
'is_live': True,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class TegenlichtVproIE(NPOIE):
|
class TegenlichtVproIE(NPOIE):
|
||||||
IE_NAME = 'tegenlicht.vpro.nl'
|
IE_NAME = 'tegenlicht.vpro.nl'
|
||||||
_VALID_URL = r'https?://tegenlicht\.vpro\.nl/afleveringen/.*?'
|
_VALID_URL = r'https?://tegenlicht\.vpro\.nl/afleveringen/.*?'
|
||||||
|
@@ -10,6 +10,7 @@ from ..compat import (
|
|||||||
compat_urllib_request,
|
compat_urllib_request,
|
||||||
)
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
|
ExtractorError,
|
||||||
str_to_int,
|
str_to_int,
|
||||||
)
|
)
|
||||||
from ..aes import (
|
from ..aes import (
|
||||||
@@ -44,6 +45,15 @@ class PornHubIE(InfoExtractor):
|
|||||||
req.add_header('Cookie', 'age_verified=1')
|
req.add_header('Cookie', 'age_verified=1')
|
||||||
webpage = self._download_webpage(req, video_id)
|
webpage = self._download_webpage(req, video_id)
|
||||||
|
|
||||||
|
error_msg = self._html_search_regex(
|
||||||
|
r'(?s)<div class="userMessageSection[^"]*".*?>(.*?)</div>',
|
||||||
|
webpage, 'error message', default=None)
|
||||||
|
if error_msg:
|
||||||
|
error_msg = re.sub(r'\s+', ' ', error_msg)
|
||||||
|
raise ExtractorError(
|
||||||
|
'PornHub said: %s' % error_msg,
|
||||||
|
expected=True, video_id=video_id)
|
||||||
|
|
||||||
video_title = self._html_search_regex(r'<h1 [^>]+>([^<]+)', webpage, 'title')
|
video_title = self._html_search_regex(r'<h1 [^>]+>([^<]+)', webpage, 'title')
|
||||||
video_uploader = self._html_search_regex(
|
video_uploader = self._html_search_regex(
|
||||||
r'(?s)From: .+?<(?:a href="/users/|a href="/channels/|<span class="username)[^>]+>(.+?)<',
|
r'(?s)From: .+?<(?:a href="/users/|a href="/channels/|<span class="username)[^>]+>(.+?)<',
|
||||||
|
@@ -6,12 +6,13 @@ from .common import InfoExtractor
|
|||||||
|
|
||||||
|
|
||||||
class RingTVIE(InfoExtractor):
|
class RingTVIE(InfoExtractor):
|
||||||
_VALID_URL = r'(?:http://)?(?:www\.)?ringtv\.craveonline\.com/(?P<type>news|videos/video)/(?P<id>[^/?#]+)'
|
_VALID_URL = r'http://(?:www\.)?ringtv\.craveonline\.com/(?P<type>news|videos/video)/(?P<id>[^/?#]+)'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
"url": "http://ringtv.craveonline.com/news/310833-luis-collazo-says-victor-ortiz-better-not-quit-on-jan-30",
|
"url": "http://ringtv.craveonline.com/news/310833-luis-collazo-says-victor-ortiz-better-not-quit-on-jan-30",
|
||||||
"file": "857645.mp4",
|
|
||||||
"md5": "d25945f5df41cdca2d2587165ac28720",
|
"md5": "d25945f5df41cdca2d2587165ac28720",
|
||||||
"info_dict": {
|
"info_dict": {
|
||||||
|
'id': '857645',
|
||||||
|
'ext': 'mp4',
|
||||||
"title": 'Video: Luis Collazo says Victor Ortiz "better not quit on Jan. 30" - Ring TV',
|
"title": 'Video: Luis Collazo says Victor Ortiz "better not quit on Jan. 30" - Ring TV',
|
||||||
"description": 'Luis Collazo is excited about his Jan. 30 showdown with fellow former welterweight titleholder Victor Ortiz at Barclays Center in his hometown of Brooklyn. The SuperBowl week fight headlines a Golden Boy Live! card on Fox Sports 1.',
|
"description": 'Luis Collazo is excited about his Jan. 30 showdown with fellow former welterweight titleholder Victor Ortiz at Barclays Center in his hometown of Brooklyn. The SuperBowl week fight headlines a Golden Boy Live! card on Fox Sports 1.',
|
||||||
}
|
}
|
||||||
|
@@ -10,8 +10,9 @@ class RottenTomatoesIE(VideoDetectiveIE):
|
|||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.rottentomatoes.com/m/toy_story_3/trailers/11028566/',
|
'url': 'http://www.rottentomatoes.com/m/toy_story_3/trailers/11028566/',
|
||||||
'file': '613340.mp4',
|
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'id': '613340',
|
||||||
|
'ext': 'mp4',
|
||||||
'title': 'TOY STORY 3',
|
'title': 'TOY STORY 3',
|
||||||
'description': 'From the creators of the beloved TOY STORY films, comes a story that will reunite the gang in a whole new way.',
|
'description': 'From the creators of the beloved TOY STORY films, comes a story that will reunite the gang in a whole new way.',
|
||||||
},
|
},
|
||||||
|
72
youtube_dl/extractor/rtl2.py
Normal file
72
youtube_dl/extractor/rtl2.py
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
# encoding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
|
class RTL2IE(InfoExtractor):
|
||||||
|
_VALID_URL = r'http?://(?:www\.)?rtl2\.de/[^?#]*?/(?P<id>[^?#/]*?)(?:$|/(?:$|[?#]))'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://www.rtl2.de/sendung/grip-das-motormagazin/folge/folge-203-0',
|
||||||
|
'md5': 'bfcc179030535b08dc2b36b469b5adc7',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'folge-203-0',
|
||||||
|
'ext': 'f4v',
|
||||||
|
'title': 'GRIP sucht den Sommerkönig',
|
||||||
|
'description': 'Matthias, Det und Helge treten gegeneinander an.'
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.rtl2.de/sendung/koeln-50667/video/5512-anna/21040-anna-erwischt-alex/',
|
||||||
|
'md5': 'ffcd517d2805b57ce11a58a2980c2b02',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '21040-anna-erwischt-alex',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Anna erwischt Alex!',
|
||||||
|
'description': 'Anna ist Alex\' Tochter bei Köln 50667.'
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
# Some rtl2 urls have no slash at the end, so append it.
|
||||||
|
if not url.endswith('/'):
|
||||||
|
url += '/'
|
||||||
|
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
vico_id = self._html_search_regex(
|
||||||
|
r'vico_id\s*:\s*([0-9]+)', webpage, 'vico_id')
|
||||||
|
vivi_id = self._html_search_regex(
|
||||||
|
r'vivi_id\s*:\s*([0-9]+)', webpage, 'vivi_id')
|
||||||
|
info_url = 'http://www.rtl2.de/video/php/get_video.php?vico_id=' + vico_id + '&vivi_id=' + vivi_id
|
||||||
|
webpage = self._download_webpage(info_url, '')
|
||||||
|
|
||||||
|
info = self._download_json(info_url, video_id)
|
||||||
|
video_info = info['video']
|
||||||
|
title = video_info['titel']
|
||||||
|
description = video_info.get('beschreibung')
|
||||||
|
thumbnail = video_info.get('image')
|
||||||
|
|
||||||
|
download_url = video_info['streamurl']
|
||||||
|
download_url = download_url.replace('\\', '')
|
||||||
|
stream_url = 'mp4:' + self._html_search_regex(r'ondemand/(.*)', download_url, 'stream URL')
|
||||||
|
rtmp_conn = ["S:connect", "O:1", "NS:pageUrl:" + url, "NB:fpad:0", "NN:videoFunction:1", "O:0"]
|
||||||
|
|
||||||
|
formats = [{
|
||||||
|
'url': download_url,
|
||||||
|
'play_path': stream_url,
|
||||||
|
'player_url': 'http://www.rtl2.de/flashplayer/vipo_player.swf',
|
||||||
|
'page_url': url,
|
||||||
|
'flash_version': 'LNX 11,2,202,429',
|
||||||
|
'rtmp_conn': rtmp_conn,
|
||||||
|
'no_resume': True,
|
||||||
|
}]
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'description': description,
|
||||||
|
'formats': formats,
|
||||||
|
}
|
@@ -57,7 +57,7 @@ def _decrypt_url(png):
|
|||||||
class RTVEALaCartaIE(InfoExtractor):
|
class RTVEALaCartaIE(InfoExtractor):
|
||||||
IE_NAME = 'rtve.es:alacarta'
|
IE_NAME = 'rtve.es:alacarta'
|
||||||
IE_DESC = 'RTVE a la carta'
|
IE_DESC = 'RTVE a la carta'
|
||||||
_VALID_URL = r'http://www\.rtve\.es/alacarta/videos/[^/]+/[^/]+/(?P<id>\d+)'
|
_VALID_URL = r'http://www\.rtve\.es/(m/)?alacarta/videos/[^/]+/[^/]+/(?P<id>\d+)'
|
||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.rtve.es/alacarta/videos/balonmano/o-swiss-cup-masculina-final-espana-suecia/2491869/',
|
'url': 'http://www.rtve.es/alacarta/videos/balonmano/o-swiss-cup-masculina-final-espana-suecia/2491869/',
|
||||||
@@ -74,7 +74,11 @@ class RTVEALaCartaIE(InfoExtractor):
|
|||||||
'id': '1694255',
|
'id': '1694255',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'title': 'TODO',
|
'title': 'TODO',
|
||||||
}
|
},
|
||||||
|
'skip': 'The f4m manifest can\'t be used yet',
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.rtve.es/m/alacarta/videos/cuentame-como-paso/cuentame-como-paso-t16-ultimo-minuto-nuestra-vida-capitulo-276/2969138/?media=tve',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
@@ -86,6 +90,18 @@ class RTVEALaCartaIE(InfoExtractor):
|
|||||||
png_url = 'http://www.rtve.es/ztnr/movil/thumbnail/default/videos/%s.png' % video_id
|
png_url = 'http://www.rtve.es/ztnr/movil/thumbnail/default/videos/%s.png' % video_id
|
||||||
png = self._download_webpage(png_url, video_id, 'Downloading url information')
|
png = self._download_webpage(png_url, video_id, 'Downloading url information')
|
||||||
video_url = _decrypt_url(png)
|
video_url = _decrypt_url(png)
|
||||||
|
if not video_url.endswith('.f4m'):
|
||||||
|
auth_url = video_url.replace(
|
||||||
|
'resources/', 'auth/resources/'
|
||||||
|
).replace('.net.rtve', '.multimedia.cdn.rtve')
|
||||||
|
video_path = self._download_webpage(
|
||||||
|
auth_url, video_id, 'Getting video url')
|
||||||
|
# Use mvod.akcdn instead of flash.akamaihd.multimedia.cdn to get
|
||||||
|
# the right Content-Length header and the mp4 format
|
||||||
|
video_url = (
|
||||||
|
'http://mvod.akcdn.rtve.es/{0}&v=2.6.8'
|
||||||
|
'&fp=MAC%2016,0,0,296&r=MRUGG&g=OEOJWFXNFGCP'.format(video_path)
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
|
@@ -162,10 +162,8 @@ class RUTVIE(InfoExtractor):
|
|||||||
'vbr': int(quality),
|
'vbr': int(quality),
|
||||||
}
|
}
|
||||||
elif transport == 'm3u8':
|
elif transport == 'm3u8':
|
||||||
fmt = {
|
formats.extend(self._extract_m3u8_formats(url, video_id, 'mp4'))
|
||||||
'url': url,
|
continue
|
||||||
'ext': 'mp4',
|
|
||||||
}
|
|
||||||
else:
|
else:
|
||||||
fmt = {
|
fmt = {
|
||||||
'url': url
|
'url': url
|
||||||
|
@@ -1,7 +1,5 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
int_or_none,
|
int_or_none,
|
||||||
@@ -13,10 +11,15 @@ class ServingSysIE(InfoExtractor):
|
|||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://bs.serving-sys.com/BurstingPipe/adServer.bs?cn=is&c=23&pl=VAST&pli=5349193&PluID=0&pos=7135&ord=[timestamp]&cim=1?',
|
'url': 'http://bs.serving-sys.com/BurstingPipe/adServer.bs?cn=is&c=23&pl=VAST&pli=5349193&PluID=0&pos=7135&ord=[timestamp]&cim=1?',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '5349193',
|
||||||
|
'title': 'AdAPPter_Hyundai_demo',
|
||||||
|
},
|
||||||
'playlist': [{
|
'playlist': [{
|
||||||
'file': '29955898.flv',
|
|
||||||
'md5': 'baed851342df6846eb8677a60a011a0f',
|
'md5': 'baed851342df6846eb8677a60a011a0f',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'id': '29955898',
|
||||||
|
'ext': 'flv',
|
||||||
'title': 'AdAPPter_Hyundai_demo (1)',
|
'title': 'AdAPPter_Hyundai_demo (1)',
|
||||||
'duration': 74,
|
'duration': 74,
|
||||||
'tbr': 1378,
|
'tbr': 1378,
|
||||||
@@ -24,9 +27,10 @@ class ServingSysIE(InfoExtractor):
|
|||||||
'height': 400,
|
'height': 400,
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
'file': '29907998.flv',
|
|
||||||
'md5': '979b4da2655c4bc2d81aeb915a8c5014',
|
'md5': '979b4da2655c4bc2d81aeb915a8c5014',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'id': '29907998',
|
||||||
|
'ext': 'flv',
|
||||||
'title': 'AdAPPter_Hyundai_demo (2)',
|
'title': 'AdAPPter_Hyundai_demo (2)',
|
||||||
'duration': 34,
|
'duration': 34,
|
||||||
'width': 854,
|
'width': 854,
|
||||||
@@ -37,14 +41,13 @@ class ServingSysIE(InfoExtractor):
|
|||||||
'params': {
|
'params': {
|
||||||
'playlistend': 2,
|
'playlistend': 2,
|
||||||
},
|
},
|
||||||
'skip': 'Blocked in the US [sic]',
|
'_skip': 'Blocked in the US [sic]',
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
pl_id = self._match_id(url)
|
||||||
pl_id = mobj.group('id')
|
|
||||||
|
|
||||||
vast_doc = self._download_xml(url, pl_id)
|
vast_doc = self._download_xml(url, pl_id)
|
||||||
|
|
||||||
title = vast_doc.find('.//AdTitle').text
|
title = vast_doc.find('.//AdTitle').text
|
||||||
media = vast_doc.find('.//MediaFile').text
|
media = vast_doc.find('.//MediaFile').text
|
||||||
info_url = self._search_regex(r'&adData=([^&]+)&', media, 'info URL')
|
info_url = self._search_regex(r'&adData=([^&]+)&', media, 'info URL')
|
||||||
|
@@ -11,7 +11,7 @@ from ..compat import (
|
|||||||
|
|
||||||
|
|
||||||
class SinaIE(InfoExtractor):
|
class SinaIE(InfoExtractor):
|
||||||
_VALID_URL = r'''https?://(.*?\.)?video\.sina\.com\.cn/
|
_VALID_URL = r'''(?x)https?://(.*?\.)?video\.sina\.com\.cn/
|
||||||
(
|
(
|
||||||
(.+?/(((?P<pseudo_id>\d+).html)|(.*?(\#|(vid=)|b/)(?P<id>\d+?)($|&|\-))))
|
(.+?/(((?P<pseudo_id>\d+).html)|(.*?(\#|(vid=)|b/)(?P<id>\d+?)($|&|\-))))
|
||||||
|
|
|
|
||||||
@@ -23,9 +23,10 @@ class SinaIE(InfoExtractor):
|
|||||||
_TESTS = [
|
_TESTS = [
|
||||||
{
|
{
|
||||||
'url': 'http://video.sina.com.cn/news/vlist/zt/chczlj2013/?opsubject_id=top12#110028898',
|
'url': 'http://video.sina.com.cn/news/vlist/zt/chczlj2013/?opsubject_id=top12#110028898',
|
||||||
'file': '110028898.flv',
|
|
||||||
'md5': 'd65dd22ddcf44e38ce2bf58a10c3e71f',
|
'md5': 'd65dd22ddcf44e38ce2bf58a10c3e71f',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'id': '110028898',
|
||||||
|
'ext': 'flv',
|
||||||
'title': '《中国新闻》 朝鲜要求巴拿马立即释放被扣船员',
|
'title': '《中国新闻》 朝鲜要求巴拿马立即释放被扣船员',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -39,10 +40,6 @@ class SinaIE(InfoExtractor):
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def suitable(cls, url):
|
|
||||||
return re.match(cls._VALID_URL, url, flags=re.VERBOSE) is not None
|
|
||||||
|
|
||||||
def _extract_video(self, video_id):
|
def _extract_video(self, video_id):
|
||||||
data = compat_urllib_parse.urlencode({'vid': video_id})
|
data = compat_urllib_parse.urlencode({'vid': video_id})
|
||||||
url_doc = self._download_xml('http://v.iask.com/v_play.php?%s' % data,
|
url_doc = self._download_xml('http://v.iask.com/v_play.php?%s' % data,
|
||||||
@@ -59,7 +56,7 @@ class SinaIE(InfoExtractor):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url, flags=re.VERBOSE)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
video_id = mobj.group('id')
|
video_id = mobj.group('id')
|
||||||
if mobj.group('token') is not None:
|
if mobj.group('token') is not None:
|
||||||
# The video id is in the redirected url
|
# The video id is in the redirected url
|
||||||
|
@@ -90,10 +90,25 @@ class SmotriIE(InfoExtractor):
|
|||||||
},
|
},
|
||||||
'skip': 'Video is not approved by moderator',
|
'skip': 'Video is not approved by moderator',
|
||||||
},
|
},
|
||||||
|
# not approved by moderator, but available
|
||||||
|
{
|
||||||
|
'url': 'http://smotri.com/video/view/?id=v28888533b73',
|
||||||
|
'md5': 'f44bc7adac90af518ef1ecf04893bb34',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'v28888533b73',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Russian Spies Killed By ISIL Child Soldier',
|
||||||
|
'uploader': 'Mopeder',
|
||||||
|
'uploader_id': 'mopeder',
|
||||||
|
'duration': 71,
|
||||||
|
'thumbnail': 'http://frame9.loadup.ru/d7/32/2888853.2.3.jpg',
|
||||||
|
'upload_date': '20150114',
|
||||||
|
},
|
||||||
|
},
|
||||||
# swf player
|
# swf player
|
||||||
{
|
{
|
||||||
'url': 'http://pics.smotri.com/scrubber_custom8.swf?file=v9188090500',
|
'url': 'http://pics.smotri.com/scrubber_custom8.swf?file=v9188090500',
|
||||||
'md5': '4d47034979d9390d14acdf59c4935bc2',
|
'md5': '31099eeb4bc906712c5f40092045108d',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'v9188090500',
|
'id': 'v9188090500',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
@@ -124,9 +139,6 @@ class SmotriIE(InfoExtractor):
|
|||||||
def _search_meta(self, name, html, display_name=None):
|
def _search_meta(self, name, html, display_name=None):
|
||||||
if display_name is None:
|
if display_name is None:
|
||||||
display_name = name
|
display_name = name
|
||||||
return self._html_search_regex(
|
|
||||||
r'<meta itemprop="%s" content="([^"]+)" />' % re.escape(name),
|
|
||||||
html, display_name, fatal=False)
|
|
||||||
return self._html_search_meta(name, html, display_name)
|
return self._html_search_meta(name, html, display_name)
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
@@ -146,13 +158,16 @@ class SmotriIE(InfoExtractor):
|
|||||||
|
|
||||||
video = self._download_json(request, video_id, 'Downloading video JSON')
|
video = self._download_json(request, video_id, 'Downloading video JSON')
|
||||||
|
|
||||||
|
video_url = video.get('_vidURL') or video.get('_vidURL_mp4')
|
||||||
|
|
||||||
|
if not video_url:
|
||||||
if video.get('_moderate_no') or not video.get('moderated'):
|
if video.get('_moderate_no') or not video.get('moderated'):
|
||||||
raise ExtractorError('Video %s has not been approved by moderator' % video_id, expected=True)
|
raise ExtractorError(
|
||||||
|
'Video %s has not been approved by moderator' % video_id, expected=True)
|
||||||
|
|
||||||
if video.get('error'):
|
if video.get('error'):
|
||||||
raise ExtractorError('Video %s does not exist' % video_id, expected=True)
|
raise ExtractorError('Video %s does not exist' % video_id, expected=True)
|
||||||
|
|
||||||
video_url = video.get('_vidURL') or video.get('_vidURL_mp4')
|
|
||||||
title = video['title']
|
title = video['title']
|
||||||
thumbnail = video['_imgURL']
|
thumbnail = video['_imgURL']
|
||||||
upload_date = unified_strdate(video['added'])
|
upload_date = unified_strdate(video['added'])
|
||||||
|
@@ -246,6 +246,7 @@ class SoundcloudSetIE(SoundcloudIE):
|
|||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'https://soundcloud.com/the-concept-band/sets/the-royal-concept-ep',
|
'url': 'https://soundcloud.com/the-concept-band/sets/the-royal-concept-ep',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'id': '2284613',
|
||||||
'title': 'The Royal Concept EP',
|
'title': 'The Royal Concept EP',
|
||||||
},
|
},
|
||||||
'playlist_mincount': 6,
|
'playlist_mincount': 6,
|
||||||
@@ -279,7 +280,7 @@ class SoundcloudSetIE(SoundcloudIE):
|
|||||||
return {
|
return {
|
||||||
'_type': 'playlist',
|
'_type': 'playlist',
|
||||||
'entries': [self._extract_info_dict(track, secret_token=token) for track in info['tracks']],
|
'entries': [self._extract_info_dict(track, secret_token=token) for track in info['tracks']],
|
||||||
'id': info['id'],
|
'id': '%s' % info['id'],
|
||||||
'title': info['title'],
|
'title': info['title'],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -60,23 +60,24 @@ class SpiegelIE(InfoExtractor):
|
|||||||
xml_url = base_url + video_id + '.xml'
|
xml_url = base_url + video_id + '.xml'
|
||||||
idoc = self._download_xml(xml_url, video_id)
|
idoc = self._download_xml(xml_url, video_id)
|
||||||
|
|
||||||
formats = [
|
formats = []
|
||||||
{
|
for n in list(idoc):
|
||||||
'format_id': n.tag.rpartition('type')[2],
|
if n.tag.startswith('type') and n.tag != 'type6':
|
||||||
'url': base_url + n.find('./filename').text,
|
format_id = n.tag.rpartition('type')[2]
|
||||||
|
video_url = base_url + n.find('./filename').text
|
||||||
|
formats.append({
|
||||||
|
'format_id': format_id,
|
||||||
|
'url': video_url,
|
||||||
'width': int(n.find('./width').text),
|
'width': int(n.find('./width').text),
|
||||||
'height': int(n.find('./height').text),
|
'height': int(n.find('./height').text),
|
||||||
'abr': int(n.find('./audiobitrate').text),
|
'abr': int(n.find('./audiobitrate').text),
|
||||||
'vbr': int(n.find('./videobitrate').text),
|
'vbr': int(n.find('./videobitrate').text),
|
||||||
'vcodec': n.find('./codec').text,
|
'vcodec': n.find('./codec').text,
|
||||||
'acodec': 'MP4A',
|
'acodec': 'MP4A',
|
||||||
}
|
})
|
||||||
for n in list(idoc)
|
|
||||||
# Blacklist type 6, it's extremely LQ and not available on the same server
|
|
||||||
if n.tag.startswith('type') and n.tag != 'type6'
|
|
||||||
]
|
|
||||||
duration = float(idoc[0].findall('./duration')[0].text)
|
duration = float(idoc[0].findall('./duration')[0].text)
|
||||||
|
|
||||||
|
self._check_formats(formats, video_id)
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@@ -1,14 +1,12 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .mtv import MTVServicesInfoExtractor
|
from .mtv import MTVServicesInfoExtractor
|
||||||
|
|
||||||
|
|
||||||
class SpikeIE(MTVServicesInfoExtractor):
|
class SpikeIE(MTVServicesInfoExtractor):
|
||||||
_VALID_URL = r'''(?x)https?://
|
_VALID_URL = r'''(?x)https?://
|
||||||
(www\.spike\.com/(video-clips|episodes)/.+|
|
(?:www\.spike\.com/(?:video-clips|(?:full-)?episodes)/.+|
|
||||||
m\.spike\.com/videos/video.rbml\?id=(?P<mobile_id>[^&]+))
|
m\.spike\.com/videos/video\.rbml\?id=(?P<id>[^&]+))
|
||||||
'''
|
'''
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.spike.com/video-clips/lhtu8m/auction-hunters-can-allen-ride-a-hundred-year-old-motorcycle',
|
'url': 'http://www.spike.com/video-clips/lhtu8m/auction-hunters-can-allen-ride-a-hundred-year-old-motorcycle',
|
||||||
@@ -25,8 +23,7 @@ class SpikeIE(MTVServicesInfoExtractor):
|
|||||||
_MOBILE_TEMPLATE = 'http://m.spike.com/videos/video.rbml?id=%s'
|
_MOBILE_TEMPLATE = 'http://m.spike.com/videos/video.rbml?id=%s'
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.search(self._VALID_URL, url)
|
mobile_id = self._match_id(url)
|
||||||
mobile_id = mobj.group('mobile_id')
|
if mobile_id:
|
||||||
if mobile_id is not None:
|
|
||||||
url = 'http://www.spike.com/video-clips/%s' % mobile_id
|
url = 'http://www.spike.com/video-clips/%s' % mobile_id
|
||||||
return super(SpikeIE, self)._real_extract(url)
|
return super(SpikeIE, self)._real_extract(url)
|
||||||
|
@@ -8,7 +8,7 @@ from ..utils import js_to_json
|
|||||||
|
|
||||||
|
|
||||||
class SRMediathekIE(InfoExtractor):
|
class SRMediathekIE(InfoExtractor):
|
||||||
IE_DESC = 'Süddeutscher Rundfunk'
|
IE_DESC = 'Saarländischer Rundfunk'
|
||||||
_VALID_URL = r'https?://sr-mediathek\.sr-online\.de/index\.php\?.*?&id=(?P<id>[0-9]+)'
|
_VALID_URL = r'https?://sr-mediathek\.sr-online\.de/index\.php\?.*?&id=(?P<id>[0-9]+)'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
|
51
youtube_dl/extractor/streetvoice.py
Normal file
51
youtube_dl/extractor/streetvoice.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..compat import compat_str
|
||||||
|
from ..utils import unified_strdate
|
||||||
|
|
||||||
|
|
||||||
|
class StreetVoiceIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:.+?\.)?streetvoice\.com/[^/]+/songs/(?P<id>[0-9]+)'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://streetvoice.com/skippylu/songs/94440/',
|
||||||
|
'md5': '15974627fc01a29e492c98593c2fd472',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '94440',
|
||||||
|
'ext': 'mp3',
|
||||||
|
'filesize': 4167053,
|
||||||
|
'title': '輸',
|
||||||
|
'description': 'Crispy脆樂團 - 輸',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
'duration': 260,
|
||||||
|
'upload_date': '20091018',
|
||||||
|
'uploader': 'Crispy脆樂團',
|
||||||
|
'uploader_id': '627810',
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'url': 'http://tw.streetvoice.com/skippylu/songs/94440/',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
song_id = self._match_id(url)
|
||||||
|
|
||||||
|
song = self._download_json(
|
||||||
|
'http://streetvoice.com/music/api/song/%s' % song_id, song_id)
|
||||||
|
|
||||||
|
title = song['name']
|
||||||
|
author = song['musician']['name']
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': song_id,
|
||||||
|
'url': song['file'],
|
||||||
|
'filesize': song.get('size'),
|
||||||
|
'title': title,
|
||||||
|
'description': '%s - %s' % (author, title),
|
||||||
|
'thumbnail': self._proto_relative_url(song.get('image'), 'http:'),
|
||||||
|
'duration': song.get('length'),
|
||||||
|
'upload_date': unified_strdate(song.get('created_at')),
|
||||||
|
'uploader': author,
|
||||||
|
'uploader_id': compat_str(song['musician']['id']),
|
||||||
|
}
|
@@ -10,17 +10,19 @@ class TeamcocoIE(InfoExtractor):
|
|||||||
_TESTS = [
|
_TESTS = [
|
||||||
{
|
{
|
||||||
'url': 'http://teamcoco.com/video/80187/conan-becomes-a-mary-kay-beauty-consultant',
|
'url': 'http://teamcoco.com/video/80187/conan-becomes-a-mary-kay-beauty-consultant',
|
||||||
'file': '80187.mp4',
|
|
||||||
'md5': '3f7746aa0dc86de18df7539903d399ea',
|
'md5': '3f7746aa0dc86de18df7539903d399ea',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'id': '80187',
|
||||||
|
'ext': 'mp4',
|
||||||
'title': 'Conan Becomes A Mary Kay Beauty Consultant',
|
'title': 'Conan Becomes A Mary Kay Beauty Consultant',
|
||||||
'description': 'Mary Kay is perhaps the most trusted name in female beauty, so of course Conan is a natural choice to sell their products.'
|
'description': 'Mary Kay is perhaps the most trusted name in female beauty, so of course Conan is a natural choice to sell their products.'
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://teamcoco.com/video/louis-ck-interview-george-w-bush',
|
'url': 'http://teamcoco.com/video/louis-ck-interview-george-w-bush',
|
||||||
'file': '19705.mp4',
|
|
||||||
'md5': 'cde9ba0fa3506f5f017ce11ead928f9a',
|
'md5': 'cde9ba0fa3506f5f017ce11ead928f9a',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'id': '19705',
|
||||||
|
'ext': 'mp4',
|
||||||
"description": "Louis C.K. got starstruck by George W. Bush, so what? Part one.",
|
"description": "Louis C.K. got starstruck by George W. Bush, so what? Part one.",
|
||||||
"title": "Louis C.K. Interview Pt. 1 11/3/11"
|
"title": "Louis C.K. Interview Pt. 1 11/3/11"
|
||||||
}
|
}
|
||||||
@@ -36,7 +38,7 @@ class TeamcocoIE(InfoExtractor):
|
|||||||
video_id = mobj.group("video_id")
|
video_id = mobj.group("video_id")
|
||||||
if not video_id:
|
if not video_id:
|
||||||
video_id = self._html_search_regex(
|
video_id = self._html_search_regex(
|
||||||
r'data-node-id="(\d+?)"',
|
r'<div\s+class="player".*?data-id="(\d+?)"',
|
||||||
webpage, 'video id')
|
webpage, 'video id')
|
||||||
|
|
||||||
data_url = 'http://teamcoco.com/cvp/2.0/%s.xml' % video_id
|
data_url = 'http://teamcoco.com/cvp/2.0/%s.xml' % video_id
|
||||||
|
@@ -11,6 +11,7 @@ class TeleTaskIE(InfoExtractor):
|
|||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.tele-task.de/archive/video/html5/26168/',
|
'url': 'http://www.tele-task.de/archive/video/html5/26168/',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'id': '26168',
|
||||||
'title': 'Duplicate Detection',
|
'title': 'Duplicate Detection',
|
||||||
},
|
},
|
||||||
'playlist': [{
|
'playlist': [{
|
||||||
@@ -34,7 +35,6 @@ class TeleTaskIE(InfoExtractor):
|
|||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
lecture_id = self._match_id(url)
|
lecture_id = self._match_id(url)
|
||||||
|
|
||||||
webpage = self._download_webpage(url, lecture_id)
|
webpage = self._download_webpage(url, lecture_id)
|
||||||
|
|
||||||
title = self._html_search_regex(
|
title = self._html_search_regex(
|
||||||
|
72
youtube_dl/extractor/testtube.py
Normal file
72
youtube_dl/extractor/testtube.py
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
int_or_none,
|
||||||
|
qualities,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestTubeIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://testtube\.com/[^/?#]+/(?P<id>[^/?#]+)'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://testtube.com/dnews/5-weird-ways-plants-can-eat-animals?utm_source=FB&utm_medium=DNews&utm_campaign=DNewsSocial',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '60163',
|
||||||
|
'display_id': '5-weird-ways-plants-can-eat-animals',
|
||||||
|
'duration': 275,
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': '5 Weird Ways Plants Can Eat Animals',
|
||||||
|
'description': 'Why have some plants evolved to eat meat?',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
'uploader': 'DNews',
|
||||||
|
'uploader_id': 'dnews',
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
display_id = self._match_id(url)
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
video_id = self._search_regex(
|
||||||
|
r"player\.loadRevision3Item\('video_id',\s*([0-9]+)\);",
|
||||||
|
webpage, 'video ID')
|
||||||
|
|
||||||
|
all_info = self._download_json(
|
||||||
|
'https://testtube.com/api/getPlaylist.json?api_key=ba9c741bce1b9d8e3defcc22193f3651b8867e62&codecs=h264,vp8,theora&video_id=%s' % video_id,
|
||||||
|
video_id)
|
||||||
|
info = all_info['items'][0]
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
for vcodec, fdatas in info['media'].items():
|
||||||
|
for name, fdata in fdatas.items():
|
||||||
|
formats.append({
|
||||||
|
'format_id': '%s-%s' % (vcodec, name),
|
||||||
|
'url': fdata['url'],
|
||||||
|
'vcodec': vcodec,
|
||||||
|
'tbr': fdata.get('bitrate'),
|
||||||
|
})
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
duration = int_or_none(info.get('duration'))
|
||||||
|
images = info.get('images')
|
||||||
|
thumbnails = None
|
||||||
|
preference = qualities(['mini', 'small', 'medium', 'large'])
|
||||||
|
if images:
|
||||||
|
thumbnails = [{
|
||||||
|
'id': thumbnail_id,
|
||||||
|
'url': img_url,
|
||||||
|
'preference': preference(thumbnail_id)
|
||||||
|
} for thumbnail_id, img_url in images.items()]
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'display_id': display_id,
|
||||||
|
'title': info['title'],
|
||||||
|
'description': info.get('summary'),
|
||||||
|
'thumbnails': thumbnails,
|
||||||
|
'uploader': info.get('show', {}).get('name'),
|
||||||
|
'uploader_id': info.get('show', {}).get('slug'),
|
||||||
|
'duration': duration,
|
||||||
|
'formats': formats,
|
||||||
|
}
|
@@ -9,17 +9,23 @@ from ..utils import ExtractorError
|
|||||||
class TinyPicIE(InfoExtractor):
|
class TinyPicIE(InfoExtractor):
|
||||||
IE_NAME = 'tinypic'
|
IE_NAME = 'tinypic'
|
||||||
IE_DESC = 'tinypic.com videos'
|
IE_DESC = 'tinypic.com videos'
|
||||||
_VALID_URL = r'http://tinypic\.com/player\.php\?v=(?P<id>[^&]+)&s=\d+'
|
_VALID_URL = r'http://(?:.+?\.)?tinypic\.com/player\.php\?v=(?P<id>[^&]+)&s=\d+'
|
||||||
|
|
||||||
_TEST = {
|
_TESTS = [
|
||||||
|
{
|
||||||
'url': 'http://tinypic.com/player.php?v=6xw7tc%3E&s=5#.UtqZmbRFCM8',
|
'url': 'http://tinypic.com/player.php?v=6xw7tc%3E&s=5#.UtqZmbRFCM8',
|
||||||
'md5': '609b74432465364e72727ebc6203f044',
|
'md5': '609b74432465364e72727ebc6203f044',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '6xw7tc',
|
'id': '6xw7tc',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'title': 'shadow phenomenon weird',
|
'title': 'shadow phenomenon weird',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://de.tinypic.com/player.php?v=dy90yh&s=8',
|
||||||
|
'only_matching': True,
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
@@ -16,8 +16,9 @@ class TouTvIE(InfoExtractor):
|
|||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.tou.tv/30-vies/S04E41',
|
'url': 'http://www.tou.tv/30-vies/S04E41',
|
||||||
'file': '30-vies_S04E41.mp4',
|
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'id': '30-vies_S04E41',
|
||||||
|
'ext': 'mp4',
|
||||||
'title': '30 vies Saison 4 / Épisode 41',
|
'title': '30 vies Saison 4 / Épisode 41',
|
||||||
'description': 'md5:da363002db82ccbe4dafeb9cab039b09',
|
'description': 'md5:da363002db82ccbe4dafeb9cab039b09',
|
||||||
'age_limit': 8,
|
'age_limit': 8,
|
||||||
|
@@ -12,61 +12,59 @@ class TvpIE(InfoExtractor):
|
|||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://vod.tvp.pl/filmy-fabularne/filmy-za-darmo/ogniem-i-mieczem/wideo/odc-2/4278035',
|
'url': 'http://vod.tvp.pl/filmy-fabularne/filmy-za-darmo/ogniem-i-mieczem/wideo/odc-2/4278035',
|
||||||
|
'md5': 'cdd98303338b8a7f7abab5cd14092bf2',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '4278035',
|
'id': '4278035',
|
||||||
'ext': 'wmv',
|
'ext': 'wmv',
|
||||||
'title': 'Ogniem i mieczem, odc. 2',
|
'title': 'Ogniem i mieczem, odc. 2',
|
||||||
'description': 'Bohun dowiaduje się o złamaniu przez kniahinię danego mu słowa i wyrusza do Rozłogów. Helenie w ostatniej chwili udaje się uciec dzięki pomocy Zagłoby.',
|
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://vod.tvp.pl/seriale/obyczajowe/czas-honoru/sezon-1-1-13/i-seria-odc-13/194536',
|
'url': 'http://vod.tvp.pl/seriale/obyczajowe/czas-honoru/sezon-1-1-13/i-seria-odc-13/194536',
|
||||||
|
'md5': '8aa518c15e5cc32dfe8db400dc921fbb',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '194536',
|
'id': '194536',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Czas honoru, I seria – odc. 13',
|
'title': 'Czas honoru, I seria – odc. 13',
|
||||||
# 'description': 'WŁADEK\nCzesław prosi Marię o dostarczenie Władkowi zarazki tyfusu. Jeśli zachoruje zostanie przewieziony do szpitala skąd łatwiej będzie go odbić. Czy matka zdecyduje się zarazić syna? Karol odwiedza Wandę przyznaje się, że ją oszukiwał, ale ostrzega też, że grozi jej aresztowanie i nalega, żeby wyjechała z Warszawy. Czy dziewczyna zdecyduje się znów oddalić od ukochanego? Rozpoczyna się akcja odbicia Władka.',
|
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.tvp.pl/there-can-be-anything-so-i-shortened-it/17916176',
|
'url': 'http://www.tvp.pl/there-can-be-anything-so-i-shortened-it/17916176',
|
||||||
|
'md5': 'c3b15ed1af288131115ff17a17c19dda',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '17916176',
|
'id': '17916176',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'TVP Gorzów pokaże filmy studentów z podroży dookoła świata',
|
'title': 'TVP Gorzów pokaże filmy studentów z podroży dookoła świata',
|
||||||
},
|
},
|
||||||
'params': {
|
|
||||||
# m3u8 download
|
|
||||||
'skip_download': 'true',
|
|
||||||
},
|
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://vod.tvp.pl/seriale/obyczajowe/na-sygnale/sezon-2-27-/odc-39/17834272',
|
'url': 'http://vod.tvp.pl/seriale/obyczajowe/na-sygnale/sezon-2-27-/odc-39/17834272',
|
||||||
|
'md5': 'c3b15ed1af288131115ff17a17c19dda',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '17834272',
|
'id': '17834272',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Na sygnale, odc. 39',
|
'title': 'Na sygnale, odc. 39',
|
||||||
'description': 'Ekipa Wiktora ratuje młodą matkę, która spadła ze schodów trzymając na rękach noworodka. Okazuje się, że dziewczyna jest surogatką, a biologiczni rodzice dziecka próbują zmusić ją do oddania synka…',
|
|
||||||
},
|
|
||||||
'params': {
|
|
||||||
# m3u8 download
|
|
||||||
'skip_download': 'true',
|
|
||||||
},
|
},
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
webpage = self._download_webpage(
|
webpage = self._download_webpage(
|
||||||
'http://www.tvp.pl/sess/tvplayer.php?object_id=%s' % video_id, video_id)
|
'http://www.tvp.pl/sess/tvplayer.php?object_id=%s' % video_id, video_id)
|
||||||
|
|
||||||
title = self._og_search_title(webpage)
|
title = self._search_regex(
|
||||||
series = self._search_regex(
|
r'name\s*:\s*([\'"])Title\1\s*,\s*value\s*:\s*\1(?P<title>.+?)\1',
|
||||||
r'{name:\s*([\'"])SeriesTitle\1,\s*value:\s*\1(?P<series>.*?)\1},',
|
webpage, 'title', group='title')
|
||||||
|
series_title = self._search_regex(
|
||||||
|
r'name\s*:\s*([\'"])SeriesTitle\1\s*,\s*value\s*:\s*\1(?P<series>.+?)\1',
|
||||||
webpage, 'series', group='series', default=None)
|
webpage, 'series', group='series', default=None)
|
||||||
if series is not None and series not in title:
|
if series_title:
|
||||||
title = '%s, %s' % (series, title)
|
title = '%s, %s' % (series_title, title)
|
||||||
description = self._og_search_description(webpage, default=None)
|
|
||||||
|
thumbnail = self._search_regex(
|
||||||
|
r"poster\s*:\s*'([^']+)'", webpage, 'thumbnail', default=None)
|
||||||
|
|
||||||
video_url = self._search_regex(
|
video_url = self._search_regex(
|
||||||
r'0:{src:([\'"])(?P<url>.*?)\1', webpage, 'formats', group='url', default=None)
|
r'0:{src:([\'"])(?P<url>.*?)\1', webpage, 'formats', group='url', default=None)
|
||||||
if video_url is None:
|
if not video_url:
|
||||||
video_url = self._download_json(
|
video_url = self._download_json(
|
||||||
'http://www.tvp.pl/pub/stat/videofileinfo?video_id=%s' % video_id,
|
'http://www.tvp.pl/pub/stat/videofileinfo?video_id=%s' % video_id,
|
||||||
video_id)['video_url']
|
video_id)['video_url']
|
||||||
@@ -89,8 +87,7 @@ class TvpIE(InfoExtractor):
|
|||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': title,
|
'title': title,
|
||||||
'thumbnail': self._og_search_thumbnail(webpage),
|
'thumbnail': thumbnail,
|
||||||
'description': description,
|
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -3,9 +3,11 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import itertools
|
import itertools
|
||||||
import re
|
import re
|
||||||
|
import random
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import (
|
||||||
|
compat_str,
|
||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
compat_urllib_request,
|
compat_urllib_request,
|
||||||
)
|
)
|
||||||
@@ -15,44 +17,12 @@ from ..utils import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TwitchIE(InfoExtractor):
|
class TwitchBaseIE(InfoExtractor):
|
||||||
# TODO: One broadcast may be split into multiple videos. The key
|
_VALID_URL_BASE = r'https?://(?:www\.)?twitch\.tv'
|
||||||
# 'broadcast_id' is the same for all parts, and 'broadcast_part'
|
|
||||||
# starts at 1 and increases. Can we treat all parts as one video?
|
|
||||||
_VALID_URL = r"""(?x)^(?:http://)?(?:www\.)?twitch\.tv/
|
|
||||||
(?:
|
|
||||||
(?P<channelid>[^/]+)|
|
|
||||||
(?:(?:[^/]+)/v/(?P<vodid>[^/]+))|
|
|
||||||
(?:(?:[^/]+)/b/(?P<videoid>[^/]+))|
|
|
||||||
(?:(?:[^/]+)/c/(?P<chapterid>[^/]+))
|
|
||||||
)
|
|
||||||
/?(?:\#.*)?$
|
|
||||||
"""
|
|
||||||
_PAGE_LIMIT = 100
|
|
||||||
_API_BASE = 'https://api.twitch.tv'
|
_API_BASE = 'https://api.twitch.tv'
|
||||||
|
_USHER_BASE = 'http://usher.twitch.tv'
|
||||||
_LOGIN_URL = 'https://secure.twitch.tv/user/login'
|
_LOGIN_URL = 'https://secure.twitch.tv/user/login'
|
||||||
_TESTS = [{
|
|
||||||
'url': 'http://www.twitch.tv/riotgames/b/577357806',
|
|
||||||
'info_dict': {
|
|
||||||
'id': 'a577357806',
|
|
||||||
'title': 'Worlds Semifinals - Star Horn Royal Club vs. OMG',
|
|
||||||
},
|
|
||||||
'playlist_mincount': 12,
|
|
||||||
}, {
|
|
||||||
'url': 'http://www.twitch.tv/acracingleague/c/5285812',
|
|
||||||
'info_dict': {
|
|
||||||
'id': 'c5285812',
|
|
||||||
'title': 'ACRL Off Season - Sports Cars @ Nordschleife',
|
|
||||||
},
|
|
||||||
'playlist_mincount': 3,
|
|
||||||
}, {
|
|
||||||
'url': 'http://www.twitch.tv/vanillatv',
|
|
||||||
'info_dict': {
|
|
||||||
'id': 'vanillatv',
|
|
||||||
'title': 'VanillaTV',
|
|
||||||
},
|
|
||||||
'playlist_mincount': 412,
|
|
||||||
}]
|
|
||||||
|
|
||||||
def _handle_error(self, response):
|
def _handle_error(self, response):
|
||||||
if not isinstance(response, dict):
|
if not isinstance(response, dict):
|
||||||
@@ -64,71 +34,10 @@ class TwitchIE(InfoExtractor):
|
|||||||
expected=True)
|
expected=True)
|
||||||
|
|
||||||
def _download_json(self, url, video_id, note='Downloading JSON metadata'):
|
def _download_json(self, url, video_id, note='Downloading JSON metadata'):
|
||||||
response = super(TwitchIE, self)._download_json(url, video_id, note)
|
response = super(TwitchBaseIE, self)._download_json(url, video_id, note)
|
||||||
self._handle_error(response)
|
self._handle_error(response)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def _extract_media(self, item, item_id):
|
|
||||||
ITEMS = {
|
|
||||||
'a': 'video',
|
|
||||||
'v': 'vod',
|
|
||||||
'c': 'chapter',
|
|
||||||
}
|
|
||||||
info = self._extract_info(self._download_json(
|
|
||||||
'%s/kraken/videos/%s%s' % (self._API_BASE, item, item_id), item_id,
|
|
||||||
'Downloading %s info JSON' % ITEMS[item]))
|
|
||||||
|
|
||||||
if item == 'v':
|
|
||||||
access_token = self._download_json(
|
|
||||||
'%s/api/vods/%s/access_token' % (self._API_BASE, item_id), item_id,
|
|
||||||
'Downloading %s access token' % ITEMS[item])
|
|
||||||
formats = self._extract_m3u8_formats(
|
|
||||||
'http://usher.twitch.tv/vod/%s?nauth=%s&nauthsig=%s'
|
|
||||||
% (item_id, access_token['token'], access_token['sig']),
|
|
||||||
item_id, 'mp4')
|
|
||||||
info['formats'] = formats
|
|
||||||
return info
|
|
||||||
|
|
||||||
response = self._download_json(
|
|
||||||
'%s/api/videos/%s%s' % (self._API_BASE, item, item_id), item_id,
|
|
||||||
'Downloading %s playlist JSON' % ITEMS[item])
|
|
||||||
entries = []
|
|
||||||
chunks = response['chunks']
|
|
||||||
qualities = list(chunks.keys())
|
|
||||||
for num, fragment in enumerate(zip(*chunks.values()), start=1):
|
|
||||||
formats = []
|
|
||||||
for fmt_num, fragment_fmt in enumerate(fragment):
|
|
||||||
format_id = qualities[fmt_num]
|
|
||||||
fmt = {
|
|
||||||
'url': fragment_fmt['url'],
|
|
||||||
'format_id': format_id,
|
|
||||||
'quality': 1 if format_id == 'live' else 0,
|
|
||||||
}
|
|
||||||
m = re.search(r'^(?P<height>\d+)[Pp]', format_id)
|
|
||||||
if m:
|
|
||||||
fmt['height'] = int(m.group('height'))
|
|
||||||
formats.append(fmt)
|
|
||||||
self._sort_formats(formats)
|
|
||||||
entry = dict(info)
|
|
||||||
entry['id'] = '%s_%d' % (entry['id'], num)
|
|
||||||
entry['title'] = '%s part %d' % (entry['title'], num)
|
|
||||||
entry['formats'] = formats
|
|
||||||
entries.append(entry)
|
|
||||||
return self.playlist_result(entries, info['id'], info['title'])
|
|
||||||
|
|
||||||
def _extract_info(self, info):
|
|
||||||
return {
|
|
||||||
'id': info['_id'],
|
|
||||||
'title': info['title'],
|
|
||||||
'description': info['description'],
|
|
||||||
'duration': info['length'],
|
|
||||||
'thumbnail': info['preview'],
|
|
||||||
'uploader': info['channel']['display_name'],
|
|
||||||
'uploader_id': info['channel']['name'],
|
|
||||||
'timestamp': parse_iso8601(info['recorded_at']),
|
|
||||||
'view_count': info['views'],
|
|
||||||
}
|
|
||||||
|
|
||||||
def _real_initialize(self):
|
def _real_initialize(self):
|
||||||
self._login()
|
self._login()
|
||||||
|
|
||||||
@@ -167,66 +76,139 @@ class TwitchIE(InfoExtractor):
|
|||||||
raise ExtractorError(
|
raise ExtractorError(
|
||||||
'Unable to login: %s' % m.group('msg').strip(), expected=True)
|
'Unable to login: %s' % m.group('msg').strip(), expected=True)
|
||||||
|
|
||||||
def _real_extract(self, url):
|
|
||||||
mobj = re.match(self._VALID_URL, url)
|
|
||||||
if mobj.group('chapterid'):
|
|
||||||
return self._extract_media('c', mobj.group('chapterid'))
|
|
||||||
|
|
||||||
"""
|
class TwitchItemBaseIE(TwitchBaseIE):
|
||||||
webpage = self._download_webpage(url, chapter_id)
|
def _download_info(self, item, item_id):
|
||||||
m = re.search(r'PP\.archive_id = "([0-9]+)";', webpage)
|
return self._extract_info(self._download_json(
|
||||||
if not m:
|
'%s/kraken/videos/%s%s' % (self._API_BASE, item, item_id), item_id,
|
||||||
raise ExtractorError('Cannot find archive of a chapter')
|
'Downloading %s info JSON' % self._ITEM_TYPE))
|
||||||
archive_id = m.group(1)
|
|
||||||
|
|
||||||
api = api_base + '/broadcast/by_chapter/%s.xml' % chapter_id
|
def _extract_media(self, item_id):
|
||||||
doc = self._download_xml(
|
info = self._download_info(self._ITEM_SHORTCUT, item_id)
|
||||||
api, chapter_id,
|
response = self._download_json(
|
||||||
note='Downloading chapter information',
|
'%s/api/videos/%s%s' % (self._API_BASE, self._ITEM_SHORTCUT, item_id), item_id,
|
||||||
errnote='Chapter information download failed')
|
'Downloading %s playlist JSON' % self._ITEM_TYPE)
|
||||||
for a in doc.findall('.//archive'):
|
entries = []
|
||||||
if archive_id == a.find('./id').text:
|
chunks = response['chunks']
|
||||||
break
|
qualities = list(chunks.keys())
|
||||||
else:
|
for num, fragment in enumerate(zip(*chunks.values()), start=1):
|
||||||
raise ExtractorError('Could not find chapter in chapter information')
|
formats = []
|
||||||
|
for fmt_num, fragment_fmt in enumerate(fragment):
|
||||||
video_url = a.find('./video_file_url').text
|
format_id = qualities[fmt_num]
|
||||||
video_ext = video_url.rpartition('.')[2] or 'flv'
|
fmt = {
|
||||||
|
'url': fragment_fmt['url'],
|
||||||
chapter_api_url = 'https://api.twitch.tv/kraken/videos/c' + chapter_id
|
'format_id': format_id,
|
||||||
chapter_info = self._download_json(
|
'quality': 1 if format_id == 'live' else 0,
|
||||||
chapter_api_url, 'c' + chapter_id,
|
|
||||||
note='Downloading chapter metadata',
|
|
||||||
errnote='Download of chapter metadata failed')
|
|
||||||
|
|
||||||
bracket_start = int(doc.find('.//bracket_start').text)
|
|
||||||
bracket_end = int(doc.find('.//bracket_end').text)
|
|
||||||
|
|
||||||
# TODO determine start (and probably fix up file)
|
|
||||||
# youtube-dl -v http://www.twitch.tv/firmbelief/c/1757457
|
|
||||||
#video_url += '?start=' + TODO:start_timestamp
|
|
||||||
# bracket_start is 13290, but we want 51670615
|
|
||||||
self._downloader.report_warning('Chapter detected, but we can just download the whole file. '
|
|
||||||
'Chapter starts at %s and ends at %s' % (formatSeconds(bracket_start), formatSeconds(bracket_end)))
|
|
||||||
|
|
||||||
info = {
|
|
||||||
'id': 'c' + chapter_id,
|
|
||||||
'url': video_url,
|
|
||||||
'ext': video_ext,
|
|
||||||
'title': chapter_info['title'],
|
|
||||||
'thumbnail': chapter_info['preview'],
|
|
||||||
'description': chapter_info['description'],
|
|
||||||
'uploader': chapter_info['channel']['display_name'],
|
|
||||||
'uploader_id': chapter_info['channel']['name'],
|
|
||||||
}
|
}
|
||||||
|
m = re.search(r'^(?P<height>\d+)[Pp]', format_id)
|
||||||
|
if m:
|
||||||
|
fmt['height'] = int(m.group('height'))
|
||||||
|
formats.append(fmt)
|
||||||
|
self._sort_formats(formats)
|
||||||
|
entry = dict(info)
|
||||||
|
entry['id'] = '%s_%d' % (entry['id'], num)
|
||||||
|
entry['title'] = '%s part %d' % (entry['title'], num)
|
||||||
|
entry['formats'] = formats
|
||||||
|
entries.append(entry)
|
||||||
|
return self.playlist_result(entries, info['id'], info['title'])
|
||||||
|
|
||||||
|
def _extract_info(self, info):
|
||||||
|
return {
|
||||||
|
'id': info['_id'],
|
||||||
|
'title': info['title'],
|
||||||
|
'description': info['description'],
|
||||||
|
'duration': info['length'],
|
||||||
|
'thumbnail': info['preview'],
|
||||||
|
'uploader': info['channel']['display_name'],
|
||||||
|
'uploader_id': info['channel']['name'],
|
||||||
|
'timestamp': parse_iso8601(info['recorded_at']),
|
||||||
|
'view_count': info['views'],
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
return self._extract_media(self._match_id(url))
|
||||||
|
|
||||||
|
|
||||||
|
class TwitchVideoIE(TwitchItemBaseIE):
|
||||||
|
IE_NAME = 'twitch:video'
|
||||||
|
_VALID_URL = r'%s/[^/]+/b/(?P<id>[^/]+)' % TwitchBaseIE._VALID_URL_BASE
|
||||||
|
_ITEM_TYPE = 'video'
|
||||||
|
_ITEM_SHORTCUT = 'a'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.twitch.tv/riotgames/b/577357806',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'a577357806',
|
||||||
|
'title': 'Worlds Semifinals - Star Horn Royal Club vs. OMG',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 12,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TwitchChapterIE(TwitchItemBaseIE):
|
||||||
|
IE_NAME = 'twitch:chapter'
|
||||||
|
_VALID_URL = r'%s/[^/]+/c/(?P<id>[^/]+)' % TwitchBaseIE._VALID_URL_BASE
|
||||||
|
_ITEM_TYPE = 'chapter'
|
||||||
|
_ITEM_SHORTCUT = 'c'
|
||||||
|
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://www.twitch.tv/acracingleague/c/5285812',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'c5285812',
|
||||||
|
'title': 'ACRL Off Season - Sports Cars @ Nordschleife',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 3,
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.twitch.tv/tsm_theoddone/c/2349361',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
|
||||||
|
class TwitchVodIE(TwitchItemBaseIE):
|
||||||
|
IE_NAME = 'twitch:vod'
|
||||||
|
_VALID_URL = r'%s/[^/]+/v/(?P<id>[^/]+)' % TwitchBaseIE._VALID_URL_BASE
|
||||||
|
_ITEM_TYPE = 'vod'
|
||||||
|
_ITEM_SHORTCUT = 'v'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.twitch.tv/ksptv/v/3622000',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'v3622000',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': '''KSPTV: Squadcast: "Everyone's on vacation so here's Dahud" Edition!''',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
'duration': 6951,
|
||||||
|
'timestamp': 1419028564,
|
||||||
|
'upload_date': '20141219',
|
||||||
|
'uploader': 'KSPTV',
|
||||||
|
'uploader_id': 'ksptv',
|
||||||
|
'view_count': int,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# m3u8 download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
item_id = self._match_id(url)
|
||||||
|
info = self._download_info(self._ITEM_SHORTCUT, item_id)
|
||||||
|
access_token = self._download_json(
|
||||||
|
'%s/api/vods/%s/access_token' % (self._API_BASE, item_id), item_id,
|
||||||
|
'Downloading %s access token' % self._ITEM_TYPE)
|
||||||
|
formats = self._extract_m3u8_formats(
|
||||||
|
'%s/vod/%s?nauth=%s&nauthsig=%s'
|
||||||
|
% (self._USHER_BASE, item_id, access_token['token'], access_token['sig']),
|
||||||
|
item_id, 'mp4')
|
||||||
|
info['formats'] = formats
|
||||||
return info
|
return info
|
||||||
"""
|
|
||||||
elif mobj.group('videoid'):
|
|
||||||
return self._extract_media('a', mobj.group('videoid'))
|
class TwitchPlaylistBaseIE(TwitchBaseIE):
|
||||||
elif mobj.group('vodid'):
|
_PLAYLIST_URL = '%s/kraken/channels/%%s/videos/?offset=%%d&limit=%%d' % TwitchBaseIE._API_BASE
|
||||||
return self._extract_media('v', mobj.group('vodid'))
|
_PAGE_LIMIT = 100
|
||||||
elif mobj.group('channelid'):
|
|
||||||
channel_id = mobj.group('channelid')
|
def _extract_playlist(self, channel_id):
|
||||||
info = self._download_json(
|
info = self._download_json(
|
||||||
'%s/kraken/channels/%s' % (self._API_BASE, channel_id),
|
'%s/kraken/channels/%s' % (self._API_BASE, channel_id),
|
||||||
channel_id, 'Downloading channel info JSON')
|
channel_id, 'Downloading channel info JSON')
|
||||||
@@ -236,12 +218,165 @@ class TwitchIE(InfoExtractor):
|
|||||||
limit = self._PAGE_LIMIT
|
limit = self._PAGE_LIMIT
|
||||||
for counter in itertools.count(1):
|
for counter in itertools.count(1):
|
||||||
response = self._download_json(
|
response = self._download_json(
|
||||||
'%s/kraken/channels/%s/videos/?offset=%d&limit=%d'
|
self._PLAYLIST_URL % (channel_id, offset, limit),
|
||||||
% (self._API_BASE, channel_id, offset, limit),
|
channel_id, 'Downloading %s videos JSON page %d' % (self._PLAYLIST_TYPE, counter))
|
||||||
channel_id, 'Downloading channel videos JSON page %d' % counter)
|
page_entries = self._extract_playlist_page(response)
|
||||||
videos = response['videos']
|
if not page_entries:
|
||||||
if not videos:
|
|
||||||
break
|
break
|
||||||
entries.extend([self.url_result(video['url'], 'Twitch') for video in videos])
|
entries.extend(page_entries)
|
||||||
offset += limit
|
offset += limit
|
||||||
return self.playlist_result(entries, channel_id, channel_name)
|
return self.playlist_result(
|
||||||
|
[self.url_result(entry) for entry in set(entries)],
|
||||||
|
channel_id, channel_name)
|
||||||
|
|
||||||
|
def _extract_playlist_page(self, response):
|
||||||
|
videos = response.get('videos')
|
||||||
|
return [video['url'] for video in videos] if videos else []
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
return self._extract_playlist(self._match_id(url))
|
||||||
|
|
||||||
|
|
||||||
|
class TwitchProfileIE(TwitchPlaylistBaseIE):
|
||||||
|
IE_NAME = 'twitch:profile'
|
||||||
|
_VALID_URL = r'%s/(?P<id>[^/]+)/profile/?(?:\#.*)?$' % TwitchBaseIE._VALID_URL_BASE
|
||||||
|
_PLAYLIST_TYPE = 'profile'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.twitch.tv/vanillatv/profile',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'vanillatv',
|
||||||
|
'title': 'VanillaTV',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 412,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TwitchPastBroadcastsIE(TwitchPlaylistBaseIE):
|
||||||
|
IE_NAME = 'twitch:past_broadcasts'
|
||||||
|
_VALID_URL = r'%s/(?P<id>[^/]+)/profile/past_broadcasts/?(?:\#.*)?$' % TwitchBaseIE._VALID_URL_BASE
|
||||||
|
_PLAYLIST_URL = TwitchPlaylistBaseIE._PLAYLIST_URL + '&broadcasts=true'
|
||||||
|
_PLAYLIST_TYPE = 'past broadcasts'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.twitch.tv/spamfish/profile/past_broadcasts',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'spamfish',
|
||||||
|
'title': 'Spamfish',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 54,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TwitchBookmarksIE(TwitchPlaylistBaseIE):
|
||||||
|
IE_NAME = 'twitch:bookmarks'
|
||||||
|
_VALID_URL = r'%s/(?P<id>[^/]+)/profile/bookmarks/?(?:\#.*)?$' % TwitchBaseIE._VALID_URL_BASE
|
||||||
|
_PLAYLIST_URL = '%s/api/bookmark/?user=%%s&offset=%%d&limit=%%d' % TwitchBaseIE._API_BASE
|
||||||
|
_PLAYLIST_TYPE = 'bookmarks'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.twitch.tv/ognos/profile/bookmarks',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'ognos',
|
||||||
|
'title': 'Ognos',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _extract_playlist_page(self, response):
|
||||||
|
entries = []
|
||||||
|
for bookmark in response.get('bookmarks', []):
|
||||||
|
video = bookmark.get('video')
|
||||||
|
if not video:
|
||||||
|
continue
|
||||||
|
entries.append(video['url'])
|
||||||
|
return entries
|
||||||
|
|
||||||
|
|
||||||
|
class TwitchStreamIE(TwitchBaseIE):
|
||||||
|
IE_NAME = 'twitch:stream'
|
||||||
|
_VALID_URL = r'%s/(?P<id>[^/]+)/?(?:\#.*)?$' % TwitchBaseIE._VALID_URL_BASE
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.twitch.tv/shroomztv',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '12772022048',
|
||||||
|
'display_id': 'shroomztv',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 're:^ShroomzTV [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$',
|
||||||
|
'description': 'H1Z1 - lonewolfing with ShroomzTV | A3 Battle Royale later - @ShroomzTV',
|
||||||
|
'is_live': True,
|
||||||
|
'timestamp': 1421928037,
|
||||||
|
'upload_date': '20150122',
|
||||||
|
'uploader': 'ShroomzTV',
|
||||||
|
'uploader_id': 'shroomztv',
|
||||||
|
'view_count': int,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# m3u8 download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
channel_id = self._match_id(url)
|
||||||
|
|
||||||
|
stream = self._download_json(
|
||||||
|
'%s/kraken/streams/%s' % (self._API_BASE, channel_id), channel_id,
|
||||||
|
'Downloading stream JSON').get('stream')
|
||||||
|
|
||||||
|
# Fallback on profile extraction if stream is offline
|
||||||
|
if not stream:
|
||||||
|
return self.url_result(
|
||||||
|
'http://www.twitch.tv/%s/profile' % channel_id,
|
||||||
|
'TwitchProfile', channel_id)
|
||||||
|
|
||||||
|
access_token = self._download_json(
|
||||||
|
'%s/api/channels/%s/access_token' % (self._API_BASE, channel_id), channel_id,
|
||||||
|
'Downloading channel access token')
|
||||||
|
|
||||||
|
query = {
|
||||||
|
'allow_source': 'true',
|
||||||
|
'p': random.randint(1000000, 10000000),
|
||||||
|
'player': 'twitchweb',
|
||||||
|
'segment_preference': '4',
|
||||||
|
'sig': access_token['sig'],
|
||||||
|
'token': access_token['token'],
|
||||||
|
}
|
||||||
|
|
||||||
|
formats = self._extract_m3u8_formats(
|
||||||
|
'%s/api/channel/hls/%s.m3u8?%s'
|
||||||
|
% (self._USHER_BASE, channel_id, compat_urllib_parse.urlencode(query).encode('utf-8')),
|
||||||
|
channel_id, 'mp4')
|
||||||
|
|
||||||
|
view_count = stream.get('viewers')
|
||||||
|
timestamp = parse_iso8601(stream.get('created_at'))
|
||||||
|
|
||||||
|
channel = stream['channel']
|
||||||
|
title = self._live_title(channel.get('display_name') or channel.get('name'))
|
||||||
|
description = channel.get('status')
|
||||||
|
|
||||||
|
thumbnails = []
|
||||||
|
for thumbnail_key, thumbnail_url in stream['preview'].items():
|
||||||
|
m = re.search(r'(?P<width>\d+)x(?P<height>\d+)\.jpg$', thumbnail_key)
|
||||||
|
if not m:
|
||||||
|
continue
|
||||||
|
thumbnails.append({
|
||||||
|
'url': thumbnail_url,
|
||||||
|
'width': int(m.group('width')),
|
||||||
|
'height': int(m.group('height')),
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': compat_str(stream['_id']),
|
||||||
|
'display_id': channel_id,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'thumbnails': thumbnails,
|
||||||
|
'uploader': channel.get('display_name'),
|
||||||
|
'uploader_id': channel.get('name'),
|
||||||
|
'timestamp': timestamp,
|
||||||
|
'view_count': view_count,
|
||||||
|
'formats': formats,
|
||||||
|
'is_live': True,
|
||||||
|
}
|
||||||
|
@@ -3,50 +3,51 @@ from __future__ import unicode_literals
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import int_or_none
|
from ..utils import (
|
||||||
|
int_or_none,
|
||||||
|
qualities,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class UbuIE(InfoExtractor):
|
class UbuIE(InfoExtractor):
|
||||||
_VALID_URL = r'http://(?:www\.)?ubu\.com/film/(?P<id>[\da-z_-]+)\.html'
|
_VALID_URL = r'http://(?:www\.)?ubu\.com/film/(?P<id>[\da-z_-]+)\.html'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://ubu.com/film/her_noise.html',
|
'url': 'http://ubu.com/film/her_noise.html',
|
||||||
'md5': '8edd46ee8aa6b265fb5ed6cf05c36bc9',
|
'md5': '138d5652618bf0f03878978db9bef1ee',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'her_noise',
|
'id': 'her_noise',
|
||||||
'ext': 'mp4',
|
'ext': 'm4v',
|
||||||
'title': 'Her Noise - The Making Of (2007)',
|
'title': 'Her Noise - The Making Of (2007)',
|
||||||
'duration': 3600,
|
'duration': 3600,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = mobj.group('id')
|
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
title = self._html_search_regex(
|
title = self._html_search_regex(
|
||||||
r'<title>.+?Film & Video: ([^<]+)</title>', webpage, 'title')
|
r'<title>.+?Film & Video: ([^<]+)</title>', webpage, 'title')
|
||||||
|
|
||||||
duration = int_or_none(self._html_search_regex(
|
duration = int_or_none(self._html_search_regex(
|
||||||
r'Duration: (\d+) minutes', webpage, 'duration', fatal=False, default=None))
|
r'Duration: (\d+) minutes', webpage, 'duration', fatal=False),
|
||||||
if duration:
|
invscale=60)
|
||||||
duration *= 60
|
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
|
|
||||||
FORMAT_REGEXES = [
|
FORMAT_REGEXES = [
|
||||||
['sq', r"'flashvars'\s*,\s*'file=([^']+)'"],
|
('sq', r"'flashvars'\s*,\s*'file=([^']+)'"),
|
||||||
['hq', r'href="(http://ubumexico\.centro\.org\.mx/video/[^"]+)"']
|
('hq', r'href="(http://ubumexico\.centro\.org\.mx/video/[^"]+)"'),
|
||||||
]
|
]
|
||||||
|
preference = qualities([fid for fid, _ in FORMAT_REGEXES])
|
||||||
for format_id, format_regex in FORMAT_REGEXES:
|
for format_id, format_regex in FORMAT_REGEXES:
|
||||||
m = re.search(format_regex, webpage)
|
m = re.search(format_regex, webpage)
|
||||||
if m:
|
if m:
|
||||||
formats.append({
|
formats.append({
|
||||||
'url': m.group(1),
|
'url': m.group(1),
|
||||||
'format_id': format_id,
|
'format_id': format_id,
|
||||||
|
'preference': preference(format_id),
|
||||||
})
|
})
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
|
@@ -8,6 +8,7 @@ from ..compat import (
|
|||||||
compat_urlparse,
|
compat_urlparse,
|
||||||
)
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
|
ExtractorError,
|
||||||
clean_html,
|
clean_html,
|
||||||
get_element_by_id,
|
get_element_by_id,
|
||||||
)
|
)
|
||||||
@@ -17,13 +18,13 @@ class VeeHDIE(InfoExtractor):
|
|||||||
_VALID_URL = r'https?://veehd\.com/video/(?P<id>\d+)'
|
_VALID_URL = r'https?://veehd\.com/video/(?P<id>\d+)'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://veehd.com/video/4686958',
|
'url': 'http://veehd.com/video/4639434_Solar-Sinter',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '4686958',
|
'id': '4639434',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Time Lapse View from Space ( ISS)',
|
'title': 'Solar Sinter',
|
||||||
'uploader_id': 'spotted',
|
'uploader_id': 'VideoEyes',
|
||||||
'description': 'md5:f0094c4cf3a72e22bc4e4239ef767ad7',
|
'description': 'md5:46a840e8692ddbaffb5f81d9885cb457',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,6 +35,10 @@ class VeeHDIE(InfoExtractor):
|
|||||||
# See https://github.com/rg3/youtube-dl/issues/2102
|
# See https://github.com/rg3/youtube-dl/issues/2102
|
||||||
self._download_webpage(url, video_id, 'Requesting webpage')
|
self._download_webpage(url, video_id, 'Requesting webpage')
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
if 'This video has been removed<' in webpage:
|
||||||
|
raise ExtractorError('Video %s has been removed' % video_id, expected=True)
|
||||||
|
|
||||||
player_path = self._search_regex(
|
player_path = self._search_regex(
|
||||||
r'\$\("#playeriframe"\).attr\({src : "(.+?)"',
|
r'\$\("#playeriframe"\).attr\({src : "(.+?)"',
|
||||||
webpage, 'player path')
|
webpage, 'player path')
|
||||||
@@ -42,17 +47,34 @@ class VeeHDIE(InfoExtractor):
|
|||||||
self._download_webpage(player_url, video_id, 'Requesting player page')
|
self._download_webpage(player_url, video_id, 'Requesting player page')
|
||||||
player_page = self._download_webpage(
|
player_page = self._download_webpage(
|
||||||
player_url, video_id, 'Downloading player page')
|
player_url, video_id, 'Downloading player page')
|
||||||
config_json = self._search_regex(
|
|
||||||
r'value=\'config=({.+?})\'', player_page, 'config json')
|
|
||||||
config = json.loads(config_json)
|
|
||||||
|
|
||||||
|
config_json = self._search_regex(
|
||||||
|
r'value=\'config=({.+?})\'', player_page, 'config json', default=None)
|
||||||
|
|
||||||
|
if config_json:
|
||||||
|
config = json.loads(config_json)
|
||||||
video_url = compat_urlparse.unquote(config['clip']['url'])
|
video_url = compat_urlparse.unquote(config['clip']['url'])
|
||||||
|
else:
|
||||||
|
iframe_src = self._search_regex(
|
||||||
|
r'<iframe[^>]+src="/?([^"]+)"', player_page, 'iframe url')
|
||||||
|
iframe_url = 'http://veehd.com/%s' % iframe_src
|
||||||
|
|
||||||
|
self._download_webpage(iframe_url, video_id, 'Requesting iframe page')
|
||||||
|
iframe_page = self._download_webpage(
|
||||||
|
iframe_url, video_id, 'Downloading iframe page')
|
||||||
|
|
||||||
|
video_url = self._search_regex(
|
||||||
|
r"file\s*:\s*'([^']+)'", iframe_page, 'video url')
|
||||||
|
|
||||||
title = clean_html(get_element_by_id('videoName', webpage).rpartition('|')[0])
|
title = clean_html(get_element_by_id('videoName', webpage).rpartition('|')[0])
|
||||||
uploader_id = self._html_search_regex(r'<a href="/profile/\d+">(.+?)</a>',
|
uploader_id = self._html_search_regex(
|
||||||
|
r'<a href="/profile/\d+">(.+?)</a>',
|
||||||
webpage, 'uploader')
|
webpage, 'uploader')
|
||||||
thumbnail = self._search_regex(r'<img id="veehdpreview" src="(.+?)"',
|
thumbnail = self._search_regex(
|
||||||
|
r'<img id="veehdpreview" src="(.+?)"',
|
||||||
webpage, 'thumbnail')
|
webpage, 'thumbnail')
|
||||||
description = self._html_search_regex(r'<td class="infodropdown".*?<div>(.*?)<ul',
|
description = self._html_search_regex(
|
||||||
|
r'<td class="infodropdown".*?<div>(.*?)<ul',
|
||||||
webpage, 'description', flags=re.DOTALL)
|
webpage, 'description', flags=re.DOTALL)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@@ -5,27 +5,58 @@ from ..utils import (
|
|||||||
float_or_none,
|
float_or_none,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
)
|
)
|
||||||
|
from ..compat import (
|
||||||
|
compat_urllib_request
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ViddlerIE(InfoExtractor):
|
class ViddlerIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?viddler\.com/(?:v|embed|player)/(?P<id>[a-z0-9]+)'
|
_VALID_URL = r'https?://(?:www\.)?viddler\.com/(?:v|embed|player)/(?P<id>[a-z0-9]+)'
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
"url": "http://www.viddler.com/v/43903784",
|
'url': 'http://www.viddler.com/v/43903784',
|
||||||
'md5': 'ae43ad7cb59431ce043f0ff7fa13cbf4',
|
'md5': 'ae43ad7cb59431ce043f0ff7fa13cbf4',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '43903784',
|
'id': '43903784',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
"title": "Video Made Easy",
|
'title': 'Video Made Easy',
|
||||||
'description': 'You don\'t need to be a professional to make high-quality video content. Viddler provides some quick and easy tips on how to produce great video content with limited resources. ',
|
'description': 'md5:6a697ebd844ff3093bd2e82c37b409cd',
|
||||||
"uploader": "viddler",
|
'uploader': 'viddler',
|
||||||
'timestamp': 1335371429,
|
'timestamp': 1335371429,
|
||||||
'upload_date': '20120425',
|
'upload_date': '20120425',
|
||||||
"duration": 100.89,
|
'duration': 100.89,
|
||||||
'thumbnail': 're:^https?://.*\.jpg$',
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
'view_count': int,
|
'view_count': int,
|
||||||
|
'comment_count': int,
|
||||||
'categories': ['video content', 'high quality video', 'video made easy', 'how to produce video with limited resources', 'viddler'],
|
'categories': ['video content', 'high quality video', 'video made easy', 'how to produce video with limited resources', 'viddler'],
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.viddler.com/v/4d03aad9/',
|
||||||
|
'md5': 'faa71fbf70c0bee7ab93076fd007f4b0',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '4d03aad9',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'WALL-TO-GORTAT',
|
||||||
|
'upload_date': '20150126',
|
||||||
|
'uploader': 'deadspin',
|
||||||
|
'timestamp': 1422285291,
|
||||||
|
'view_count': int,
|
||||||
|
'comment_count': int,
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.viddler.com/player/221ebbbd/0/',
|
||||||
|
'md5': '0defa2bd0ea613d14a6e9bd1db6be326',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '221ebbbd',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'LETeens-Grammar-snack-third-conditional',
|
||||||
|
'description': ' ',
|
||||||
|
'upload_date': '20140929',
|
||||||
|
'uploader': 'BCLETeens',
|
||||||
|
'timestamp': 1411997190,
|
||||||
|
'view_count': int,
|
||||||
|
'comment_count': int,
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
@@ -33,14 +64,17 @@ class ViddlerIE(InfoExtractor):
|
|||||||
json_url = (
|
json_url = (
|
||||||
'http://api.viddler.com/api/v2/viddler.videos.getPlaybackDetails.json?video_id=%s&key=v0vhrt7bg2xq1vyxhkct' %
|
'http://api.viddler.com/api/v2/viddler.videos.getPlaybackDetails.json?video_id=%s&key=v0vhrt7bg2xq1vyxhkct' %
|
||||||
video_id)
|
video_id)
|
||||||
data = self._download_json(json_url, video_id)['video']
|
headers = {'Referer': 'http://static.cdn-ec.viddler.com/js/arpeggio/v2/embed.html'}
|
||||||
|
request = compat_urllib_request.Request(json_url, None, headers)
|
||||||
|
data = self._download_json(request, video_id)['video']
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
for filed in data['files']:
|
for filed in data['files']:
|
||||||
if filed.get('status', 'ready') != 'ready':
|
if filed.get('status', 'ready') != 'ready':
|
||||||
continue
|
continue
|
||||||
|
format_id = filed.get('profile_id') or filed['profile_name']
|
||||||
f = {
|
f = {
|
||||||
'format_id': filed['profile_id'],
|
'format_id': format_id,
|
||||||
'format_note': filed['profile_name'],
|
'format_note': filed['profile_name'],
|
||||||
'url': self._proto_relative_url(filed['url']),
|
'url': self._proto_relative_url(filed['url']),
|
||||||
'width': int_or_none(filed.get('width')),
|
'width': int_or_none(filed.get('width')),
|
||||||
@@ -53,16 +87,15 @@ class ViddlerIE(InfoExtractor):
|
|||||||
|
|
||||||
if filed.get('cdn_url'):
|
if filed.get('cdn_url'):
|
||||||
f = f.copy()
|
f = f.copy()
|
||||||
f['url'] = self._proto_relative_url(filed['cdn_url'])
|
f['url'] = self._proto_relative_url(filed['cdn_url'], 'http:')
|
||||||
f['format_id'] = filed['profile_id'] + '-cdn'
|
f['format_id'] = format_id + '-cdn'
|
||||||
f['source_preference'] = 1
|
f['source_preference'] = 1
|
||||||
formats.append(f)
|
formats.append(f)
|
||||||
|
|
||||||
if filed.get('html5_video_source'):
|
if filed.get('html5_video_source'):
|
||||||
f = f.copy()
|
f = f.copy()
|
||||||
f['url'] = self._proto_relative_url(
|
f['url'] = self._proto_relative_url(filed['html5_video_source'])
|
||||||
filed['html5_video_source'])
|
f['format_id'] = format_id + '-html5'
|
||||||
f['format_id'] = filed['profile_id'] + '-html5'
|
|
||||||
f['source_preference'] = 0
|
f['source_preference'] = 0
|
||||||
formats.append(f)
|
formats.append(f)
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
@@ -71,7 +104,6 @@ class ViddlerIE(InfoExtractor):
|
|||||||
t.get('text') for t in data.get('tags', []) if 'text' in t]
|
t.get('text') for t in data.get('tags', []) if 'text' in t]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'_type': 'video',
|
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': data['title'],
|
'title': data['title'],
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
@@ -81,5 +113,6 @@ class ViddlerIE(InfoExtractor):
|
|||||||
'uploader': data.get('author'),
|
'uploader': data.get('author'),
|
||||||
'duration': float_or_none(data.get('length')),
|
'duration': float_or_none(data.get('length')),
|
||||||
'view_count': int_or_none(data.get('view_count')),
|
'view_count': int_or_none(data.get('view_count')),
|
||||||
|
'comment_count': int_or_none(data.get('comment_count')),
|
||||||
'categories': categories,
|
'categories': categories,
|
||||||
}
|
}
|
||||||
|
@@ -1,11 +1,15 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import (
|
||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
|
compat_urllib_request,
|
||||||
)
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
|
ExtractorError,
|
||||||
remove_start,
|
remove_start,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,34 +20,40 @@ class VideoMegaIE(InfoExtractor):
|
|||||||
(?:iframe\.php)?\?ref=(?P<id>[A-Za-z0-9]+)
|
(?:iframe\.php)?\?ref=(?P<id>[A-Za-z0-9]+)
|
||||||
'''
|
'''
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://videomega.tv/?ref=GKeGPVedBe',
|
'url': 'http://videomega.tv/?ref=QR0HCUHI1661IHUCH0RQ',
|
||||||
'md5': '240fb5bcf9199961f48eb17839b084d6',
|
'md5': 'bf5c2f95c4c917536e80936af7bc51e1',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'GKeGPVedBe',
|
'id': 'QR0HCUHI1661IHUCH0RQ',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'XXL - All Sports United',
|
'title': 'Big Buck Bunny',
|
||||||
'thumbnail': 're:^https?://.*\.jpg$',
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
url = 'http://videomega.tv/iframe.php?ref={0:}'.format(video_id)
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
|
||||||
|
|
||||||
escaped_data = self._search_regex(
|
iframe_url = 'http://videomega.tv/iframe.php?ref={0:}'.format(video_id)
|
||||||
r'unescape\("([^"]+)"\)', webpage, 'escaped data')
|
req = compat_urllib_request.Request(iframe_url)
|
||||||
|
req.add_header('Referer', url)
|
||||||
|
webpage = self._download_webpage(req, video_id)
|
||||||
|
|
||||||
|
try:
|
||||||
|
escaped_data = re.findall(r'unescape\("([^"]+)"\)', webpage)[-1]
|
||||||
|
except IndexError:
|
||||||
|
raise ExtractorError('Unable to extract escaped data')
|
||||||
|
|
||||||
playlist = compat_urllib_parse.unquote(escaped_data)
|
playlist = compat_urllib_parse.unquote(escaped_data)
|
||||||
|
|
||||||
thumbnail = self._search_regex(
|
thumbnail = self._search_regex(
|
||||||
r'image:\s*"([^"]+)"', playlist, 'thumbnail', fatal=False)
|
r'image:\s*"([^"]+)"', playlist, 'thumbnail', fatal=False)
|
||||||
url = self._search_regex(r'file:\s*"([^"]+)"', playlist, 'URL')
|
video_url = self._search_regex(r'file:\s*"([^"]+)"', playlist, 'URL')
|
||||||
title = remove_start(self._html_search_regex(
|
title = remove_start(self._html_search_regex(
|
||||||
r'<title>(.*?)</title>', webpage, 'title'), 'VideoMega.tv - ')
|
r'<title>(.*?)</title>', webpage, 'title'), 'VideoMega.tv - ')
|
||||||
|
|
||||||
formats = [{
|
formats = [{
|
||||||
'format_id': 'sd',
|
'format_id': 'sd',
|
||||||
'url': url,
|
'url': video_url,
|
||||||
}]
|
}]
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
@@ -52,4 +62,7 @@ class VideoMegaIE(InfoExtractor):
|
|||||||
'title': title,
|
'title': title,
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
'thumbnail': thumbnail,
|
'thumbnail': thumbnail,
|
||||||
|
'http_headers': {
|
||||||
|
'Referer': iframe_url,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
@@ -13,9 +13,9 @@ from ..utils import (
|
|||||||
class VideoTtIE(InfoExtractor):
|
class VideoTtIE(InfoExtractor):
|
||||||
ID_NAME = 'video.tt'
|
ID_NAME = 'video.tt'
|
||||||
IE_DESC = 'video.tt - Your True Tube'
|
IE_DESC = 'video.tt - Your True Tube'
|
||||||
_VALID_URL = r'http://(?:www\.)?video\.tt/(?:video/|watch_video\.php\?v=)(?P<id>[\da-zA-Z]{9})'
|
_VALID_URL = r'http://(?:www\.)?video\.tt/(?:(?:video|embed)/|watch_video\.php\?v=)(?P<id>[\da-zA-Z]{9})'
|
||||||
|
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
'url': 'http://www.video.tt/watch_video.php?v=amd5YujV8',
|
'url': 'http://www.video.tt/watch_video.php?v=amd5YujV8',
|
||||||
'md5': 'b13aa9e2f267effb5d1094443dff65ba',
|
'md5': 'b13aa9e2f267effb5d1094443dff65ba',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@@ -26,7 +26,10 @@ class VideoTtIE(InfoExtractor):
|
|||||||
'upload_date': '20130827',
|
'upload_date': '20130827',
|
||||||
'uploader': 'joseph313',
|
'uploader': 'joseph313',
|
||||||
}
|
}
|
||||||
}
|
}, {
|
||||||
|
'url': 'http://video.tt/embed/amd5YujV8',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
@@ -501,9 +501,10 @@ class VimeoReviewIE(InfoExtractor):
|
|||||||
_VALID_URL = r'https?://vimeo\.com/[^/]+/review/(?P<id>[^/]+)'
|
_VALID_URL = r'https?://vimeo\.com/[^/]+/review/(?P<id>[^/]+)'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'https://vimeo.com/user21297594/review/75524534/3c257a1b5d',
|
'url': 'https://vimeo.com/user21297594/review/75524534/3c257a1b5d',
|
||||||
'file': '75524534.mp4',
|
|
||||||
'md5': 'c507a72f780cacc12b2248bb4006d253',
|
'md5': 'c507a72f780cacc12b2248bb4006d253',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'id': '75524534',
|
||||||
|
'ext': 'mp4',
|
||||||
'title': "DICK HARDWICK 'Comedian'",
|
'title': "DICK HARDWICK 'Comedian'",
|
||||||
'uploader': 'Richard Hardwick',
|
'uploader': 'Richard Hardwick',
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
# coding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
@@ -11,9 +12,10 @@ from ..utils import (
|
|||||||
|
|
||||||
class WashingtonPostIE(InfoExtractor):
|
class WashingtonPostIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?washingtonpost\.com/.*?/(?P<id>[^/]+)/(?:$|[?#])'
|
_VALID_URL = r'https?://(?:www\.)?washingtonpost\.com/.*?/(?P<id>[^/]+)/(?:$|[?#])'
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
'url': 'http://www.washingtonpost.com/sf/national/2014/03/22/sinkhole-of-bureaucracy/',
|
'url': 'http://www.washingtonpost.com/sf/national/2014/03/22/sinkhole-of-bureaucracy/',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'id': 'sinkhole-of-bureaucracy',
|
||||||
'title': 'Sinkhole of bureaucracy',
|
'title': 'Sinkhole of bureaucracy',
|
||||||
},
|
},
|
||||||
'playlist': [{
|
'playlist': [{
|
||||||
@@ -40,15 +42,38 @@ class WashingtonPostIE(InfoExtractor):
|
|||||||
'upload_date': '20140322',
|
'upload_date': '20140322',
|
||||||
'uploader': 'The Washington Post',
|
'uploader': 'The Washington Post',
|
||||||
},
|
},
|
||||||
}]
|
}],
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.washingtonpost.com/blogs/wonkblog/wp/2014/12/31/one-airline-figured-out-how-to-make-sure-its-airplanes-never-disappear/',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'one-airline-figured-out-how-to-make-sure-its-airplanes-never-disappear',
|
||||||
|
'title': 'One airline figured out how to make sure its airplanes never disappear',
|
||||||
|
},
|
||||||
|
'playlist': [{
|
||||||
|
'md5': 'a7c1b5634ba5e57a6a82cdffa5b1e0d0',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '0e4bb54c-9065-11e4-a66f-0ca5037a597d',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'description': 'Washington Post transportation reporter Ashley Halsey III explains why a plane\'s black box needs to be recovered from a crash site instead of having its information streamed in real time throughout the flight.',
|
||||||
|
'upload_date': '20141230',
|
||||||
|
'uploader': 'The Washington Post',
|
||||||
|
'timestamp': 1419974765,
|
||||||
|
'title': 'Why black boxes don’t transmit data in real time',
|
||||||
}
|
}
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
page_id = self._match_id(url)
|
page_id = self._match_id(url)
|
||||||
webpage = self._download_webpage(url, page_id)
|
webpage = self._download_webpage(url, page_id)
|
||||||
|
|
||||||
title = self._og_search_title(webpage)
|
title = self._og_search_title(webpage)
|
||||||
uuids = re.findall(r'data-video-uuid="([^"]+)"', webpage)
|
|
||||||
|
uuids = re.findall(r'''(?x)
|
||||||
|
(?:
|
||||||
|
<div\s+class="posttv-video-embed[^>]*?data-uuid=|
|
||||||
|
data-video-uuid=
|
||||||
|
)"([^"]+)"''', webpage)
|
||||||
entries = []
|
entries = []
|
||||||
for i, uuid in enumerate(uuids, start=1):
|
for i, uuid in enumerate(uuids, start=1):
|
||||||
vinfo_all = self._download_json(
|
vinfo_all = self._download_json(
|
||||||
@@ -75,10 +100,11 @@ class WashingtonPostIE(InfoExtractor):
|
|||||||
'filesize': s.get('fileSize'),
|
'filesize': s.get('fileSize'),
|
||||||
'url': s.get('url'),
|
'url': s.get('url'),
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
|
'preference': -100 if s.get('type') == 'smil' else None,
|
||||||
'protocol': {
|
'protocol': {
|
||||||
'MP4': 'http',
|
'MP4': 'http',
|
||||||
'F4F': 'f4m',
|
'F4F': 'f4m',
|
||||||
}.get(s.get('type'))
|
}.get(s.get('type')),
|
||||||
} for s in vinfo.get('streams', [])]
|
} for s in vinfo.get('streams', [])]
|
||||||
source_media_url = vinfo.get('sourceMediaURL')
|
source_media_url = vinfo.get('sourceMediaURL')
|
||||||
if source_media_url:
|
if source_media_url:
|
||||||
|
@@ -71,6 +71,9 @@ class WDRIE(InfoExtractor):
|
|||||||
{
|
{
|
||||||
'url': 'http://www1.wdr.de/mediathek/video/sendungen/quarks_und_co/filterseite-quarks-und-co100.html',
|
'url': 'http://www1.wdr.de/mediathek/video/sendungen/quarks_und_co/filterseite-quarks-und-co100.html',
|
||||||
'playlist_mincount': 146,
|
'playlist_mincount': 146,
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'mediathek/video/sendungen/quarks_und_co/filterseite-quarks-und-co100',
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -169,7 +172,9 @@ class WDRMobileIE(InfoExtractor):
|
|||||||
'title': mobj.group('title'),
|
'title': mobj.group('title'),
|
||||||
'age_limit': int(mobj.group('age_limit')),
|
'age_limit': int(mobj.group('age_limit')),
|
||||||
'url': url,
|
'url': url,
|
||||||
'user_agent': 'mobile',
|
'http_headers': {
|
||||||
|
'User-Agent': 'mobile',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -30,7 +30,7 @@ class XboxClipsIE(InfoExtractor):
|
|||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
video_url = self._html_search_regex(
|
video_url = self._html_search_regex(
|
||||||
r'>(?:Link|Download): <a href="([^"]+)">', webpage, 'video URL')
|
r'>(?:Link|Download): <a[^>]+href="([^"]+)"', webpage, 'video URL')
|
||||||
title = self._html_search_regex(
|
title = self._html_search_regex(
|
||||||
r'<title>XboxClips \| ([^<]+)</title>', webpage, 'title')
|
r'<title>XboxClips \| ([^<]+)</title>', webpage, 'title')
|
||||||
upload_date = unified_strdate(self._html_search_regex(
|
upload_date = unified_strdate(self._html_search_regex(
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user