Compare commits
435 Commits
2014.03.27
...
2014.06.02
Author | SHA1 | Date | |
---|---|---|---|
b7e8b6e37a | |||
ceb7a17f34 | |||
1a2f2e1e66 | |||
6803016858 | |||
9b7c4fd981 | |||
dc31942f42 | |||
1f6b8f3115 | |||
9168308579 | |||
7e8fdb1aae | |||
386ba39cac | |||
236d0cd07c | |||
ed86f38a11 | |||
6db80ad2db | |||
6ebb46c106 | |||
0f97c9a06f | |||
77fb72646f | |||
aae74e3832 | |||
894e730911 | |||
63961d87a6 | |||
87fe568c28 | |||
46531b374d | |||
9e8753911c | |||
5c6b1e578c | |||
8f0c8fb452 | |||
b702ecebf0 | |||
950dc95e97 | |||
d9dd3584e1 | |||
15a9f36849 | |||
d0087d4ff2 | |||
cc5ada6f4c | |||
dfb2e1a325 | |||
65bab327b4 | |||
9eeb7abc6b | |||
c70df21099 | |||
418424e5f5 | |||
8477466125 | |||
865dbd4a26 | |||
b1e6f55912 | |||
4d78f3b770 | |||
7f739999e9 | |||
0f8a01d4f3 | |||
e2bf499b14 | |||
7cf4547ab6 | |||
eec4d8ef96 | |||
1c783bca88 | |||
ac73651f66 | |||
e5ceb3bfda | |||
c2ef29234c | |||
1a1826c1af | |||
c7c6d43fe1 | |||
2902d44f99 | |||
d6e4ba287b | |||
f50ee8d1c3 | |||
0e67ab0d8e | |||
77541837e5 | |||
e3a6576f35 | |||
89bb8e97ee | |||
375696b1b1 | |||
4ea5c7b70d | |||
8dfa187b8a | |||
c1ed1f7055 | |||
1514f74967 | |||
2e8323e3f7 | |||
69f8364042 | |||
79981f039b | |||
91994c2c81 | |||
76e92371ac | |||
08af0205f9 | |||
a725fb1f43 | |||
05ee2b6dad | |||
b74feacac5 | |||
426b52fc5d | |||
5c30b26846 | |||
f07b74fc18 | |||
a5a45015ba | |||
beee53de06 | |||
8712f2bea7 | |||
ea102818c9 | |||
0a871f6880 | |||
481efc84a8 | |||
01ed5c9be3 | |||
ad3bc6acd5 | |||
5afa7f8bee | |||
ec8deefc27 | |||
a2d5a4ee64 | |||
dffcc2ea0c | |||
1800eeefed | |||
d7e7dedbde | |||
d19bb9c0aa | |||
3ef79a974a | |||
bc6800fbed | |||
65314dccf8 | |||
feb7221209 | |||
56a94d8cbb | |||
24e6ec8ac8 | |||
87724af7a8 | |||
b65c3e77e8 | |||
5301304bf2 | |||
948bcc60df | |||
25dfe0eb10 | |||
8e71456a81 | |||
ccdd34ed78 | |||
26d886354f | |||
a172b258ac | |||
7b93c2c204 | |||
57c7411f46 | |||
d0a122348e | |||
e4cbb5f382 | |||
c1bce22f23 | |||
e3abbbe301 | |||
55b36e3710 | |||
877bea9ce1 | |||
33c7ff861e | |||
749fe60c1e | |||
63b31b059c | |||
1476b497eb | |||
e399853d0c | |||
fdb205b19e | |||
fbe8053120 | |||
ea783d01e1 | |||
b7d73595dc | |||
e97e53eeed | |||
342f630dbf | |||
69c8fb9e5d | |||
5f0f8013ac | |||
b5368acee8 | |||
f71959fcf5 | |||
5c9f3b8b16 | |||
bebd6f9308 | |||
84a2806c16 | |||
d0111a7409 | |||
aab8874c55 | |||
fcf5b01746 | |||
4de9e9a6db | |||
0067d6c4be | |||
2099125333 | |||
b48f147d5a | |||
4f3e943080 | |||
7558830fa3 | |||
867274e997 | |||
6515778305 | |||
3b1dfc0f2f | |||
d664de44b7 | |||
bbe99d26ec | |||
50fc59968e | |||
b8b01bb92a | |||
eb45133451 | |||
10c0e2d818 | |||
669f0e7cda | |||
32fd27ec98 | |||
0c13f378de | |||
0049594efb | |||
113c7d3eb0 | |||
549371fc99 | |||
957f27e5bb | |||
1f8c19767b | |||
a383a98af6 | |||
acd69589a5 | |||
b30b8698ea | |||
f1f25be6db | |||
deab8c1960 | |||
c57f775710 | |||
e75cafe9fb | |||
33ab8453c4 | |||
ebd3c7b370 | |||
29645a1d44 | |||
22d99a801a | |||
57b8d84cd9 | |||
65e4ad5bfe | |||
98b7d476d9 | |||
201e3c99b9 | |||
8a7a4a9796 | |||
df297c8794 | |||
3f53a75f02 | |||
7c360e3a04 | |||
d2176c8011 | |||
aa92f06308 | |||
e00c9cf599 | |||
ba60a3ebe0 | |||
efb7e11988 | |||
a55c8b7aac | |||
a980bc4324 | |||
4b10aadffc | |||
5bec574859 | |||
d11271dd29 | |||
1d9d26d09b | |||
c0292e8ab7 | |||
f44e5d8b43 | |||
6ea74538e3 | |||
24b8924b46 | |||
86a3c67112 | |||
8be874370d | |||
aec74dd95a | |||
6890574256 | |||
d03745c684 | |||
28746fbd59 | |||
0321213c11 | |||
3f0aae4244 | |||
48099643cc | |||
621f33c9d0 | |||
f07a9f6f43 | |||
e51880fd32 | |||
88ce273da4 | |||
b9ba5dfa28 | |||
4086f11929 | |||
478c2c6193 | |||
d2d6481afb | |||
43acb120f3 | |||
e8f2025edf | |||
a4eb9578af | |||
fa35cdad02 | |||
d1b9c912a4 | |||
edec83a025 | |||
c0a7c60815 | |||
117a7d1944 | |||
a40e0dd434 | |||
188b086dd9 | |||
1f27d2c0e1 | |||
7560096db5 | |||
282cb9c7ba | |||
3a9d6790ad | |||
0610a3e0b2 | |||
7f9c31df88 | |||
3fa6b6e293 | |||
3c50b99ab4 | |||
52fadd5fb2 | |||
5367fe7f4d | |||
427588f6e7 | |||
51745be312 | |||
d7f1e7c88f | |||
4145a257be | |||
525dc9809e | |||
1bf3210816 | |||
e6c6d10d99 | |||
f270256e06 | |||
f401c6f69f | |||
b075d25bed | |||
3d1bb6b4dd | |||
1db2666916 | |||
8f5c0218d8 | |||
d7666dff82 | |||
2d4c98dbd1 | |||
fd50bf623c | |||
d360a14678 | |||
d0f2ab6969 | |||
de906ef543 | |||
2fb3deeca1 | |||
66398056f1 | |||
77477fa4c9 | |||
a169e18ce1 | |||
381640e3ac | |||
37e3410137 | |||
97b5196960 | |||
6a4f3528c8 | |||
b9c76aa1a9 | |||
0d3070d364 | |||
7753cadbfa | |||
3950450342 | |||
c82b1fdad6 | |||
b0fb63abe8 | |||
3ab34c603e | |||
7d6413341a | |||
140012d0f6 | |||
4be9f8c814 | |||
5c802bac37 | |||
6c30ff756a | |||
62749e4708 | |||
6b7dee4b38 | |||
ef2041eb4e | |||
29e3e682af | |||
f983c44199 | |||
e4db19511a | |||
c47d21da80 | |||
269aecd0c0 | |||
aafddb2b0a | |||
6262ac8ac5 | |||
89938c719e | |||
ec0fafbb19 | |||
a5863bdf33 | |||
b58ddb32ba | |||
b9e12a8140 | |||
104aa7388a | |||
c3855d28b0 | |||
734f90bb41 | |||
91a6addeeb | |||
9afb76c5ad | |||
dfb2cb5cfd | |||
650d688d10 | |||
0ba77818f3 | |||
09baa7da7e | |||
85e787f51d | |||
2a9e1e453a | |||
ee1e199685 | |||
17c5a00774 | |||
15c0e8e7b2 | |||
cca37fba48 | |||
9d0993ec4a | |||
342f33bf9e | |||
7cd3bc5f99 | |||
931055e6cb | |||
d0e4cf82f1 | |||
6f88df2c57 | |||
4479bf2762 | |||
1ff7c0f7d8 | |||
610e47c87e | |||
50f566076f | |||
92810ff497 | |||
60ccc59a1c | |||
91745595d3 | |||
d6e40507d0 | |||
deed48b472 | |||
e4d41bfca5 | |||
a355b70f27 | |||
f8514f6186 | |||
e09b8fcd9d | |||
7d1b527ff9 | |||
f943c7b622 | |||
676eb3f2dd | |||
98b7cf1ace | |||
c465afd736 | |||
b84d6e7fc4 | |||
2efd5d78c1 | |||
c8edf47b3a | |||
3b4c26a428 | |||
1525148114 | |||
9e0c5791c1 | |||
29a1ab2afc | |||
fa387d2d99 | |||
6d0d573eca | |||
bb799e811b | |||
04ee53eca1 | |||
659eb98a53 | |||
ca6aada48e | |||
43df5a7e71 | |||
88f1c6de7b | |||
65a40ab82b | |||
4b9cced103 | |||
5c38625259 | |||
6344fa04bb | |||
e3ced9ed61 | |||
5075d598bc | |||
68eb8e90e6 | |||
d3a96346c4 | |||
0e518e2fea | |||
1e0a235f39 | |||
9ad400f75e | |||
3537b93d8a | |||
56eca2e956 | |||
2ad4d1ba07 | |||
4853de808b | |||
6ff5f12218 | |||
52a180684f | |||
b21e25702f | |||
983af2600f | |||
f34e6a2cd6 | |||
a9f304031b | |||
9271bc8355 | |||
d1b3e3dd75 | |||
968ed2a777 | |||
24de5d2556 | |||
d26e981df4 | |||
e45d40b171 | |||
4a419b8851 | |||
5fbd672c38 | |||
bec1fad223 | |||
177fed41bc | |||
b900e7cba4 | |||
14cb4979f0 | |||
69e61e30fe | |||
cce929eaac | |||
b6cfde99b7 | |||
1be99f052d | |||
2410c43d83 | |||
aea6e7fc3c | |||
91a76c40c0 | |||
d2b194607c | |||
f6177462db | |||
9ddaf4ef8c | |||
97b5573848 | |||
18c95c1ab0 | |||
0479c625a4 | |||
f659951e22 | |||
5853a7316e | |||
a612753db9 | |||
c8fc3fb524 | |||
5912c639df | |||
017e4dd58c | |||
651486621d | |||
28d9032c88 | |||
16f4eb723a | |||
1cbd410620 | |||
d41ac5f5dc | |||
9c1fc022ae | |||
83d548ef0f | |||
c72477bd32 | |||
9a7b072e38 | |||
cbc4a6cc7e | |||
cd7481a39e | |||
acd213ed6d | |||
77ffa95701 | |||
2b25cb5d76 | |||
62fec3b2ff | |||
e79162558e | |||
2da67107ee | |||
2ff7f8975e | |||
87a2566048 | |||
986f56736b | |||
2583a0308b | |||
40c716d2a2 | |||
79bfd01001 | |||
f2bcdd8e02 | |||
8c5850eeb4 | |||
bd3e077a2d | |||
7e70ac36b3 | |||
2cc0082dc0 | |||
056b56688a | |||
b17418313f | |||
e9a6fd6a68 | |||
bf30f3bd9d | |||
330edf2d84 | |||
43f775e4ca | |||
8f6562448c | |||
263f4b514b | |||
f0da3f1ef9 | |||
cb3ac1c610 | |||
8efd15f477 | |||
d26ebe990f | |||
28acf5500a | |||
214c22c704 | |||
8cdafb47b9 | |||
0dae5083f1 | |||
784763c565 | |||
39c68260c0 | |||
149254d0d5 | |||
0c14e2fbe3 |
@ -3,6 +3,7 @@ python:
|
|||||||
- "2.6"
|
- "2.6"
|
||||||
- "2.7"
|
- "2.7"
|
||||||
- "3.3"
|
- "3.3"
|
||||||
|
- "3.4"
|
||||||
script: nosetests test --verbose
|
script: nosetests test --verbose
|
||||||
notifications:
|
notifications:
|
||||||
email:
|
email:
|
||||||
|
14
CHANGELOG
14
CHANGELOG
@ -1,14 +0,0 @@
|
|||||||
2013.01.02 Codename: GIULIA
|
|
||||||
|
|
||||||
* Add support for ComedyCentral clips <nto>
|
|
||||||
* Corrected Vimeo description fetching <Nick Daniels>
|
|
||||||
* Added the --no-post-overwrites argument <Barbu Paul - Gheorghe>
|
|
||||||
* --verbose offers more environment info
|
|
||||||
* New info_dict field: uploader_id
|
|
||||||
* New updates system, with signature checking
|
|
||||||
* New IEs: NBA, JustinTV, FunnyOrDie, TweetReel, Steam, Ustream
|
|
||||||
* Fixed IEs: BlipTv
|
|
||||||
* Fixed for Python 3 IEs: Xvideo, Youku, XNXX, Dailymotion, Vimeo, InfoQ
|
|
||||||
* Simplified IEs and test code
|
|
||||||
* Various (Python 3 and other) fixes
|
|
||||||
* Revamped and expanded tests
|
|
@ -3,5 +3,4 @@ include test/*.py
|
|||||||
include test/*.json
|
include test/*.json
|
||||||
include youtube-dl.bash-completion
|
include youtube-dl.bash-completion
|
||||||
include youtube-dl.1
|
include youtube-dl.1
|
||||||
recursive-include docs *
|
recursive-include docs Makefile conf.py *.rst
|
||||||
prune docs/_build
|
|
||||||
|
8
Makefile
8
Makefile
@ -1,7 +1,7 @@
|
|||||||
all: youtube-dl README.md README.txt youtube-dl.1 youtube-dl.bash-completion
|
all: youtube-dl README.md README.txt youtube-dl.1 youtube-dl.bash-completion
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf youtube-dl.1 youtube-dl.bash-completion README.txt MANIFEST build/ dist/ .coverage cover/ youtube-dl.tar.gz
|
rm -rf youtube-dl.1.temp.md youtube-dl.1 youtube-dl.bash-completion README.txt MANIFEST build/ dist/ .coverage cover/ youtube-dl.tar.gz
|
||||||
|
|
||||||
cleanall: clean
|
cleanall: clean
|
||||||
rm -f youtube-dl youtube-dl.exe
|
rm -f youtube-dl youtube-dl.exe
|
||||||
@ -55,7 +55,9 @@ README.txt: README.md
|
|||||||
pandoc -f markdown -t plain README.md -o README.txt
|
pandoc -f markdown -t plain README.md -o README.txt
|
||||||
|
|
||||||
youtube-dl.1: README.md
|
youtube-dl.1: README.md
|
||||||
pandoc -s -f markdown -t man README.md -o youtube-dl.1
|
python devscripts/prepare_manpage.py >youtube-dl.1.temp.md
|
||||||
|
pandoc -s -f markdown -t man youtube-dl.1.temp.md -o youtube-dl.1
|
||||||
|
rm -f youtube-dl.1.temp.md
|
||||||
|
|
||||||
youtube-dl.bash-completion: youtube_dl/*.py youtube_dl/*/*.py devscripts/bash-completion.in
|
youtube-dl.bash-completion: youtube_dl/*.py youtube_dl/*/*.py devscripts/bash-completion.in
|
||||||
python devscripts/bash-completion.py
|
python devscripts/bash-completion.py
|
||||||
@ -75,6 +77,6 @@ youtube-dl.tar.gz: youtube-dl README.md README.txt youtube-dl.1 youtube-dl.bash-
|
|||||||
--exclude 'docs/_build' \
|
--exclude 'docs/_build' \
|
||||||
-- \
|
-- \
|
||||||
bin devscripts test youtube_dl docs \
|
bin devscripts test youtube_dl docs \
|
||||||
CHANGELOG LICENSE README.md README.txt \
|
LICENSE README.md README.txt \
|
||||||
Makefile MANIFEST.in youtube-dl.1 youtube-dl.bash-completion setup.py \
|
Makefile MANIFEST.in youtube-dl.1 youtube-dl.bash-completion setup.py \
|
||||||
youtube-dl
|
youtube-dl
|
||||||
|
89
README.md
89
README.md
@ -1,11 +1,24 @@
|
|||||||
% YOUTUBE-DL(1)
|
|
||||||
|
|
||||||
# NAME
|
|
||||||
youtube-dl - download videos from youtube.com or other video platforms
|
youtube-dl - download videos from youtube.com or other video platforms
|
||||||
|
|
||||||
# SYNOPSIS
|
# SYNOPSIS
|
||||||
**youtube-dl** [OPTIONS] URL [URL...]
|
**youtube-dl** [OPTIONS] URL [URL...]
|
||||||
|
|
||||||
|
# INSTALLATION
|
||||||
|
|
||||||
|
To install it right away for all UNIX users (Linux, OS X, etc.), type:
|
||||||
|
|
||||||
|
sudo curl https://yt-dl.org/latest/youtube-dl -o /usr/local/bin/youtube-dl
|
||||||
|
sudo chmod a+x /usr/local/bin/youtube-dl
|
||||||
|
|
||||||
|
If you do not have curl, you can alternatively use a recent wget:
|
||||||
|
|
||||||
|
sudo wget https://yt-dl.org/downloads/2014.05.13/youtube-dl -O /usr/local/bin/youtube-dl
|
||||||
|
sudo chmod a+x /usr/local/bin/youtube-dl
|
||||||
|
|
||||||
|
Windows users can [download a .exe file](https://yt-dl.org/latest/youtube-dl.exe) and place it in their home directory or any other location on their [PATH](http://en.wikipedia.org/wiki/PATH_%28variable%29).
|
||||||
|
|
||||||
|
Alternatively, refer to the developer instructions below for how to check out and work with the git repository. For further options, including PGP signatures, see https://rg3.github.io/youtube-dl/download.html .
|
||||||
|
|
||||||
# DESCRIPTION
|
# DESCRIPTION
|
||||||
**youtube-dl** is a small command-line program to download videos from
|
**youtube-dl** is a small command-line program to download videos from
|
||||||
YouTube.com and a few more sites. It requires the Python interpreter, version
|
YouTube.com and a few more sites. It requires the Python interpreter, version
|
||||||
@ -65,6 +78,7 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
configuration in ~/.config/youtube-dl.conf
|
configuration in ~/.config/youtube-dl.conf
|
||||||
(%APPDATA%/youtube-dl/config.txt on
|
(%APPDATA%/youtube-dl/config.txt on
|
||||||
Windows)
|
Windows)
|
||||||
|
--encoding ENCODING Force the specified encoding (experimental)
|
||||||
|
|
||||||
## Video Selection:
|
## Video Selection:
|
||||||
--playlist-start NUMBER playlist video to start at (default is 1)
|
--playlist-start NUMBER playlist video to start at (default is 1)
|
||||||
@ -181,7 +195,9 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
--get-duration simulate, quiet but print video length
|
--get-duration simulate, quiet but print video length
|
||||||
--get-filename simulate, quiet but print output filename
|
--get-filename simulate, quiet but print output filename
|
||||||
--get-format simulate, quiet but print output format
|
--get-format simulate, quiet but print output format
|
||||||
-j, --dump-json simulate, quiet but print JSON information
|
-j, --dump-json simulate, quiet but print JSON information.
|
||||||
|
See --output for a description of available
|
||||||
|
keys.
|
||||||
--newline output progress bar as new lines
|
--newline output progress bar as new lines
|
||||||
--no-progress do not print progress bar
|
--no-progress do not print progress bar
|
||||||
--console-title display progress in console titlebar
|
--console-title display progress in console titlebar
|
||||||
@ -247,6 +263,7 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
default
|
default
|
||||||
--embed-subs embed subtitles in the video (only for mp4
|
--embed-subs embed subtitles in the video (only for mp4
|
||||||
videos)
|
videos)
|
||||||
|
--embed-thumbnail embed thumbnail in the audio as cover art
|
||||||
--add-metadata write metadata to the video file
|
--add-metadata write metadata to the video file
|
||||||
--xattrs write metadata to the video file's xattrs
|
--xattrs write metadata to the video file's xattrs
|
||||||
(using dublin core and xdg standards)
|
(using dublin core and xdg standards)
|
||||||
@ -368,7 +385,67 @@ If you want to create a build of youtube-dl yourself, you'll need
|
|||||||
|
|
||||||
### Adding support for a new site
|
### Adding support for a new site
|
||||||
|
|
||||||
If you want to add support for a new site, copy *any* [recently modified](https://github.com/rg3/youtube-dl/commits/master/youtube_dl/extractor) file in `youtube_dl/extractor`, add an import in [`youtube_dl/extractor/__init__.py`](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/__init__.py). 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). Don't forget to run the tests with `python test/test_download.py TestDownload.test_YourExtractor`! For a detailed tutorial, refer to [this blog post](http://filippo.io/add-support-for-a-new-video-site-to-youtube-dl/).
|
If you want to add support for a new site, you can follow this quick list (assuming your service is called `yourextractor`):
|
||||||
|
|
||||||
|
1. [Fork this repository](https://github.com/rg3/youtube-dl/fork)
|
||||||
|
2. Check out the source code with `git clone git@github.com:YOUR_GITHUB_USERNAME/youtube-dl.git`
|
||||||
|
3. Start a new git branch with `cd youtube-dl; git checkout -b yourextractor`
|
||||||
|
4. Start with this simple template and save it to `youtube_dl/extractor/yourextractor.py`:
|
||||||
|
|
||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
|
class YourExtractorIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?yourextractor\.com/watch/(?P<id>[0-9]+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://yourextractor.com/watch/42',
|
||||||
|
'md5': 'TODO: md5 sum of the first 10KiB of the video file',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '42',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Video title goes here',
|
||||||
|
# TODO more properties, either as:
|
||||||
|
# * A value
|
||||||
|
# * MD5 checksum; start the string with md5:
|
||||||
|
# * A regular expression; start the string with re:
|
||||||
|
# * Any Python type (for example int or float)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
# TODO more code goes here, for example ...
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
title = self._html_search_regex(r'<h1>(.*?)</h1>', webpage, 'title')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
# TODO more properties (see youtube_dl/extractor/common.py)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
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).
|
||||||
|
9. When the tests pass, [add](https://www.kernel.org/pub/software/scm/git/docs/git-add.html) the new files and [commit](https://www.kernel.org/pub/software/scm/git/docs/git-commit.html) them and [push](https://www.kernel.org/pub/software/scm/git/docs/git-push.html) the result, like this:
|
||||||
|
|
||||||
|
$ git add youtube_dl/extractor/__init__.py
|
||||||
|
$ git add youtube_dl/extractor/yourextractor.py
|
||||||
|
$ git commit -m '[yourextractor] Add new extractor'
|
||||||
|
$ git push origin yourextractor
|
||||||
|
|
||||||
|
10. Finally, [create a pull request](https://help.github.com/articles/creating-a-pull-request). We'll then review and merge it.
|
||||||
|
|
||||||
|
In any case, thank you very much for your contributions!
|
||||||
|
|
||||||
# BUGS
|
# BUGS
|
||||||
|
|
||||||
@ -394,7 +471,7 @@ If your report is shorter than two lines, it is almost certainly missing some of
|
|||||||
|
|
||||||
For bug reports, this means that your report should contain the *complete* output of youtube-dl when called with the -v flag. The error message you get for (most) bugs even says so, but you would not believe how many of our bug reports do not contain this information.
|
For bug reports, this means that your report should contain the *complete* output of youtube-dl when called with the -v flag. The error message you get for (most) bugs even says so, but you would not believe how many of our bug reports do not contain this information.
|
||||||
|
|
||||||
Site support requests must contain an example URL. An example URL is a URL you might want to download, like http://www.youtube.com/watch?v=BaW_jenozKc . There should be an obvious video present. Except under very special circumstances, the main page of a video service (e.g. http://www.youtube.com/ ) is *not* an example URL.
|
Site support requests **must contain an example URL**. An example URL is a URL you might want to download, like http://www.youtube.com/watch?v=BaW_jenozKc . There should be an obvious video present. Except under very special circumstances, the main page of a video service (e.g. http://www.youtube.com/ ) is *not* an example URL.
|
||||||
|
|
||||||
### Are you using the latest version?
|
### Are you using the latest version?
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ header = oldreadme[:oldreadme.index('# OPTIONS')]
|
|||||||
footer = oldreadme[oldreadme.index('# CONFIGURATION'):]
|
footer = oldreadme[oldreadme.index('# CONFIGURATION'):]
|
||||||
|
|
||||||
options = helptext[helptext.index(' General Options:') + 19:]
|
options = helptext[helptext.index(' General Options:') + 19:]
|
||||||
options = re.sub(r'^ (\w.+)$', r'## \1', options, flags=re.M)
|
options = re.sub(r'(?m)^ (\w.+)$', r'## \1', options)
|
||||||
options = '# OPTIONS\n' + options + '\n'
|
options = '# OPTIONS\n' + options + '\n'
|
||||||
|
|
||||||
with io.open(README_FILE, 'w', encoding='utf-8') as f:
|
with io.open(README_FILE, 'w', encoding='utf-8') as f:
|
||||||
|
20
devscripts/prepare_manpage.py
Normal file
20
devscripts/prepare_manpage.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
|
||||||
|
import io
|
||||||
|
import os.path
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
|
||||||
|
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
README_FILE = os.path.join(ROOT_DIR, 'README.md')
|
||||||
|
|
||||||
|
with io.open(README_FILE, encoding='utf-8') as f:
|
||||||
|
readme = f.read()
|
||||||
|
|
||||||
|
PREFIX = '%YOUTUBE-DL(1)\n\n# NAME\n'
|
||||||
|
readme = re.sub(r'(?s)# INSTALLATION.*?(?=# DESCRIPTION)', '', readme)
|
||||||
|
readme = PREFIX + readme
|
||||||
|
|
||||||
|
if sys.version_info < (3, 0):
|
||||||
|
print(readme.encode('utf-8'))
|
||||||
|
else:
|
||||||
|
print(readme)
|
@ -45,9 +45,9 @@ fi
|
|||||||
/bin/echo -e "\n### Changing version in version.py..."
|
/bin/echo -e "\n### Changing version in version.py..."
|
||||||
sed -i "s/__version__ = '.*'/__version__ = '$version'/" youtube_dl/version.py
|
sed -i "s/__version__ = '.*'/__version__ = '$version'/" youtube_dl/version.py
|
||||||
|
|
||||||
/bin/echo -e "\n### Committing CHANGELOG README.md and youtube_dl/version.py..."
|
/bin/echo -e "\n### Committing README.md and youtube_dl/version.py..."
|
||||||
make README.md
|
make README.md
|
||||||
git add CHANGELOG README.md youtube_dl/version.py
|
git add README.md youtube_dl/version.py
|
||||||
git commit -m "release $version"
|
git commit -m "release $version"
|
||||||
|
|
||||||
/bin/echo -e "\n### Now tagging, signing and pushing..."
|
/bin/echo -e "\n### Now tagging, signing and pushing..."
|
||||||
|
@ -74,13 +74,19 @@ class FakeYDL(YoutubeDL):
|
|||||||
old_report_warning(message)
|
old_report_warning(message)
|
||||||
self.report_warning = types.MethodType(report_warning, self)
|
self.report_warning = types.MethodType(report_warning, self)
|
||||||
|
|
||||||
def gettestcases():
|
|
||||||
|
def gettestcases(include_onlymatching=False):
|
||||||
for ie in youtube_dl.extractor.gen_extractors():
|
for ie in youtube_dl.extractor.gen_extractors():
|
||||||
t = getattr(ie, '_TEST', None)
|
t = getattr(ie, '_TEST', None)
|
||||||
if t:
|
if t:
|
||||||
t['name'] = type(ie).__name__[:-len('IE')]
|
assert not hasattr(ie, '_TESTS'), \
|
||||||
yield t
|
'%s has _TEST and _TESTS' % type(ie).__name__
|
||||||
for t in getattr(ie, '_TESTS', []):
|
tests = [t]
|
||||||
|
else:
|
||||||
|
tests = getattr(ie, '_TESTS', [])
|
||||||
|
for t in tests:
|
||||||
|
if not include_onlymatching and t.get('only_matching', False):
|
||||||
|
continue
|
||||||
t['name'] = type(ie).__name__[:-len('IE')]
|
t['name'] = type(ie).__name__[:-len('IE')]
|
||||||
yield t
|
yield t
|
||||||
|
|
||||||
@ -101,7 +107,7 @@ def expect_info_dict(self, expected_dict, got_dict):
|
|||||||
elif isinstance(expected, type):
|
elif isinstance(expected, type):
|
||||||
got = got_dict.get(info_field)
|
got = got_dict.get(info_field)
|
||||||
self.assertTrue(isinstance(got, expected),
|
self.assertTrue(isinstance(got, expected),
|
||||||
u'Expected type %r, but got value %r of type %r' % (expected, got, type(got)))
|
u'Expected type %r for field %s, but got value %r of type %r' % (expected, info_field, got, type(got)))
|
||||||
else:
|
else:
|
||||||
if isinstance(expected, compat_str) and expected.startswith('md5:'):
|
if isinstance(expected, compat_str) and expected.startswith('md5:'):
|
||||||
got = 'md5:' + md5(got_dict.get(info_field))
|
got = 'md5:' + md5(got_dict.get(info_field))
|
||||||
@ -128,3 +134,17 @@ def expect_info_dict(self, expected_dict, got_dict):
|
|||||||
missing_keys,
|
missing_keys,
|
||||||
'Missing keys in test definition: %s' % (
|
'Missing keys in test definition: %s' % (
|
||||||
', '.join(sorted(missing_keys))))
|
', '.join(sorted(missing_keys))))
|
||||||
|
|
||||||
|
|
||||||
|
def assertRegexpMatches(self, text, regexp, msg=None):
|
||||||
|
if hasattr(self, 'assertRegexpMatches'):
|
||||||
|
return self.assertRegexpMatches(text, regexp, msg)
|
||||||
|
else:
|
||||||
|
m = re.match(regexp, text)
|
||||||
|
if not m:
|
||||||
|
note = 'Regexp didn\'t match: %r not found in %r' % (regexp, text)
|
||||||
|
if msg is None:
|
||||||
|
msg = note
|
||||||
|
else:
|
||||||
|
msg = note + ', ' + msg
|
||||||
|
self.assertTrue(m, msg)
|
||||||
|
@ -8,7 +8,7 @@ import sys
|
|||||||
import unittest
|
import unittest
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
from test.helper import FakeYDL
|
from test.helper import FakeYDL, assertRegexpMatches
|
||||||
from youtube_dl import YoutubeDL
|
from youtube_dl import YoutubeDL
|
||||||
from youtube_dl.extractor import YoutubeIE
|
from youtube_dl.extractor import YoutubeIE
|
||||||
|
|
||||||
@ -26,16 +26,27 @@ class YDL(FakeYDL):
|
|||||||
self.msgs.append(msg)
|
self.msgs.append(msg)
|
||||||
|
|
||||||
|
|
||||||
|
def _make_result(formats, **kwargs):
|
||||||
|
res = {
|
||||||
|
'formats': formats,
|
||||||
|
'id': 'testid',
|
||||||
|
'title': 'testttitle',
|
||||||
|
'extractor': 'testex',
|
||||||
|
}
|
||||||
|
res.update(**kwargs)
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
class TestFormatSelection(unittest.TestCase):
|
class TestFormatSelection(unittest.TestCase):
|
||||||
def test_prefer_free_formats(self):
|
def test_prefer_free_formats(self):
|
||||||
# Same resolution => download webm
|
# Same resolution => download webm
|
||||||
ydl = YDL()
|
ydl = YDL()
|
||||||
ydl.params['prefer_free_formats'] = True
|
ydl.params['prefer_free_formats'] = True
|
||||||
formats = [
|
formats = [
|
||||||
{'ext': 'webm', 'height': 460},
|
{'ext': 'webm', 'height': 460, 'url': 'x'},
|
||||||
{'ext': 'mp4', 'height': 460},
|
{'ext': 'mp4', 'height': 460, 'url': 'y'},
|
||||||
]
|
]
|
||||||
info_dict = {'formats': formats, 'extractor': 'test'}
|
info_dict = _make_result(formats)
|
||||||
yie = YoutubeIE(ydl)
|
yie = YoutubeIE(ydl)
|
||||||
yie._sort_formats(info_dict['formats'])
|
yie._sort_formats(info_dict['formats'])
|
||||||
ydl.process_ie_result(info_dict)
|
ydl.process_ie_result(info_dict)
|
||||||
@ -46,8 +57,8 @@ class TestFormatSelection(unittest.TestCase):
|
|||||||
ydl = YDL()
|
ydl = YDL()
|
||||||
ydl.params['prefer_free_formats'] = True
|
ydl.params['prefer_free_formats'] = True
|
||||||
formats = [
|
formats = [
|
||||||
{'ext': 'webm', 'height': 720},
|
{'ext': 'webm', 'height': 720, 'url': 'a'},
|
||||||
{'ext': 'mp4', 'height': 1080},
|
{'ext': 'mp4', 'height': 1080, 'url': 'b'},
|
||||||
]
|
]
|
||||||
info_dict['formats'] = formats
|
info_dict['formats'] = formats
|
||||||
yie = YoutubeIE(ydl)
|
yie = YoutubeIE(ydl)
|
||||||
@ -56,13 +67,13 @@ class TestFormatSelection(unittest.TestCase):
|
|||||||
downloaded = ydl.downloaded_info_dicts[0]
|
downloaded = ydl.downloaded_info_dicts[0]
|
||||||
self.assertEqual(downloaded['ext'], 'mp4')
|
self.assertEqual(downloaded['ext'], 'mp4')
|
||||||
|
|
||||||
# No prefer_free_formats => prefer mp4 and flv for greater compatibilty
|
# No prefer_free_formats => prefer mp4 and flv for greater compatibility
|
||||||
ydl = YDL()
|
ydl = YDL()
|
||||||
ydl.params['prefer_free_formats'] = False
|
ydl.params['prefer_free_formats'] = False
|
||||||
formats = [
|
formats = [
|
||||||
{'ext': 'webm', 'height': 720},
|
{'ext': 'webm', 'height': 720, 'url': '_'},
|
||||||
{'ext': 'mp4', 'height': 720},
|
{'ext': 'mp4', 'height': 720, 'url': '_'},
|
||||||
{'ext': 'flv', 'height': 720},
|
{'ext': 'flv', 'height': 720, 'url': '_'},
|
||||||
]
|
]
|
||||||
info_dict['formats'] = formats
|
info_dict['formats'] = formats
|
||||||
yie = YoutubeIE(ydl)
|
yie = YoutubeIE(ydl)
|
||||||
@ -74,8 +85,8 @@ class TestFormatSelection(unittest.TestCase):
|
|||||||
ydl = YDL()
|
ydl = YDL()
|
||||||
ydl.params['prefer_free_formats'] = False
|
ydl.params['prefer_free_formats'] = False
|
||||||
formats = [
|
formats = [
|
||||||
{'ext': 'flv', 'height': 720},
|
{'ext': 'flv', 'height': 720, 'url': '_'},
|
||||||
{'ext': 'webm', 'height': 720},
|
{'ext': 'webm', 'height': 720, 'url': '_'},
|
||||||
]
|
]
|
||||||
info_dict['formats'] = formats
|
info_dict['formats'] = formats
|
||||||
yie = YoutubeIE(ydl)
|
yie = YoutubeIE(ydl)
|
||||||
@ -91,8 +102,7 @@ class TestFormatSelection(unittest.TestCase):
|
|||||||
{'format_id': 'great', 'url': 'http://example.com/great', 'preference': 3},
|
{'format_id': 'great', 'url': 'http://example.com/great', 'preference': 3},
|
||||||
{'format_id': 'excellent', 'url': 'http://example.com/exc', 'preference': 4},
|
{'format_id': 'excellent', 'url': 'http://example.com/exc', 'preference': 4},
|
||||||
]
|
]
|
||||||
info_dict = {
|
info_dict = _make_result(formats)
|
||||||
'formats': formats, 'extractor': 'test', 'id': 'testvid'}
|
|
||||||
|
|
||||||
ydl = YDL()
|
ydl = YDL()
|
||||||
ydl.process_ie_result(info_dict)
|
ydl.process_ie_result(info_dict)
|
||||||
@ -120,12 +130,12 @@ class TestFormatSelection(unittest.TestCase):
|
|||||||
|
|
||||||
def test_format_selection(self):
|
def test_format_selection(self):
|
||||||
formats = [
|
formats = [
|
||||||
{'format_id': '35', 'ext': 'mp4', 'preference': 1},
|
{'format_id': '35', 'ext': 'mp4', 'preference': 1, 'url': '_'},
|
||||||
{'format_id': '45', 'ext': 'webm', 'preference': 2},
|
{'format_id': '45', 'ext': 'webm', 'preference': 2, 'url': '_'},
|
||||||
{'format_id': '47', 'ext': 'webm', 'preference': 3},
|
{'format_id': '47', 'ext': 'webm', 'preference': 3, 'url': '_'},
|
||||||
{'format_id': '2', 'ext': 'flv', 'preference': 4},
|
{'format_id': '2', 'ext': 'flv', 'preference': 4, 'url': '_'},
|
||||||
]
|
]
|
||||||
info_dict = {'formats': formats, 'extractor': 'test'}
|
info_dict = _make_result(formats)
|
||||||
|
|
||||||
ydl = YDL({'format': '20/47'})
|
ydl = YDL({'format': '20/47'})
|
||||||
ydl.process_ie_result(info_dict.copy())
|
ydl.process_ie_result(info_dict.copy())
|
||||||
@ -154,12 +164,12 @@ class TestFormatSelection(unittest.TestCase):
|
|||||||
|
|
||||||
def test_format_selection_audio(self):
|
def test_format_selection_audio(self):
|
||||||
formats = [
|
formats = [
|
||||||
{'format_id': 'audio-low', 'ext': 'webm', 'preference': 1, 'vcodec': 'none'},
|
{'format_id': 'audio-low', 'ext': 'webm', 'preference': 1, 'vcodec': 'none', 'url': '_'},
|
||||||
{'format_id': 'audio-mid', 'ext': 'webm', 'preference': 2, 'vcodec': 'none'},
|
{'format_id': 'audio-mid', 'ext': 'webm', 'preference': 2, 'vcodec': 'none', 'url': '_'},
|
||||||
{'format_id': 'audio-high', 'ext': 'flv', 'preference': 3, 'vcodec': 'none'},
|
{'format_id': 'audio-high', 'ext': 'flv', 'preference': 3, 'vcodec': 'none', 'url': '_'},
|
||||||
{'format_id': 'vid', 'ext': 'mp4', 'preference': 4},
|
{'format_id': 'vid', 'ext': 'mp4', 'preference': 4, 'url': '_'},
|
||||||
]
|
]
|
||||||
info_dict = {'formats': formats, 'extractor': 'test'}
|
info_dict = _make_result(formats)
|
||||||
|
|
||||||
ydl = YDL({'format': 'bestaudio'})
|
ydl = YDL({'format': 'bestaudio'})
|
||||||
ydl.process_ie_result(info_dict.copy())
|
ydl.process_ie_result(info_dict.copy())
|
||||||
@ -172,10 +182,10 @@ class TestFormatSelection(unittest.TestCase):
|
|||||||
self.assertEqual(downloaded['format_id'], 'audio-low')
|
self.assertEqual(downloaded['format_id'], 'audio-low')
|
||||||
|
|
||||||
formats = [
|
formats = [
|
||||||
{'format_id': 'vid-low', 'ext': 'mp4', 'preference': 1},
|
{'format_id': 'vid-low', 'ext': 'mp4', 'preference': 1, 'url': '_'},
|
||||||
{'format_id': 'vid-high', 'ext': 'mp4', 'preference': 2},
|
{'format_id': 'vid-high', 'ext': 'mp4', 'preference': 2, 'url': '_'},
|
||||||
]
|
]
|
||||||
info_dict = {'formats': formats, 'extractor': 'test'}
|
info_dict = _make_result(formats)
|
||||||
|
|
||||||
ydl = YDL({'format': 'bestaudio/worstaudio/best'})
|
ydl = YDL({'format': 'bestaudio/worstaudio/best'})
|
||||||
ydl.process_ie_result(info_dict.copy())
|
ydl.process_ie_result(info_dict.copy())
|
||||||
@ -184,11 +194,11 @@ class TestFormatSelection(unittest.TestCase):
|
|||||||
|
|
||||||
def test_format_selection_video(self):
|
def test_format_selection_video(self):
|
||||||
formats = [
|
formats = [
|
||||||
{'format_id': 'dash-video-low', 'ext': 'mp4', 'preference': 1, 'acodec': 'none'},
|
{'format_id': 'dash-video-low', 'ext': 'mp4', 'preference': 1, 'acodec': 'none', 'url': '_'},
|
||||||
{'format_id': 'dash-video-high', 'ext': 'mp4', 'preference': 2, 'acodec': 'none'},
|
{'format_id': 'dash-video-high', 'ext': 'mp4', 'preference': 2, 'acodec': 'none', 'url': '_'},
|
||||||
{'format_id': 'vid', 'ext': 'mp4', 'preference': 3},
|
{'format_id': 'vid', 'ext': 'mp4', 'preference': 3, 'url': '_'},
|
||||||
]
|
]
|
||||||
info_dict = {'formats': formats, 'extractor': 'test'}
|
info_dict = _make_result(formats)
|
||||||
|
|
||||||
ydl = YDL({'format': 'bestvideo'})
|
ydl = YDL({'format': 'bestvideo'})
|
||||||
ydl.process_ie_result(info_dict.copy())
|
ydl.process_ie_result(info_dict.copy())
|
||||||
@ -217,10 +227,12 @@ class TestFormatSelection(unittest.TestCase):
|
|||||||
for f1id, f2id in zip(order, order[1:]):
|
for f1id, f2id in zip(order, order[1:]):
|
||||||
f1 = YoutubeIE._formats[f1id].copy()
|
f1 = YoutubeIE._formats[f1id].copy()
|
||||||
f1['format_id'] = f1id
|
f1['format_id'] = f1id
|
||||||
|
f1['url'] = 'url:' + f1id
|
||||||
f2 = YoutubeIE._formats[f2id].copy()
|
f2 = YoutubeIE._formats[f2id].copy()
|
||||||
f2['format_id'] = f2id
|
f2['format_id'] = f2id
|
||||||
|
f2['url'] = 'url:' + f2id
|
||||||
|
|
||||||
info_dict = {'formats': [f1, f2], 'extractor': 'youtube'}
|
info_dict = _make_result([f1, f2], extractor='youtube')
|
||||||
ydl = YDL()
|
ydl = YDL()
|
||||||
yie = YoutubeIE(ydl)
|
yie = YoutubeIE(ydl)
|
||||||
yie._sort_formats(info_dict['formats'])
|
yie._sort_formats(info_dict['formats'])
|
||||||
@ -228,7 +240,7 @@ 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)
|
||||||
|
|
||||||
info_dict = {'formats': [f2, f1], 'extractor': 'youtube'}
|
info_dict = _make_result([f2, f1], extractor='youtube')
|
||||||
ydl = YDL()
|
ydl = YDL()
|
||||||
yie = YoutubeIE(ydl)
|
yie = YoutubeIE(ydl)
|
||||||
yie._sort_formats(info_dict['formats'])
|
yie._sort_formats(info_dict['formats'])
|
||||||
@ -262,6 +274,12 @@ class TestFormatSelection(unittest.TestCase):
|
|||||||
# Replace missing fields with 'NA'
|
# Replace missing fields with 'NA'
|
||||||
self.assertEqual(fname('%(uploader_date)s-%(id)s.%(ext)s'), 'NA-1234.mp4')
|
self.assertEqual(fname('%(uploader_date)s-%(id)s.%(ext)s'), 'NA-1234.mp4')
|
||||||
|
|
||||||
|
def test_format_note(self):
|
||||||
|
ydl = YoutubeDL()
|
||||||
|
self.assertEqual(ydl._format_note({}), '')
|
||||||
|
assertRegexpMatches(self, ydl._format_note({
|
||||||
|
'vbr': 10,
|
||||||
|
}), '^\s*10k$')
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -49,6 +49,7 @@ class TestAllURLsMatching(unittest.TestCase):
|
|||||||
self.assertMatch('http://youtu.be/BaW_jenozKc', ['youtube'])
|
self.assertMatch('http://youtu.be/BaW_jenozKc', ['youtube'])
|
||||||
self.assertMatch('http://www.youtube.com/v/BaW_jenozKc', ['youtube'])
|
self.assertMatch('http://www.youtube.com/v/BaW_jenozKc', ['youtube'])
|
||||||
self.assertMatch('https://youtube.googleapis.com/v/BaW_jenozKc', ['youtube'])
|
self.assertMatch('https://youtube.googleapis.com/v/BaW_jenozKc', ['youtube'])
|
||||||
|
self.assertMatch('http://www.cleanvideosearch.com/media/action/yt/watch?videoId=8v_4O44sfjM', ['youtube'])
|
||||||
|
|
||||||
def test_youtube_channel_matching(self):
|
def test_youtube_channel_matching(self):
|
||||||
assertChannel = lambda url: self.assertMatch(url, ['youtube:channel'])
|
assertChannel = lambda url: self.assertMatch(url, ['youtube:channel'])
|
||||||
@ -76,20 +77,20 @@ class TestAllURLsMatching(unittest.TestCase):
|
|||||||
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_justin_tv_channelid_matching(self):
|
def test_justin_tv_channelid_matching(self):
|
||||||
self.assertTrue(JustinTVIE.suitable(u"justin.tv/vanillatv"))
|
self.assertTrue(JustinTVIE.suitable('justin.tv/vanillatv'))
|
||||||
self.assertTrue(JustinTVIE.suitable(u"twitch.tv/vanillatv"))
|
self.assertTrue(JustinTVIE.suitable('twitch.tv/vanillatv'))
|
||||||
self.assertTrue(JustinTVIE.suitable(u"www.justin.tv/vanillatv"))
|
self.assertTrue(JustinTVIE.suitable('www.justin.tv/vanillatv'))
|
||||||
self.assertTrue(JustinTVIE.suitable(u"www.twitch.tv/vanillatv"))
|
self.assertTrue(JustinTVIE.suitable('www.twitch.tv/vanillatv'))
|
||||||
self.assertTrue(JustinTVIE.suitable(u"http://www.justin.tv/vanillatv"))
|
self.assertTrue(JustinTVIE.suitable('http://www.justin.tv/vanillatv'))
|
||||||
self.assertTrue(JustinTVIE.suitable(u"http://www.twitch.tv/vanillatv"))
|
self.assertTrue(JustinTVIE.suitable('http://www.twitch.tv/vanillatv'))
|
||||||
self.assertTrue(JustinTVIE.suitable(u"http://www.justin.tv/vanillatv/"))
|
self.assertTrue(JustinTVIE.suitable('http://www.justin.tv/vanillatv/'))
|
||||||
self.assertTrue(JustinTVIE.suitable(u"http://www.twitch.tv/vanillatv/"))
|
self.assertTrue(JustinTVIE.suitable('http://www.twitch.tv/vanillatv/'))
|
||||||
|
|
||||||
def test_justintv_videoid_matching(self):
|
def test_justintv_videoid_matching(self):
|
||||||
self.assertTrue(JustinTVIE.suitable(u"http://www.twitch.tv/vanillatv/b/328087483"))
|
self.assertTrue(JustinTVIE.suitable('http://www.twitch.tv/vanillatv/b/328087483'))
|
||||||
|
|
||||||
def test_justin_tv_chapterid_matching(self):
|
def test_justin_tv_chapterid_matching(self):
|
||||||
self.assertTrue(JustinTVIE.suitable(u"http://www.twitch.tv/tsm_theoddone/c/2349361"))
|
self.assertTrue(JustinTVIE.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)
|
||||||
@ -105,7 +106,7 @@ class TestAllURLsMatching(unittest.TestCase):
|
|||||||
|
|
||||||
def test_no_duplicates(self):
|
def test_no_duplicates(self):
|
||||||
ies = gen_extractors()
|
ies = gen_extractors()
|
||||||
for tc in gettestcases():
|
for tc in gettestcases(include_onlymatching=True):
|
||||||
url = tc['url']
|
url = tc['url']
|
||||||
for ie in ies:
|
for ie in ies:
|
||||||
if type(ie).__name__ in ('GenericIE', tc['name'] + 'IE'):
|
if type(ie).__name__ in ('GenericIE', tc['name'] + 'IE'):
|
||||||
@ -144,7 +145,37 @@ class TestAllURLsMatching(unittest.TestCase):
|
|||||||
self.assertMatch('http://video.pbs.org/widget/partnerplayer/980042464/', ['PBS'])
|
self.assertMatch('http://video.pbs.org/widget/partnerplayer/980042464/', ['PBS'])
|
||||||
|
|
||||||
def test_ComedyCentralShows(self):
|
def test_ComedyCentralShows(self):
|
||||||
self.assertMatch('http://thedailyshow.cc.com/extended-interviews/xm3fnq/andrew-napolitano-extended-interview', ['ComedyCentralShows'])
|
self.assertMatch(
|
||||||
|
'http://thedailyshow.cc.com/extended-interviews/xm3fnq/andrew-napolitano-extended-interview',
|
||||||
|
['ComedyCentralShows'])
|
||||||
|
self.assertMatch(
|
||||||
|
'http://thecolbertreport.cc.com/videos/29w6fx/-realhumanpraise-for-fox-news',
|
||||||
|
['ComedyCentralShows'])
|
||||||
|
self.assertMatch(
|
||||||
|
'http://thecolbertreport.cc.com/videos/gh6urb/neil-degrasse-tyson-pt--1?xrs=eml_col_031114',
|
||||||
|
['ComedyCentralShows'])
|
||||||
|
self.assertMatch(
|
||||||
|
'http://thedailyshow.cc.com/guests/michael-lewis/3efna8/exclusive---michael-lewis-extended-interview-pt--3',
|
||||||
|
['ComedyCentralShows'])
|
||||||
|
self.assertMatch(
|
||||||
|
'http://thedailyshow.cc.com/episodes/sy7yv0/april-8--2014---denis-leary',
|
||||||
|
['ComedyCentralShows'])
|
||||||
|
self.assertMatch(
|
||||||
|
'http://thecolbertreport.cc.com/episodes/8ase07/april-8--2014---jane-goodall',
|
||||||
|
['ComedyCentralShows'])
|
||||||
|
self.assertMatch(
|
||||||
|
'http://thedailyshow.cc.com/video-playlists/npde3s/the-daily-show-19088-highlights',
|
||||||
|
['ComedyCentralShows'])
|
||||||
|
self.assertMatch(
|
||||||
|
'http://thedailyshow.cc.com/special-editions/2l8fdb/special-edition---a-look-back-at-food',
|
||||||
|
['ComedyCentralShows'])
|
||||||
|
|
||||||
|
def test_yahoo_https(self):
|
||||||
|
# https://github.com/rg3/youtube-dl/issues/2701
|
||||||
|
self.assertMatch(
|
||||||
|
'https://screen.yahoo.com/smartwatches-latest-wearable-gadgets-163745379-cbs.html',
|
||||||
|
['Yahoo'])
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -10,6 +10,7 @@ import unittest
|
|||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
from test.helper import (
|
from test.helper import (
|
||||||
|
assertRegexpMatches,
|
||||||
expect_info_dict,
|
expect_info_dict,
|
||||||
FakeYDL,
|
FakeYDL,
|
||||||
)
|
)
|
||||||
@ -22,9 +23,11 @@ from youtube_dl.extractor import (
|
|||||||
VimeoUserIE,
|
VimeoUserIE,
|
||||||
VimeoAlbumIE,
|
VimeoAlbumIE,
|
||||||
VimeoGroupsIE,
|
VimeoGroupsIE,
|
||||||
|
VineUserIE,
|
||||||
UstreamChannelIE,
|
UstreamChannelIE,
|
||||||
SoundcloudSetIE,
|
SoundcloudSetIE,
|
||||||
SoundcloudUserIE,
|
SoundcloudUserIE,
|
||||||
|
SoundcloudPlaylistIE,
|
||||||
LivestreamIE,
|
LivestreamIE,
|
||||||
NHLVideocenterIE,
|
NHLVideocenterIE,
|
||||||
BambuserChannelIE,
|
BambuserChannelIE,
|
||||||
@ -42,6 +45,8 @@ from youtube_dl.extractor import (
|
|||||||
ToypicsUserIE,
|
ToypicsUserIE,
|
||||||
XTubeUserIE,
|
XTubeUserIE,
|
||||||
InstagramUserIE,
|
InstagramUserIE,
|
||||||
|
CSpanIE,
|
||||||
|
AolIE,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -98,6 +103,13 @@ class TestPlaylists(unittest.TestCase):
|
|||||||
self.assertEqual(result['title'], 'Rolex Awards for Enterprise')
|
self.assertEqual(result['title'], 'Rolex Awards for Enterprise')
|
||||||
self.assertTrue(len(result['entries']) > 72)
|
self.assertTrue(len(result['entries']) > 72)
|
||||||
|
|
||||||
|
def test_vine_user(self):
|
||||||
|
dl = FakeYDL()
|
||||||
|
ie = VineUserIE(dl)
|
||||||
|
result = ie.extract('https://vine.co/Visa')
|
||||||
|
self.assertIsPlaylist(result)
|
||||||
|
self.assertTrue(len(result['entries']) >= 50)
|
||||||
|
|
||||||
def test_ustream_channel(self):
|
def test_ustream_channel(self):
|
||||||
dl = FakeYDL()
|
dl = FakeYDL()
|
||||||
ie = UstreamChannelIE(dl)
|
ie = UstreamChannelIE(dl)
|
||||||
@ -122,6 +134,17 @@ class TestPlaylists(unittest.TestCase):
|
|||||||
self.assertEqual(result['id'], '9615865')
|
self.assertEqual(result['id'], '9615865')
|
||||||
self.assertTrue(len(result['entries']) >= 12)
|
self.assertTrue(len(result['entries']) >= 12)
|
||||||
|
|
||||||
|
def test_soundcloud_playlist(self):
|
||||||
|
dl = FakeYDL()
|
||||||
|
ie = SoundcloudPlaylistIE(dl)
|
||||||
|
result = ie.extract('http://api.soundcloud.com/playlists/4110309')
|
||||||
|
self.assertIsPlaylist(result)
|
||||||
|
self.assertEqual(result['id'], '4110309')
|
||||||
|
self.assertEqual(result['title'], 'TILT Brass - Bowery Poetry Club, August \'03 [Non-Site SCR 02]')
|
||||||
|
assertRegexpMatches(
|
||||||
|
self, result['description'], r'TILT Brass - Bowery Poetry Club')
|
||||||
|
self.assertEqual(len(result['entries']), 6)
|
||||||
|
|
||||||
def test_livestream_event(self):
|
def test_livestream_event(self):
|
||||||
dl = FakeYDL()
|
dl = FakeYDL()
|
||||||
ie = LivestreamIE(dl)
|
ie = LivestreamIE(dl)
|
||||||
@ -186,20 +209,20 @@ class TestPlaylists(unittest.TestCase):
|
|||||||
def test_ivi_compilation(self):
|
def test_ivi_compilation(self):
|
||||||
dl = FakeYDL()
|
dl = FakeYDL()
|
||||||
ie = IviCompilationIE(dl)
|
ie = IviCompilationIE(dl)
|
||||||
result = ie.extract('http://www.ivi.ru/watch/dezhurnyi_angel')
|
result = ie.extract('http://www.ivi.ru/watch/dvoe_iz_lartsa')
|
||||||
self.assertIsPlaylist(result)
|
self.assertIsPlaylist(result)
|
||||||
self.assertEqual(result['id'], 'dezhurnyi_angel')
|
self.assertEqual(result['id'], 'dvoe_iz_lartsa')
|
||||||
self.assertEqual(result['title'], 'Дежурный ангел (2010 - 2012)')
|
self.assertEqual(result['title'], 'Двое из ларца (2006 - 2008)')
|
||||||
self.assertTrue(len(result['entries']) >= 36)
|
self.assertTrue(len(result['entries']) >= 24)
|
||||||
|
|
||||||
def test_ivi_compilation_season(self):
|
def test_ivi_compilation_season(self):
|
||||||
dl = FakeYDL()
|
dl = FakeYDL()
|
||||||
ie = IviCompilationIE(dl)
|
ie = IviCompilationIE(dl)
|
||||||
result = ie.extract('http://www.ivi.ru/watch/dezhurnyi_angel/season2')
|
result = ie.extract('http://www.ivi.ru/watch/dvoe_iz_lartsa/season1')
|
||||||
self.assertIsPlaylist(result)
|
self.assertIsPlaylist(result)
|
||||||
self.assertEqual(result['id'], 'dezhurnyi_angel/season2')
|
self.assertEqual(result['id'], 'dvoe_iz_lartsa/season1')
|
||||||
self.assertEqual(result['title'], 'Дежурный ангел (2010 - 2012) 2 сезон')
|
self.assertEqual(result['title'], 'Двое из ларца (2006 - 2008) 1 сезон')
|
||||||
self.assertTrue(len(result['entries']) >= 20)
|
self.assertTrue(len(result['entries']) >= 12)
|
||||||
|
|
||||||
def test_imdb_list(self):
|
def test_imdb_list(self):
|
||||||
dl = FakeYDL()
|
dl = FakeYDL()
|
||||||
@ -314,6 +337,28 @@ class TestPlaylists(unittest.TestCase):
|
|||||||
}
|
}
|
||||||
expect_info_dict(self, EXPECTED, test_video)
|
expect_info_dict(self, EXPECTED, test_video)
|
||||||
|
|
||||||
|
def test_CSpan_playlist(self):
|
||||||
|
dl = FakeYDL()
|
||||||
|
ie = CSpanIE(dl)
|
||||||
|
result = ie.extract(
|
||||||
|
'http://www.c-span.org/video/?318608-1/gm-ignition-switch-recall')
|
||||||
|
self.assertIsPlaylist(result)
|
||||||
|
self.assertEqual(result['id'], '342759')
|
||||||
|
self.assertEqual(
|
||||||
|
result['title'], 'General Motors Ignition Switch Recall')
|
||||||
|
whole_duration = sum(e['duration'] for e in result['entries'])
|
||||||
|
self.assertEqual(whole_duration, 14855)
|
||||||
|
|
||||||
|
def test_aol_playlist(self):
|
||||||
|
dl = FakeYDL()
|
||||||
|
ie = AolIE(dl)
|
||||||
|
result = ie.extract(
|
||||||
|
'http://on.aol.com/playlist/brace-yourself---todays-weirdest-news-152147?icid=OnHomepageC4_Omg_Img#_videoid=518184316')
|
||||||
|
self.assertIsPlaylist(result)
|
||||||
|
self.assertEqual(result['id'], '152147')
|
||||||
|
self.assertEqual(
|
||||||
|
result['title'], 'Brace Yourself - Today\'s Weirdest News')
|
||||||
|
self.assertTrue(len(result['entries']) >= 10)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -181,7 +181,7 @@ class TestTedSubtitles(BaseTestSubtitles):
|
|||||||
self.DL.params['writesubtitles'] = True
|
self.DL.params['writesubtitles'] = True
|
||||||
self.DL.params['allsubtitles'] = True
|
self.DL.params['allsubtitles'] = True
|
||||||
subtitles = self.getSubtitles()
|
subtitles = self.getSubtitles()
|
||||||
self.assertEqual(len(subtitles.keys()), 28)
|
self.assertTrue(len(subtitles.keys()) >= 28)
|
||||||
|
|
||||||
def test_list_subtitles(self):
|
def test_list_subtitles(self):
|
||||||
self.DL.expect_warning(u'Automatic Captions not supported by this server')
|
self.DL.expect_warning(u'Automatic Captions not supported by this server')
|
||||||
|
@ -38,6 +38,7 @@ from youtube_dl.utils import (
|
|||||||
xpath_with_ns,
|
xpath_with_ns,
|
||||||
parse_iso8601,
|
parse_iso8601,
|
||||||
strip_jsonp,
|
strip_jsonp,
|
||||||
|
uppercase_escape,
|
||||||
)
|
)
|
||||||
|
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
@ -279,6 +280,9 @@ class TestUtil(unittest.TestCase):
|
|||||||
d = json.loads(stripped)
|
d = json.loads(stripped)
|
||||||
self.assertEqual(d, [{"id": "532cb", "x": 3}])
|
self.assertEqual(d, [{"id": "532cb", "x": 3}])
|
||||||
|
|
||||||
|
def test_uppercase_escpae(self):
|
||||||
|
self.assertEqual(uppercase_escape(u'aä'), u'aä')
|
||||||
|
self.assertEqual(uppercase_escape(u'\\U0001d550'), u'𝕐')
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
164
youtube_dl/YoutubeDL.py
Normal file → Executable file
164
youtube_dl/YoutubeDL.py
Normal file → Executable file
@ -8,6 +8,7 @@ import datetime
|
|||||||
import errno
|
import errno
|
||||||
import io
|
import io
|
||||||
import json
|
import json
|
||||||
|
import locale
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import re
|
import re
|
||||||
@ -30,6 +31,7 @@ from .utils import (
|
|||||||
ContentTooShortError,
|
ContentTooShortError,
|
||||||
date_from_str,
|
date_from_str,
|
||||||
DateRange,
|
DateRange,
|
||||||
|
DEFAULT_OUTTMPL,
|
||||||
determine_ext,
|
determine_ext,
|
||||||
DownloadError,
|
DownloadError,
|
||||||
encodeFilename,
|
encodeFilename,
|
||||||
@ -159,6 +161,7 @@ class YoutubeDL(object):
|
|||||||
include_ads: Download ads as well
|
include_ads: Download ads as well
|
||||||
default_search: Prepend this string if an input url is not valid.
|
default_search: Prepend this string if an input url is not valid.
|
||||||
'auto' for elaborate guessing
|
'auto' for elaborate guessing
|
||||||
|
encoding: Use this encoding instead of the system-specified.
|
||||||
|
|
||||||
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:
|
||||||
@ -284,6 +287,9 @@ class YoutubeDL(object):
|
|||||||
"""Print message to stdout if not in quiet mode."""
|
"""Print message to stdout if not in quiet mode."""
|
||||||
return self.to_stdout(message, skip_eol, check_quiet=True)
|
return self.to_stdout(message, skip_eol, check_quiet=True)
|
||||||
|
|
||||||
|
def _write_string(self, s, out=None):
|
||||||
|
write_string(s, out=out, encoding=self.params.get('encoding'))
|
||||||
|
|
||||||
def to_stdout(self, message, skip_eol=False, check_quiet=False):
|
def to_stdout(self, message, skip_eol=False, check_quiet=False):
|
||||||
"""Print message to stdout if not in quiet mode."""
|
"""Print message to stdout if not in quiet mode."""
|
||||||
if self.params.get('logger'):
|
if self.params.get('logger'):
|
||||||
@ -293,7 +299,7 @@ class YoutubeDL(object):
|
|||||||
terminator = ['\n', ''][skip_eol]
|
terminator = ['\n', ''][skip_eol]
|
||||||
output = message + terminator
|
output = message + terminator
|
||||||
|
|
||||||
write_string(output, self._screen_file)
|
self._write_string(output, self._screen_file)
|
||||||
|
|
||||||
def to_stderr(self, message):
|
def to_stderr(self, message):
|
||||||
"""Print message to stderr."""
|
"""Print message to stderr."""
|
||||||
@ -303,7 +309,7 @@ class YoutubeDL(object):
|
|||||||
else:
|
else:
|
||||||
message = self._bidi_workaround(message)
|
message = self._bidi_workaround(message)
|
||||||
output = message + '\n'
|
output = message + '\n'
|
||||||
write_string(output, self._err_file)
|
self._write_string(output, self._err_file)
|
||||||
|
|
||||||
def to_console_title(self, message):
|
def to_console_title(self, message):
|
||||||
if not self.params.get('consoletitle', False):
|
if not self.params.get('consoletitle', False):
|
||||||
@ -313,21 +319,21 @@ class YoutubeDL(object):
|
|||||||
# already of type unicode()
|
# already of type unicode()
|
||||||
ctypes.windll.kernel32.SetConsoleTitleW(ctypes.c_wchar_p(message))
|
ctypes.windll.kernel32.SetConsoleTitleW(ctypes.c_wchar_p(message))
|
||||||
elif 'TERM' in os.environ:
|
elif 'TERM' in os.environ:
|
||||||
write_string('\033]0;%s\007' % message, self._screen_file)
|
self._write_string('\033]0;%s\007' % message, self._screen_file)
|
||||||
|
|
||||||
def save_console_title(self):
|
def save_console_title(self):
|
||||||
if not self.params.get('consoletitle', False):
|
if not self.params.get('consoletitle', False):
|
||||||
return
|
return
|
||||||
if 'TERM' in os.environ:
|
if 'TERM' in os.environ:
|
||||||
# Save the title on stack
|
# Save the title on stack
|
||||||
write_string('\033[22;0t', self._screen_file)
|
self._write_string('\033[22;0t', self._screen_file)
|
||||||
|
|
||||||
def restore_console_title(self):
|
def restore_console_title(self):
|
||||||
if not self.params.get('consoletitle', False):
|
if not self.params.get('consoletitle', False):
|
||||||
return
|
return
|
||||||
if 'TERM' in os.environ:
|
if 'TERM' in os.environ:
|
||||||
# Restore the title from stack
|
# Restore the title from stack
|
||||||
write_string('\033[23;0t', self._screen_file)
|
self._write_string('\033[23;0t', self._screen_file)
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
self.save_console_title()
|
self.save_console_title()
|
||||||
@ -435,7 +441,8 @@ class YoutubeDL(object):
|
|||||||
if v is not None)
|
if v is not None)
|
||||||
template_dict = collections.defaultdict(lambda: 'NA', template_dict)
|
template_dict = collections.defaultdict(lambda: 'NA', template_dict)
|
||||||
|
|
||||||
tmpl = os.path.expanduser(self.params['outtmpl'])
|
outtmpl = self.params.get('outtmpl', DEFAULT_OUTTMPL)
|
||||||
|
tmpl = os.path.expanduser(outtmpl)
|
||||||
filename = tmpl % template_dict
|
filename = tmpl % template_dict
|
||||||
return filename
|
return filename
|
||||||
except ValueError as err:
|
except ValueError as err:
|
||||||
@ -700,6 +707,11 @@ class YoutubeDL(object):
|
|||||||
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'
|
||||||
|
|
||||||
|
if 'id' not in info_dict:
|
||||||
|
raise ExtractorError('Missing "id" field in extractor result')
|
||||||
|
if 'title' not in info_dict:
|
||||||
|
raise ExtractorError('Missing "title" field in extractor result')
|
||||||
|
|
||||||
if 'playlist' not in info_dict:
|
if 'playlist' not in info_dict:
|
||||||
# It isn't part of a playlist
|
# It isn't part of a playlist
|
||||||
info_dict['playlist'] = None
|
info_dict['playlist'] = None
|
||||||
@ -731,6 +743,9 @@ class YoutubeDL(object):
|
|||||||
|
|
||||||
# We check that all the formats have the format and format_id fields
|
# We check that all the formats have the format and format_id fields
|
||||||
for i, format in enumerate(formats):
|
for i, format in enumerate(formats):
|
||||||
|
if 'url' not in format:
|
||||||
|
raise ExtractorError('Missing "url" key in result (index %d)' % i)
|
||||||
|
|
||||||
if format.get('format_id') is None:
|
if format.get('format_id') is None:
|
||||||
format['format_id'] = compat_str(i)
|
format['format_id'] = compat_str(i)
|
||||||
if format.get('format') is None:
|
if format.get('format') is None:
|
||||||
@ -741,7 +756,7 @@ 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'])
|
format['ext'] = determine_ext(format['url']).lower()
|
||||||
|
|
||||||
format_limit = self.params.get('format_limit', None)
|
format_limit = self.params.get('format_limit', None)
|
||||||
if format_limit:
|
if format_limit:
|
||||||
@ -866,7 +881,7 @@ class YoutubeDL(object):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
dn = os.path.dirname(encodeFilename(filename))
|
dn = os.path.dirname(encodeFilename(filename))
|
||||||
if dn != '' and not os.path.exists(dn):
|
if dn and not os.path.exists(dn):
|
||||||
os.makedirs(dn)
|
os.makedirs(dn)
|
||||||
except (OSError, IOError) as err:
|
except (OSError, IOError) as err:
|
||||||
self.report_error('unable to create directory ' + compat_str(err))
|
self.report_error('unable to create directory ' + compat_str(err))
|
||||||
@ -923,7 +938,7 @@ class YoutubeDL(object):
|
|||||||
with io.open(encodeFilename(sub_filename), 'w', encoding='utf-8') as subfile:
|
with io.open(encodeFilename(sub_filename), 'w', encoding='utf-8') as subfile:
|
||||||
subfile.write(sub)
|
subfile.write(sub)
|
||||||
except (OSError, IOError):
|
except (OSError, IOError):
|
||||||
self.report_error('Cannot write subtitles file ' + descfn)
|
self.report_error('Cannot write subtitles file ' + sub_filename)
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.params.get('writeinfojson', False):
|
if self.params.get('writeinfojson', False):
|
||||||
@ -1012,10 +1027,11 @@ class YoutubeDL(object):
|
|||||||
|
|
||||||
def download(self, url_list):
|
def download(self, url_list):
|
||||||
"""Download a given list of URLs."""
|
"""Download a given list of URLs."""
|
||||||
|
outtmpl = self.params.get('outtmpl', DEFAULT_OUTTMPL)
|
||||||
if (len(url_list) > 1 and
|
if (len(url_list) > 1 and
|
||||||
'%' not in self.params['outtmpl']
|
'%' not in outtmpl
|
||||||
and self.params.get('max_downloads') != 1):
|
and self.params.get('max_downloads') != 1):
|
||||||
raise SameFileError(self.params['outtmpl'])
|
raise SameFileError(outtmpl)
|
||||||
|
|
||||||
for url in url_list:
|
for url in url_list:
|
||||||
try:
|
try:
|
||||||
@ -1126,57 +1142,57 @@ class YoutubeDL(object):
|
|||||||
res = default
|
res = default
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def list_formats(self, info_dict):
|
def _format_note(self, fdict):
|
||||||
def format_note(fdict):
|
res = ''
|
||||||
res = ''
|
if fdict.get('ext') in ['f4f', 'f4m']:
|
||||||
if fdict.get('ext') in ['f4f', 'f4m']:
|
res += '(unsupported) '
|
||||||
res += '(unsupported) '
|
if fdict.get('format_note') is not None:
|
||||||
if fdict.get('format_note') is not None:
|
res += fdict['format_note'] + ' '
|
||||||
res += fdict['format_note'] + ' '
|
if fdict.get('tbr') is not None:
|
||||||
if fdict.get('tbr') is not None:
|
res += '%4dk ' % fdict['tbr']
|
||||||
res += '%4dk ' % fdict['tbr']
|
if fdict.get('container') is not None:
|
||||||
if fdict.get('container') is not None:
|
if res:
|
||||||
if res:
|
res += ', '
|
||||||
res += ', '
|
res += '%s container' % fdict['container']
|
||||||
res += '%s container' % fdict['container']
|
if (fdict.get('vcodec') is not None and
|
||||||
if (fdict.get('vcodec') is not None and
|
fdict.get('vcodec') != 'none'):
|
||||||
fdict.get('vcodec') != 'none'):
|
if res:
|
||||||
if res:
|
res += ', '
|
||||||
res += ', '
|
res += fdict['vcodec']
|
||||||
res += fdict['vcodec']
|
|
||||||
if fdict.get('vbr') is not None:
|
|
||||||
res += '@'
|
|
||||||
elif fdict.get('vbr') is not None and fdict.get('abr') is not None:
|
|
||||||
res += 'video@'
|
|
||||||
if fdict.get('vbr') is not None:
|
if fdict.get('vbr') is not None:
|
||||||
res += '%4dk' % fdict['vbr']
|
res += '@'
|
||||||
if fdict.get('acodec') is not None:
|
elif fdict.get('vbr') is not None and fdict.get('abr') is not None:
|
||||||
if res:
|
res += 'video@'
|
||||||
res += ', '
|
if fdict.get('vbr') is not None:
|
||||||
if fdict['acodec'] == 'none':
|
res += '%4dk' % fdict['vbr']
|
||||||
res += 'video only'
|
if fdict.get('acodec') is not None:
|
||||||
else:
|
if res:
|
||||||
res += '%-5s' % fdict['acodec']
|
res += ', '
|
||||||
elif fdict.get('abr') is not None:
|
if fdict['acodec'] == 'none':
|
||||||
if res:
|
res += 'video only'
|
||||||
res += ', '
|
else:
|
||||||
res += 'audio'
|
res += '%-5s' % fdict['acodec']
|
||||||
if fdict.get('abr') is not None:
|
elif fdict.get('abr') is not None:
|
||||||
res += '@%3dk' % fdict['abr']
|
if res:
|
||||||
if fdict.get('asr') is not None:
|
res += ', '
|
||||||
res += ' (%5dHz)' % fdict['asr']
|
res += 'audio'
|
||||||
if fdict.get('filesize') is not None:
|
if fdict.get('abr') is not None:
|
||||||
if res:
|
res += '@%3dk' % fdict['abr']
|
||||||
res += ', '
|
if fdict.get('asr') is not None:
|
||||||
res += format_bytes(fdict['filesize'])
|
res += ' (%5dHz)' % fdict['asr']
|
||||||
return res
|
if fdict.get('filesize') is not None:
|
||||||
|
if res:
|
||||||
|
res += ', '
|
||||||
|
res += format_bytes(fdict['filesize'])
|
||||||
|
return res
|
||||||
|
|
||||||
|
def list_formats(self, info_dict):
|
||||||
def line(format, idlen=20):
|
def line(format, idlen=20):
|
||||||
return (('%-' + compat_str(idlen + 1) + 's%-10s%-12s%s') % (
|
return (('%-' + compat_str(idlen + 1) + 's%-10s%-12s%s') % (
|
||||||
format['format_id'],
|
format['format_id'],
|
||||||
format['ext'],
|
format['ext'],
|
||||||
self.format_resolution(format),
|
self.format_resolution(format),
|
||||||
format_note(format),
|
self._format_note(format),
|
||||||
))
|
))
|
||||||
|
|
||||||
formats = info_dict.get('formats', [info_dict])
|
formats = info_dict.get('formats', [info_dict])
|
||||||
@ -1184,8 +1200,8 @@ class YoutubeDL(object):
|
|||||||
max(len(f['format_id']) for f in formats))
|
max(len(f['format_id']) for f in formats))
|
||||||
formats_s = [line(f, idlen) for f in formats]
|
formats_s = [line(f, idlen) for f in formats]
|
||||||
if len(formats) > 1:
|
if len(formats) > 1:
|
||||||
formats_s[0] += (' ' if format_note(formats[0]) else '') + '(worst)'
|
formats_s[0] += (' ' if self._format_note(formats[0]) else '') + '(worst)'
|
||||||
formats_s[-1] += (' ' if format_note(formats[-1]) else '') + '(best)'
|
formats_s[-1] += (' ' if self._format_note(formats[-1]) else '') + '(best)'
|
||||||
|
|
||||||
header_line = line({
|
header_line = line({
|
||||||
'format_id': 'format code', 'ext': 'extension',
|
'format_id': 'format code', 'ext': 'extension',
|
||||||
@ -1200,7 +1216,17 @@ class YoutubeDL(object):
|
|||||||
def print_debug_header(self):
|
def print_debug_header(self):
|
||||||
if not self.params.get('verbose'):
|
if not self.params.get('verbose'):
|
||||||
return
|
return
|
||||||
write_string('[debug] youtube-dl version ' + __version__ + '\n')
|
|
||||||
|
write_string(
|
||||||
|
'[debug] Encodings: locale %s, fs %s, out %s, pref %s\n' % (
|
||||||
|
locale.getpreferredencoding(),
|
||||||
|
sys.getfilesystemencoding(),
|
||||||
|
sys.stdout.encoding,
|
||||||
|
self.get_encoding()),
|
||||||
|
encoding=None
|
||||||
|
)
|
||||||
|
|
||||||
|
self._write_string('[debug] youtube-dl version ' + __version__ + '\n')
|
||||||
try:
|
try:
|
||||||
sp = subprocess.Popen(
|
sp = subprocess.Popen(
|
||||||
['git', 'rev-parse', '--short', 'HEAD'],
|
['git', 'rev-parse', '--short', 'HEAD'],
|
||||||
@ -1209,20 +1235,20 @@ class YoutubeDL(object):
|
|||||||
out, err = sp.communicate()
|
out, err = sp.communicate()
|
||||||
out = out.decode().strip()
|
out = out.decode().strip()
|
||||||
if re.match('[0-9a-f]+', out):
|
if re.match('[0-9a-f]+', out):
|
||||||
write_string('[debug] Git HEAD: ' + out + '\n')
|
self._write_string('[debug] Git HEAD: ' + out + '\n')
|
||||||
except:
|
except:
|
||||||
try:
|
try:
|
||||||
sys.exc_clear()
|
sys.exc_clear()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
write_string('[debug] Python version %s - %s' %
|
self._write_string('[debug] Python version %s - %s' %
|
||||||
(platform.python_version(), platform_name()) + '\n')
|
(platform.python_version(), platform_name()) + '\n')
|
||||||
|
|
||||||
proxy_map = {}
|
proxy_map = {}
|
||||||
for handler in self._opener.handlers:
|
for handler in self._opener.handlers:
|
||||||
if hasattr(handler, 'proxies'):
|
if hasattr(handler, 'proxies'):
|
||||||
proxy_map.update(handler.proxies)
|
proxy_map.update(handler.proxies)
|
||||||
write_string('[debug] Proxy map: ' + compat_str(proxy_map) + '\n')
|
self._write_string('[debug] Proxy map: ' + compat_str(proxy_map) + '\n')
|
||||||
|
|
||||||
def _setup_opener(self):
|
def _setup_opener(self):
|
||||||
timeout_val = self.params.get('socket_timeout')
|
timeout_val = self.params.get('socket_timeout')
|
||||||
@ -1264,3 +1290,19 @@ class YoutubeDL(object):
|
|||||||
# (See https://github.com/rg3/youtube-dl/issues/1309 for details)
|
# (See https://github.com/rg3/youtube-dl/issues/1309 for details)
|
||||||
opener.addheaders = []
|
opener.addheaders = []
|
||||||
self._opener = opener
|
self._opener = opener
|
||||||
|
|
||||||
|
def encode(self, s):
|
||||||
|
if isinstance(s, bytes):
|
||||||
|
return s # Already encoded
|
||||||
|
|
||||||
|
try:
|
||||||
|
return s.encode(self.get_encoding())
|
||||||
|
except UnicodeEncodeError as err:
|
||||||
|
err.reason = err.reason + '. Check your system encoding configuration or use the --encoding option.'
|
||||||
|
raise
|
||||||
|
|
||||||
|
def get_encoding(self):
|
||||||
|
encoding = self.params.get('encoding')
|
||||||
|
if encoding is None:
|
||||||
|
encoding = preferredencoding()
|
||||||
|
return encoding
|
||||||
|
@ -51,6 +51,12 @@ __authors__ = (
|
|||||||
'David Wagner',
|
'David Wagner',
|
||||||
'Juan C. Olivares',
|
'Juan C. Olivares',
|
||||||
'Mattias Harrysson',
|
'Mattias Harrysson',
|
||||||
|
'phaer',
|
||||||
|
'Sainyam Kapoor',
|
||||||
|
'Nicolas Évrard',
|
||||||
|
'Jason Normore',
|
||||||
|
'Hoje Lee',
|
||||||
|
'Adam Thalhammer',
|
||||||
)
|
)
|
||||||
|
|
||||||
__license__ = 'Public Domain'
|
__license__ = 'Public Domain'
|
||||||
@ -70,6 +76,7 @@ from .utils import (
|
|||||||
compat_getpass,
|
compat_getpass,
|
||||||
compat_print,
|
compat_print,
|
||||||
DateRange,
|
DateRange,
|
||||||
|
DEFAULT_OUTTMPL,
|
||||||
decodeOption,
|
decodeOption,
|
||||||
get_term_width,
|
get_term_width,
|
||||||
DownloadError,
|
DownloadError,
|
||||||
@ -90,6 +97,8 @@ from .extractor import gen_extractors
|
|||||||
from .version import __version__
|
from .version import __version__
|
||||||
from .YoutubeDL import YoutubeDL
|
from .YoutubeDL import YoutubeDL
|
||||||
from .postprocessor import (
|
from .postprocessor import (
|
||||||
|
AtomicParsleyPP,
|
||||||
|
FFmpegAudioFixPP,
|
||||||
FFmpegMetadataPP,
|
FFmpegMetadataPP,
|
||||||
FFmpegVideoConvertor,
|
FFmpegVideoConvertor,
|
||||||
FFmpegExtractAudioPP,
|
FFmpegExtractAudioPP,
|
||||||
@ -241,7 +250,7 @@ def parseOpts(overrideArguments=None):
|
|||||||
help='Use the specified HTTP/HTTPS proxy. Pass in an empty string (--proxy "") for direct connection')
|
help='Use the specified HTTP/HTTPS proxy. Pass in an empty string (--proxy "") for direct connection')
|
||||||
general.add_option('--no-check-certificate', action='store_true', dest='no_check_certificate', default=False, help='Suppress HTTPS certificate validation.')
|
general.add_option('--no-check-certificate', action='store_true', dest='no_check_certificate', default=False, help='Suppress HTTPS certificate validation.')
|
||||||
general.add_option(
|
general.add_option(
|
||||||
'--prefer-insecure', action='store_true', dest='prefer_insecure',
|
'--prefer-insecure', '--prefer-unsecure', action='store_true', dest='prefer_insecure',
|
||||||
help='Use an unencrypted connection to retrieve information about the video. (Currently supported only for YouTube)')
|
help='Use an unencrypted connection to retrieve information about the video. (Currently supported only for YouTube)')
|
||||||
general.add_option(
|
general.add_option(
|
||||||
'--cache-dir', dest='cachedir', default=get_cachedir(), metavar='DIR',
|
'--cache-dir', dest='cachedir', default=get_cachedir(), metavar='DIR',
|
||||||
@ -255,13 +264,17 @@ def parseOpts(overrideArguments=None):
|
|||||||
general.add_option(
|
general.add_option(
|
||||||
'--bidi-workaround', dest='bidi_workaround', action='store_true',
|
'--bidi-workaround', dest='bidi_workaround', action='store_true',
|
||||||
help=u'Work around terminals that lack bidirectional text support. Requires bidiv or fribidi executable in PATH')
|
help=u'Work around terminals that lack bidirectional text support. Requires bidiv or fribidi executable in PATH')
|
||||||
general.add_option('--default-search',
|
general.add_option(
|
||||||
dest='default_search', metavar='PREFIX',
|
'--default-search',
|
||||||
help='Use this prefix for unqualified URLs. For example "gvsearch2:" downloads two videos from google videos for youtube-dl "large apple". By default (with value "auto") youtube-dl guesses.')
|
dest='default_search', metavar='PREFIX',
|
||||||
|
help='Use this prefix for unqualified URLs. For example "gvsearch2:" downloads two videos from google videos for youtube-dl "large apple". By default (with value "auto") youtube-dl guesses.')
|
||||||
general.add_option(
|
general.add_option(
|
||||||
'--ignore-config',
|
'--ignore-config',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help='Do not read configuration files. When given in the global configuration file /etc/youtube-dl.conf: do not read the user configuration in ~/.config/youtube-dl.conf (%APPDATA%/youtube-dl/config.txt on Windows)')
|
help='Do not read configuration files. When given in the global configuration file /etc/youtube-dl.conf: do not read the user configuration in ~/.config/youtube-dl.conf (%APPDATA%/youtube-dl/config.txt on Windows)')
|
||||||
|
general.add_option(
|
||||||
|
'--encoding', dest='encoding', metavar='ENCODING',
|
||||||
|
help='Force the specified encoding (experimental)')
|
||||||
|
|
||||||
selection.add_option(
|
selection.add_option(
|
||||||
'--playlist-start',
|
'--playlist-start',
|
||||||
@ -395,7 +408,7 @@ def parseOpts(overrideArguments=None):
|
|||||||
help='simulate, quiet but print output format', default=False)
|
help='simulate, quiet but print output format', default=False)
|
||||||
verbosity.add_option('-j', '--dump-json',
|
verbosity.add_option('-j', '--dump-json',
|
||||||
action='store_true', dest='dumpjson',
|
action='store_true', dest='dumpjson',
|
||||||
help='simulate, quiet but print JSON information', default=False)
|
help='simulate, quiet but print JSON information. See --output for a description of available keys.', default=False)
|
||||||
verbosity.add_option('--newline',
|
verbosity.add_option('--newline',
|
||||||
action='store_true', dest='progress_with_newline', help='output progress bar as new lines', default=False)
|
action='store_true', dest='progress_with_newline', help='output progress bar as new lines', default=False)
|
||||||
verbosity.add_option('--no-progress',
|
verbosity.add_option('--no-progress',
|
||||||
@ -497,6 +510,8 @@ def parseOpts(overrideArguments=None):
|
|||||||
help='do not overwrite post-processed files; the post-processed files are overwritten by default')
|
help='do not overwrite post-processed files; the post-processed files are overwritten by default')
|
||||||
postproc.add_option('--embed-subs', action='store_true', dest='embedsubtitles', default=False,
|
postproc.add_option('--embed-subs', action='store_true', dest='embedsubtitles', default=False,
|
||||||
help='embed subtitles in the video (only for mp4 videos)')
|
help='embed subtitles in the video (only for mp4 videos)')
|
||||||
|
postproc.add_option('--embed-thumbnail', action='store_true', dest='embedthumbnail', default=False,
|
||||||
|
help='embed thumbnail in the audio as cover art')
|
||||||
postproc.add_option('--add-metadata', action='store_true', dest='addmetadata', default=False,
|
postproc.add_option('--add-metadata', action='store_true', dest='addmetadata', default=False,
|
||||||
help='write metadata to the video file')
|
help='write metadata to the video file')
|
||||||
postproc.add_option('--xattrs', action='store_true', dest='xattrs', default=False,
|
postproc.add_option('--xattrs', action='store_true', dest='xattrs', default=False,
|
||||||
@ -539,8 +554,6 @@ def parseOpts(overrideArguments=None):
|
|||||||
write_string(u'[debug] System config: ' + repr(_hide_login_info(systemConf)) + '\n')
|
write_string(u'[debug] System config: ' + repr(_hide_login_info(systemConf)) + '\n')
|
||||||
write_string(u'[debug] User config: ' + repr(_hide_login_info(userConf)) + '\n')
|
write_string(u'[debug] User config: ' + repr(_hide_login_info(userConf)) + '\n')
|
||||||
write_string(u'[debug] Command-line args: ' + repr(_hide_login_info(commandLineConf)) + '\n')
|
write_string(u'[debug] Command-line args: ' + repr(_hide_login_info(commandLineConf)) + '\n')
|
||||||
write_string(u'[debug] Encodings: locale %r, fs %r, out %r, pref: %r\n' %
|
|
||||||
(locale.getpreferredencoding(), sys.getfilesystemencoding(), sys.stdout.encoding, preferredencoding()))
|
|
||||||
|
|
||||||
return parser, opts, args
|
return parser, opts, args
|
||||||
|
|
||||||
@ -668,13 +681,13 @@ def _real_main(argv=None):
|
|||||||
if not opts.audioquality.isdigit():
|
if not opts.audioquality.isdigit():
|
||||||
parser.error(u'invalid audio quality specified')
|
parser.error(u'invalid audio quality specified')
|
||||||
if opts.recodevideo is not None:
|
if opts.recodevideo is not None:
|
||||||
if opts.recodevideo not in ['mp4', 'flv', 'webm', 'ogg']:
|
if opts.recodevideo not in ['mp4', 'flv', 'webm', 'ogg', 'mkv']:
|
||||||
parser.error(u'invalid video recode format specified')
|
parser.error(u'invalid video recode format specified')
|
||||||
if opts.date is not None:
|
if opts.date is not None:
|
||||||
date = DateRange.day(opts.date)
|
date = DateRange.day(opts.date)
|
||||||
else:
|
else:
|
||||||
date = DateRange(opts.dateafter, opts.datebefore)
|
date = DateRange(opts.dateafter, opts.datebefore)
|
||||||
if opts.default_search not in ('auto', None) and ':' not in opts.default_search:
|
if opts.default_search not in ('auto', 'auto_warning', None) and ':' not in opts.default_search:
|
||||||
parser.error(u'--default-search invalid; did you forget a colon (:) at the end?')
|
parser.error(u'--default-search invalid; did you forget a colon (:) at the end?')
|
||||||
|
|
||||||
# Do not download videos when there are audio-only formats
|
# Do not download videos when there are audio-only formats
|
||||||
@ -697,7 +710,7 @@ def _real_main(argv=None):
|
|||||||
or (opts.usetitle and u'%(title)s-%(id)s.%(ext)s')
|
or (opts.usetitle and u'%(title)s-%(id)s.%(ext)s')
|
||||||
or (opts.useid and u'%(id)s.%(ext)s')
|
or (opts.useid and u'%(id)s.%(ext)s')
|
||||||
or (opts.autonumber and u'%(autonumber)s-%(id)s.%(ext)s')
|
or (opts.autonumber and u'%(autonumber)s-%(id)s.%(ext)s')
|
||||||
or u'%(title)s-%(id)s.%(ext)s')
|
or DEFAULT_OUTTMPL)
|
||||||
if not os.path.splitext(outtmpl)[1] and opts.extractaudio:
|
if not os.path.splitext(outtmpl)[1] and opts.extractaudio:
|
||||||
parser.error(u'Cannot download a video and extract audio into the same'
|
parser.error(u'Cannot download a video and extract audio into the same'
|
||||||
u' file! Use "{0}.%(ext)s" instead of "{0}" as the output'
|
u' file! Use "{0}.%(ext)s" instead of "{0}" as the output'
|
||||||
@ -785,6 +798,7 @@ def _real_main(argv=None):
|
|||||||
'include_ads': opts.include_ads,
|
'include_ads': opts.include_ads,
|
||||||
'default_search': opts.default_search,
|
'default_search': opts.default_search,
|
||||||
'youtube_include_dash_manifest': opts.youtube_include_dash_manifest,
|
'youtube_include_dash_manifest': opts.youtube_include_dash_manifest,
|
||||||
|
'encoding': opts.encoding,
|
||||||
}
|
}
|
||||||
|
|
||||||
with YoutubeDL(ydl_opts) as ydl:
|
with YoutubeDL(ydl_opts) as ydl:
|
||||||
@ -803,6 +817,10 @@ def _real_main(argv=None):
|
|||||||
ydl.add_post_processor(FFmpegEmbedSubtitlePP(subtitlesformat=opts.subtitlesformat))
|
ydl.add_post_processor(FFmpegEmbedSubtitlePP(subtitlesformat=opts.subtitlesformat))
|
||||||
if opts.xattrs:
|
if opts.xattrs:
|
||||||
ydl.add_post_processor(XAttrMetadataPP())
|
ydl.add_post_processor(XAttrMetadataPP())
|
||||||
|
if opts.embedthumbnail:
|
||||||
|
if not opts.addmetadata:
|
||||||
|
ydl.add_post_processor(FFmpegAudioFixPP())
|
||||||
|
ydl.add_post_processor(AtomicParsleyPP())
|
||||||
|
|
||||||
# Update version
|
# Update version
|
||||||
if opts.update_self:
|
if opts.update_self:
|
||||||
|
@ -4,9 +4,10 @@ import sys
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
|
compat_str,
|
||||||
encodeFilename,
|
encodeFilename,
|
||||||
timeconvert,
|
|
||||||
format_bytes,
|
format_bytes,
|
||||||
|
timeconvert,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -173,7 +174,7 @@ class FileDownloader(object):
|
|||||||
return
|
return
|
||||||
os.rename(encodeFilename(old_filename), encodeFilename(new_filename))
|
os.rename(encodeFilename(old_filename), encodeFilename(new_filename))
|
||||||
except (IOError, OSError) as err:
|
except (IOError, OSError) as err:
|
||||||
self.report_error(u'unable to rename file: %s' % str(err))
|
self.report_error(u'unable to rename file: %s' % compat_str(err))
|
||||||
|
|
||||||
def try_utime(self, filename, last_modified_hdr):
|
def try_utime(self, filename, last_modified_hdr):
|
||||||
"""Try to set the last-modified time of the given file."""
|
"""Try to set the last-modified time of the given file."""
|
||||||
|
@ -297,6 +297,7 @@ class F4mFD(FileDownloader):
|
|||||||
break
|
break
|
||||||
frags_filenames.append(frag_filename)
|
frags_filenames.append(frag_filename)
|
||||||
|
|
||||||
|
dest_stream.close()
|
||||||
self.report_finish(format_bytes(state['downloaded_bytes']), time.time() - start)
|
self.report_finish(format_bytes(state['downloaded_bytes']), time.time() - start)
|
||||||
|
|
||||||
self.try_rename(tmpfilename, filename)
|
self.try_rename(tmpfilename, filename)
|
||||||
|
@ -14,6 +14,8 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class HttpFD(FileDownloader):
|
class HttpFD(FileDownloader):
|
||||||
|
_TEST_FILE_SIZE = 10241
|
||||||
|
|
||||||
def real_download(self, filename, info_dict):
|
def real_download(self, filename, info_dict):
|
||||||
url = info_dict['url']
|
url = info_dict['url']
|
||||||
tmpfilename = self.temp_name(filename)
|
tmpfilename = self.temp_name(filename)
|
||||||
@ -28,8 +30,10 @@ class HttpFD(FileDownloader):
|
|||||||
basic_request = compat_urllib_request.Request(url, None, headers)
|
basic_request = compat_urllib_request.Request(url, None, headers)
|
||||||
request = compat_urllib_request.Request(url, None, headers)
|
request = compat_urllib_request.Request(url, None, headers)
|
||||||
|
|
||||||
if self.params.get('test', False):
|
is_test = self.params.get('test', False)
|
||||||
request.add_header('Range', 'bytes=0-10240')
|
|
||||||
|
if is_test:
|
||||||
|
request.add_header('Range', 'bytes=0-%s' % str(self._TEST_FILE_SIZE - 1))
|
||||||
|
|
||||||
# Establish possible resume length
|
# Establish possible resume length
|
||||||
if os.path.isfile(encodeFilename(tmpfilename)):
|
if os.path.isfile(encodeFilename(tmpfilename)):
|
||||||
@ -100,6 +104,15 @@ class HttpFD(FileDownloader):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
data_len = data.info().get('Content-length', None)
|
data_len = data.info().get('Content-length', None)
|
||||||
|
|
||||||
|
# Range HTTP header may be ignored/unsupported by a webserver
|
||||||
|
# (e.g. extractor/scivee.py, extractor/bambuser.py).
|
||||||
|
# However, for a test we still would like to download just a piece of a file.
|
||||||
|
# To achieve this we limit data_len to _TEST_FILE_SIZE and manually control
|
||||||
|
# block size when downloading a file.
|
||||||
|
if is_test and (data_len is None or int(data_len) > self._TEST_FILE_SIZE):
|
||||||
|
data_len = self._TEST_FILE_SIZE
|
||||||
|
|
||||||
if data_len is not None:
|
if data_len is not None:
|
||||||
data_len = int(data_len) + resume_len
|
data_len = int(data_len) + resume_len
|
||||||
min_data_len = self.params.get("min_filesize", None)
|
min_data_len = self.params.get("min_filesize", None)
|
||||||
@ -118,7 +131,7 @@ class HttpFD(FileDownloader):
|
|||||||
while True:
|
while True:
|
||||||
# Download and write
|
# Download and write
|
||||||
before = time.time()
|
before = time.time()
|
||||||
data_block = data.read(block_size)
|
data_block = data.read(block_size if not is_test else min(block_size, data_len - byte_counter))
|
||||||
after = time.time()
|
after = time.time()
|
||||||
if len(data_block) == 0:
|
if len(data_block) == 0:
|
||||||
break
|
break
|
||||||
@ -162,6 +175,9 @@ class HttpFD(FileDownloader):
|
|||||||
'speed': speed,
|
'speed': speed,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if is_test and byte_counter == data_len:
|
||||||
|
break
|
||||||
|
|
||||||
# Apply rate limit
|
# Apply rate limit
|
||||||
self.slow_down(start, byte_counter - resume_len)
|
self.slow_down(start, byte_counter - resume_len)
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ from .common import FileDownloader
|
|||||||
from ..utils import (
|
from ..utils import (
|
||||||
encodeFilename,
|
encodeFilename,
|
||||||
format_bytes,
|
format_bytes,
|
||||||
|
compat_str,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -127,7 +128,10 @@ class RtmpFD(FileDownloader):
|
|||||||
basic_args += ['--flashVer', flash_version]
|
basic_args += ['--flashVer', flash_version]
|
||||||
if live:
|
if live:
|
||||||
basic_args += ['--live']
|
basic_args += ['--live']
|
||||||
if conn:
|
if isinstance(conn, list):
|
||||||
|
for entry in conn:
|
||||||
|
basic_args += ['--conn', entry]
|
||||||
|
elif isinstance(conn, compat_str):
|
||||||
basic_args += ['--conn', conn]
|
basic_args += ['--conn', conn]
|
||||||
args = basic_args + [[], ['--resume', '--skip', '1']][not live and self.params.get('continuedl', False)]
|
args = basic_args + [[], ['--resume', '--skip', '1']][not live and self.params.get('continuedl', False)]
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ from .auengine import AUEngineIE
|
|||||||
from .bambuser import BambuserIE, BambuserChannelIE
|
from .bambuser import BambuserIE, BambuserChannelIE
|
||||||
from .bandcamp import BandcampIE, BandcampAlbumIE
|
from .bandcamp import BandcampIE, BandcampAlbumIE
|
||||||
from .bbccouk import BBCCoUkIE
|
from .bbccouk import BBCCoUkIE
|
||||||
|
from .bilibili import BiliBiliIE
|
||||||
from .blinkx import BlinkxIE
|
from .blinkx import BlinkxIE
|
||||||
from .bliptv import BlipTVIE, BlipTVUserIE
|
from .bliptv import BlipTVIE, BlipTVUserIE
|
||||||
from .bloomberg import BloombergIE
|
from .bloomberg import BloombergIE
|
||||||
@ -32,6 +33,7 @@ from .canal13cl import Canal13clIE
|
|||||||
from .canalplus import CanalplusIE
|
from .canalplus import CanalplusIE
|
||||||
from .canalc2 import Canalc2IE
|
from .canalc2 import Canalc2IE
|
||||||
from .cbs import CBSIE
|
from .cbs import CBSIE
|
||||||
|
from .cbsnews import CBSNewsIE
|
||||||
from .ceskatelevize import CeskaTelevizeIE
|
from .ceskatelevize import CeskaTelevizeIE
|
||||||
from .channel9 import Channel9IE
|
from .channel9 import Channel9IE
|
||||||
from .chilloutzone import ChilloutzoneIE
|
from .chilloutzone import ChilloutzoneIE
|
||||||
@ -39,7 +41,9 @@ from .cinemassacre import CinemassacreIE
|
|||||||
from .clipfish import ClipfishIE
|
from .clipfish import ClipfishIE
|
||||||
from .cliphunter import CliphunterIE
|
from .cliphunter import CliphunterIE
|
||||||
from .clipsyndicate import ClipsyndicateIE
|
from .clipsyndicate import ClipsyndicateIE
|
||||||
|
from .clubic import ClubicIE
|
||||||
from .cmt import CMTIE
|
from .cmt import CMTIE
|
||||||
|
from .cnet import CNETIE
|
||||||
from .cnn import (
|
from .cnn import (
|
||||||
CNNIE,
|
CNNIE,
|
||||||
CNNBlogsIE,
|
CNNBlogsIE,
|
||||||
@ -61,12 +65,14 @@ from .dotsub import DotsubIE
|
|||||||
from .dreisat import DreiSatIE
|
from .dreisat import DreiSatIE
|
||||||
from .defense import DefenseGouvFrIE
|
from .defense import DefenseGouvFrIE
|
||||||
from .discovery import DiscoveryIE
|
from .discovery import DiscoveryIE
|
||||||
|
from .divxstage import DivxStageIE
|
||||||
from .dropbox import DropboxIE
|
from .dropbox import DropboxIE
|
||||||
from .ebaumsworld import EbaumsWorldIE
|
from .ebaumsworld import EbaumsWorldIE
|
||||||
from .ehow import EHowIE
|
from .ehow import EHowIE
|
||||||
from .eighttracks import EightTracksIE
|
from .eighttracks import EightTracksIE
|
||||||
from .eitb import EitbIE
|
from .eitb import EitbIE
|
||||||
from .elpais import ElPaisIE
|
from .elpais import ElPaisIE
|
||||||
|
from .empflix import EmpflixIE
|
||||||
from .engadget import EngadgetIE
|
from .engadget import EngadgetIE
|
||||||
from .escapist import EscapistIE
|
from .escapist import EscapistIE
|
||||||
from .everyonesmixtape import EveryonesMixtapeIE
|
from .everyonesmixtape import EveryonesMixtapeIE
|
||||||
@ -74,6 +80,7 @@ from .exfm import ExfmIE
|
|||||||
from .extremetube import ExtremeTubeIE
|
from .extremetube import ExtremeTubeIE
|
||||||
from .facebook import FacebookIE
|
from .facebook import FacebookIE
|
||||||
from .faz import FazIE
|
from .faz import FazIE
|
||||||
|
from .fc2 import FC2IE
|
||||||
from .firstpost import FirstpostIE
|
from .firstpost import FirstpostIE
|
||||||
from .firsttv import FirstTVIE
|
from .firsttv import FirstTVIE
|
||||||
from .fivemin import FiveMinIE
|
from .fivemin import FiveMinIE
|
||||||
@ -83,6 +90,7 @@ from .fktv import (
|
|||||||
)
|
)
|
||||||
from .flickr import FlickrIE
|
from .flickr import FlickrIE
|
||||||
from .fourtube import FourTubeIE
|
from .fourtube import FourTubeIE
|
||||||
|
from .franceculture import FranceCultureIE
|
||||||
from .franceinter import FranceInterIE
|
from .franceinter import FranceInterIE
|
||||||
from .francetv import (
|
from .francetv import (
|
||||||
PluzzIE,
|
PluzzIE,
|
||||||
@ -103,10 +111,12 @@ from .googleplus import GooglePlusIE
|
|||||||
from .googlesearch import GoogleSearchIE
|
from .googlesearch import GoogleSearchIE
|
||||||
from .hark import HarkIE
|
from .hark import HarkIE
|
||||||
from .helsinki import HelsinkiIE
|
from .helsinki import HelsinkiIE
|
||||||
|
from .hentaistigma import HentaiStigmaIE
|
||||||
from .hotnewhiphop import HotNewHipHopIE
|
from .hotnewhiphop import HotNewHipHopIE
|
||||||
from .howcast import HowcastIE
|
from .howcast import HowcastIE
|
||||||
from .huffpost import HuffPostIE
|
from .huffpost import HuffPostIE
|
||||||
from .hypem import HypemIE
|
from .hypem import HypemIE
|
||||||
|
from .iconosquare import IconosquareIE
|
||||||
from .ign import IGNIE, OneUPIE
|
from .ign import IGNIE, OneUPIE
|
||||||
from .imdb import (
|
from .imdb import (
|
||||||
ImdbIE,
|
ImdbIE,
|
||||||
@ -152,10 +162,15 @@ from .mixcloud import MixcloudIE
|
|||||||
from .mpora import MporaIE
|
from .mpora import MporaIE
|
||||||
from .mofosex import MofosexIE
|
from .mofosex import MofosexIE
|
||||||
from .mooshare import MooshareIE
|
from .mooshare import MooshareIE
|
||||||
|
from .morningstar import MorningstarIE
|
||||||
|
from .motorsport import MotorsportIE
|
||||||
|
from .moviezine import MoviezineIE
|
||||||
|
from .movshare import MovShareIE
|
||||||
from .mtv import (
|
from .mtv import (
|
||||||
MTVIE,
|
MTVIE,
|
||||||
MTVIggyIE,
|
MTVIggyIE,
|
||||||
)
|
)
|
||||||
|
from .musicplayon import MusicPlayOnIE
|
||||||
from .muzu import MuzuTVIE
|
from .muzu import MuzuTVIE
|
||||||
from .myspace import MySpaceIE
|
from .myspace import MySpaceIE
|
||||||
from .myspass import MySpassIE
|
from .myspass import MySpassIE
|
||||||
@ -169,14 +184,24 @@ from .nbc import (
|
|||||||
from .ndr import NDRIE
|
from .ndr import NDRIE
|
||||||
from .ndtv import NDTVIE
|
from .ndtv import NDTVIE
|
||||||
from .newgrounds import NewgroundsIE
|
from .newgrounds import NewgroundsIE
|
||||||
|
from .newstube import NewstubeIE
|
||||||
from .nfb import NFBIE
|
from .nfb import NFBIE
|
||||||
from .nhl import NHLIE, NHLVideocenterIE
|
from .nhl import NHLIE, NHLVideocenterIE
|
||||||
from .niconico import NiconicoIE
|
from .niconico import NiconicoIE
|
||||||
from .ninegag import NineGagIE
|
from .ninegag import NineGagIE
|
||||||
|
from .noco import NocoIE
|
||||||
from .normalboots import NormalbootsIE
|
from .normalboots import NormalbootsIE
|
||||||
from .novamov import NovaMovIE
|
from .novamov import NovaMovIE
|
||||||
from .nowness import NownessIE
|
from .nowness import NownessIE
|
||||||
from .nowvideo import NowVideoIE
|
from .nowvideo import NowVideoIE
|
||||||
|
from .nrk import (
|
||||||
|
NRKIE,
|
||||||
|
NRKTVIE,
|
||||||
|
)
|
||||||
|
from .ntv import NTVIE
|
||||||
|
from .nytimes import NYTimesIE
|
||||||
|
from .nuvid import NuvidIE
|
||||||
|
from .oe1 import OE1IE
|
||||||
from .ooyala import OoyalaIE
|
from .ooyala import OoyalaIE
|
||||||
from .orf import ORFIE
|
from .orf import ORFIE
|
||||||
from .parliamentliveuk import ParliamentLiveUKIE
|
from .parliamentliveuk import ParliamentLiveUKIE
|
||||||
@ -196,8 +221,10 @@ from .ringtv import RingTVIE
|
|||||||
from .ro220 import Ro220IE
|
from .ro220 import Ro220IE
|
||||||
from .rottentomatoes import RottenTomatoesIE
|
from .rottentomatoes import RottenTomatoesIE
|
||||||
from .roxwel import RoxwelIE
|
from .roxwel import RoxwelIE
|
||||||
|
from .rtbf import RTBFIE
|
||||||
from .rtlnow import RTLnowIE
|
from .rtlnow import RTLnowIE
|
||||||
from .rts import RTSIE
|
from .rts import RTSIE
|
||||||
|
from .rtve import RTVEALaCartaIE
|
||||||
from .rutube import (
|
from .rutube import (
|
||||||
RutubeIE,
|
RutubeIE,
|
||||||
RutubeChannelIE,
|
RutubeChannelIE,
|
||||||
@ -206,9 +233,11 @@ from .rutube import (
|
|||||||
)
|
)
|
||||||
from .rutv import RUTVIE
|
from .rutv import RUTVIE
|
||||||
from .savefrom import SaveFromIE
|
from .savefrom import SaveFromIE
|
||||||
|
from .scivee import SciVeeIE
|
||||||
from .servingsys import ServingSysIE
|
from .servingsys import ServingSysIE
|
||||||
from .sina import SinaIE
|
from .sina import SinaIE
|
||||||
from .slideshare import SlideshareIE
|
from .slideshare import SlideshareIE
|
||||||
|
from .slutload import SlutloadIE
|
||||||
from .smotri import (
|
from .smotri import (
|
||||||
SmotriIE,
|
SmotriIE,
|
||||||
SmotriCommunityIE,
|
SmotriCommunityIE,
|
||||||
@ -216,7 +245,12 @@ from .smotri import (
|
|||||||
SmotriBroadcastIE,
|
SmotriBroadcastIE,
|
||||||
)
|
)
|
||||||
from .sohu import SohuIE
|
from .sohu import SohuIE
|
||||||
from .soundcloud import SoundcloudIE, SoundcloudSetIE, SoundcloudUserIE
|
from .soundcloud import (
|
||||||
|
SoundcloudIE,
|
||||||
|
SoundcloudSetIE,
|
||||||
|
SoundcloudUserIE,
|
||||||
|
SoundcloudPlaylistIE
|
||||||
|
)
|
||||||
from .southparkstudios import (
|
from .southparkstudios import (
|
||||||
SouthParkStudiosIE,
|
SouthParkStudiosIE,
|
||||||
SouthparkDeIE,
|
SouthparkDeIE,
|
||||||
@ -226,10 +260,10 @@ from .spankwire import SpankwireIE
|
|||||||
from .spiegel import SpiegelIE
|
from .spiegel import SpiegelIE
|
||||||
from .spike import SpikeIE
|
from .spike import SpikeIE
|
||||||
from .stanfordoc import StanfordOpenClassroomIE
|
from .stanfordoc import StanfordOpenClassroomIE
|
||||||
from .statigram import StatigramIE
|
|
||||||
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 .swrmediathek import SWRMediathekIE
|
||||||
from .syfy import SyfyIE
|
from .syfy import SyfyIE
|
||||||
from .sztvhu import SztvHuIE
|
from .sztvhu import SztvHuIE
|
||||||
from .teamcoco import TeamcocoIE
|
from .teamcoco import TeamcocoIE
|
||||||
@ -240,6 +274,7 @@ from .tf1 import TF1IE
|
|||||||
from .theplatform import ThePlatformIE
|
from .theplatform import ThePlatformIE
|
||||||
from .thisav import ThisAVIE
|
from .thisav import ThisAVIE
|
||||||
from .tinypic import TinyPicIE
|
from .tinypic import TinyPicIE
|
||||||
|
from .tlc import TlcIE, TlcDeIE
|
||||||
from .toutv import TouTvIE
|
from .toutv import TouTvIE
|
||||||
from .toypics import ToypicsUserIE, ToypicsIE
|
from .toypics import ToypicsUserIE, ToypicsIE
|
||||||
from .traileraddict import TrailerAddictIE
|
from .traileraddict import TrailerAddictIE
|
||||||
@ -269,6 +304,8 @@ from .videodetective import VideoDetectiveIE
|
|||||||
from .videolecturesnet import VideoLecturesNetIE
|
from .videolecturesnet import VideoLecturesNetIE
|
||||||
from .videofyme import VideofyMeIE
|
from .videofyme import VideofyMeIE
|
||||||
from .videopremium import VideoPremiumIE
|
from .videopremium import VideoPremiumIE
|
||||||
|
from .videott import VideoTtIE
|
||||||
|
from .videoweed import VideoWeedIE
|
||||||
from .vimeo import (
|
from .vimeo import (
|
||||||
VimeoIE,
|
VimeoIE,
|
||||||
VimeoChannelIE,
|
VimeoChannelIE,
|
||||||
@ -276,14 +313,23 @@ from .vimeo import (
|
|||||||
VimeoAlbumIE,
|
VimeoAlbumIE,
|
||||||
VimeoGroupsIE,
|
VimeoGroupsIE,
|
||||||
VimeoReviewIE,
|
VimeoReviewIE,
|
||||||
|
VimeoWatchLaterIE,
|
||||||
|
)
|
||||||
|
from .vine import (
|
||||||
|
VineIE,
|
||||||
|
VineUserIE,
|
||||||
)
|
)
|
||||||
from .vine import VineIE
|
|
||||||
from .viki import VikiIE
|
from .viki import VikiIE
|
||||||
from .vk import VKIE
|
from .vk import VKIE
|
||||||
from .vube import VubeIE
|
from .vube import VubeIE
|
||||||
|
from .vuclip import VuClipIE
|
||||||
from .washingtonpost import WashingtonPostIE
|
from .washingtonpost import WashingtonPostIE
|
||||||
from .wat import WatIE
|
from .wat import WatIE
|
||||||
from .wdr import WDRIE
|
from .wdr import (
|
||||||
|
WDRIE,
|
||||||
|
WDRMobileIE,
|
||||||
|
WDRMausIE,
|
||||||
|
)
|
||||||
from .weibo import WeiboIE
|
from .weibo import WeiboIE
|
||||||
from .wimp import WimpIE
|
from .wimp import WimpIE
|
||||||
from .wistia import WistiaIE
|
from .wistia import WistiaIE
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import datetime
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
@ -16,6 +15,7 @@ class AftonbladetIE(InfoExtractor):
|
|||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Vulkanutbrott i rymden - nu släpper NASA bilderna',
|
'title': 'Vulkanutbrott i rymden - nu släpper NASA bilderna',
|
||||||
'description': 'Jupiters måne mest aktiv av alla himlakroppar',
|
'description': 'Jupiters måne mest aktiv av alla himlakroppar',
|
||||||
|
'timestamp': 1394142732,
|
||||||
'upload_date': '20140306',
|
'upload_date': '20140306',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -27,17 +27,17 @@ class AftonbladetIE(InfoExtractor):
|
|||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
# find internal video meta data
|
# find internal video meta data
|
||||||
META_URL = 'http://aftonbladet-play.drlib.aptoma.no/video/%s.json'
|
meta_url = 'http://aftonbladet-play.drlib.aptoma.no/video/%s.json'
|
||||||
internal_meta_id = self._html_search_regex(
|
internal_meta_id = self._html_search_regex(
|
||||||
r'data-aptomaId="([\w\d]+)"', webpage, 'internal_meta_id')
|
r'data-aptomaId="([\w\d]+)"', webpage, 'internal_meta_id')
|
||||||
internal_meta_url = META_URL % internal_meta_id
|
internal_meta_url = meta_url % internal_meta_id
|
||||||
internal_meta_json = self._download_json(
|
internal_meta_json = self._download_json(
|
||||||
internal_meta_url, video_id, 'Downloading video meta data')
|
internal_meta_url, video_id, 'Downloading video meta data')
|
||||||
|
|
||||||
# find internal video formats
|
# find internal video formats
|
||||||
FORMATS_URL = 'http://aftonbladet-play.videodata.drvideo.aptoma.no/actions/video/?id=%s'
|
format_url = 'http://aftonbladet-play.videodata.drvideo.aptoma.no/actions/video/?id=%s'
|
||||||
internal_video_id = internal_meta_json['videoId']
|
internal_video_id = internal_meta_json['videoId']
|
||||||
internal_formats_url = FORMATS_URL % internal_video_id
|
internal_formats_url = format_url % internal_video_id
|
||||||
internal_formats_json = self._download_json(
|
internal_formats_json = self._download_json(
|
||||||
internal_formats_url, video_id, 'Downloading video formats')
|
internal_formats_url, video_id, 'Downloading video formats')
|
||||||
|
|
||||||
@ -54,16 +54,13 @@ class AftonbladetIE(InfoExtractor):
|
|||||||
})
|
})
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
timestamp = datetime.datetime.fromtimestamp(internal_meta_json['timePublished'])
|
|
||||||
upload_date = timestamp.strftime('%Y%m%d')
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': internal_meta_json['title'],
|
'title': internal_meta_json['title'],
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
'thumbnail': internal_meta_json['imageUrl'],
|
'thumbnail': internal_meta_json['imageUrl'],
|
||||||
'description': internal_meta_json['shortPreamble'],
|
'description': internal_meta_json['shortPreamble'],
|
||||||
'upload_date': upload_date,
|
'timestamp': internal_meta_json['timePublished'],
|
||||||
'duration': internal_meta_json['duration'],
|
'duration': internal_meta_json['duration'],
|
||||||
'view_count': internal_meta_json['views'],
|
'view_count': internal_meta_json['views'],
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,18 @@ from .fivemin import FiveMinIE
|
|||||||
|
|
||||||
class AolIE(InfoExtractor):
|
class AolIE(InfoExtractor):
|
||||||
IE_NAME = 'on.aol.com'
|
IE_NAME = 'on.aol.com'
|
||||||
_VALID_URL = r'http://on\.aol\.com/video/.*-(?P<id>\d+)($|\?)'
|
_VALID_URL = r'''(?x)
|
||||||
|
(?:
|
||||||
|
aol-video:|
|
||||||
|
http://on\.aol\.com/
|
||||||
|
(?:
|
||||||
|
video/.*-|
|
||||||
|
playlist/(?P<playlist_display_id>[^/?#]+?)-(?P<playlist_id>[0-9]+)[?#].*_videoid=
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(?P<id>[0-9]+)
|
||||||
|
(?:$|\?)
|
||||||
|
'''
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://on.aol.com/video/u-s--official-warns-of-largest-ever-irs-phone-scam-518167793?icid=OnHomepageC2Wide_MustSee_Img',
|
'url': 'http://on.aol.com/video/u-s--official-warns-of-largest-ever-irs-phone-scam-518167793?icid=OnHomepageC2Wide_MustSee_Img',
|
||||||
@ -24,5 +35,31 @@ class AolIE(InfoExtractor):
|
|||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
video_id = mobj.group('id')
|
video_id = mobj.group('id')
|
||||||
self.to_screen('Downloading 5min.com video %s' % video_id)
|
|
||||||
|
playlist_id = mobj.group('playlist_id')
|
||||||
|
if playlist_id and not self._downloader.params.get('noplaylist'):
|
||||||
|
self.to_screen('Downloading playlist %s - add --no-playlist to just download video %s' % (playlist_id, video_id))
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, playlist_id)
|
||||||
|
title = self._html_search_regex(
|
||||||
|
r'<h1 class="video-title[^"]*">(.+?)</h1>', webpage, 'title')
|
||||||
|
playlist_html = self._search_regex(
|
||||||
|
r"(?s)<ul\s+class='video-related[^']*'>(.*?)</ul>", webpage,
|
||||||
|
'playlist HTML')
|
||||||
|
entries = [{
|
||||||
|
'_type': 'url',
|
||||||
|
'url': 'aol-video:%s' % m.group('id'),
|
||||||
|
'ie_key': 'Aol',
|
||||||
|
} for m in re.finditer(
|
||||||
|
r"<a\s+href='.*videoid=(?P<id>[0-9]+)'\s+class='video-thumb'>",
|
||||||
|
playlist_html)]
|
||||||
|
|
||||||
|
return {
|
||||||
|
'_type': 'playlist',
|
||||||
|
'id': playlist_id,
|
||||||
|
'display_id': mobj.group('playlist_display_id'),
|
||||||
|
'title': title,
|
||||||
|
'entries': entries,
|
||||||
|
}
|
||||||
|
|
||||||
return FiveMinIE._build_result(video_id)
|
return FiveMinIE._build_result(video_id)
|
||||||
|
@ -6,7 +6,6 @@ import json
|
|||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
compat_urlparse,
|
compat_urlparse,
|
||||||
determine_ext,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -16,9 +15,10 @@ class AppleTrailersIE(InfoExtractor):
|
|||||||
"url": "http://trailers.apple.com/trailers/wb/manofsteel/",
|
"url": "http://trailers.apple.com/trailers/wb/manofsteel/",
|
||||||
"playlist": [
|
"playlist": [
|
||||||
{
|
{
|
||||||
"file": "manofsteel-trailer4.mov",
|
|
||||||
"md5": "d97a8e575432dbcb81b7c3acb741f8a8",
|
"md5": "d97a8e575432dbcb81b7c3acb741f8a8",
|
||||||
"info_dict": {
|
"info_dict": {
|
||||||
|
"id": "manofsteel-trailer4",
|
||||||
|
"ext": "mov",
|
||||||
"duration": 111,
|
"duration": 111,
|
||||||
"title": "Trailer 4",
|
"title": "Trailer 4",
|
||||||
"upload_date": "20130523",
|
"upload_date": "20130523",
|
||||||
@ -26,9 +26,10 @@ class AppleTrailersIE(InfoExtractor):
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file": "manofsteel-trailer3.mov",
|
|
||||||
"md5": "b8017b7131b721fb4e8d6f49e1df908c",
|
"md5": "b8017b7131b721fb4e8d6f49e1df908c",
|
||||||
"info_dict": {
|
"info_dict": {
|
||||||
|
"id": "manofsteel-trailer3",
|
||||||
|
"ext": "mov",
|
||||||
"duration": 182,
|
"duration": 182,
|
||||||
"title": "Trailer 3",
|
"title": "Trailer 3",
|
||||||
"upload_date": "20130417",
|
"upload_date": "20130417",
|
||||||
@ -36,9 +37,10 @@ class AppleTrailersIE(InfoExtractor):
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file": "manofsteel-trailer.mov",
|
|
||||||
"md5": "d0f1e1150989b9924679b441f3404d48",
|
"md5": "d0f1e1150989b9924679b441f3404d48",
|
||||||
"info_dict": {
|
"info_dict": {
|
||||||
|
"id": "manofsteel-trailer",
|
||||||
|
"ext": "mov",
|
||||||
"duration": 148,
|
"duration": 148,
|
||||||
"title": "Trailer",
|
"title": "Trailer",
|
||||||
"upload_date": "20121212",
|
"upload_date": "20121212",
|
||||||
@ -46,15 +48,16 @@ class AppleTrailersIE(InfoExtractor):
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file": "manofsteel-teaser.mov",
|
|
||||||
"md5": "5fe08795b943eb2e757fa95cb6def1cb",
|
"md5": "5fe08795b943eb2e757fa95cb6def1cb",
|
||||||
"info_dict": {
|
"info_dict": {
|
||||||
|
"id": "manofsteel-teaser",
|
||||||
|
"ext": "mov",
|
||||||
"duration": 93,
|
"duration": 93,
|
||||||
"title": "Teaser",
|
"title": "Teaser",
|
||||||
"upload_date": "20120721",
|
"upload_date": "20120721",
|
||||||
"uploader_id": "wb",
|
"uploader_id": "wb",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,16 +68,16 @@ class AppleTrailersIE(InfoExtractor):
|
|||||||
movie = mobj.group('movie')
|
movie = mobj.group('movie')
|
||||||
uploader_id = mobj.group('company')
|
uploader_id = mobj.group('company')
|
||||||
|
|
||||||
playlist_url = compat_urlparse.urljoin(url, u'includes/playlists/itunes.inc')
|
playlist_url = compat_urlparse.urljoin(url, 'includes/playlists/itunes.inc')
|
||||||
def fix_html(s):
|
def fix_html(s):
|
||||||
s = re.sub(r'(?s)<script[^<]*?>.*?</script>', u'', s)
|
s = re.sub(r'(?s)<script[^<]*?>.*?</script>', '', s)
|
||||||
s = re.sub(r'<img ([^<]*?)>', r'<img \1/>', s)
|
s = re.sub(r'<img ([^<]*?)>', r'<img \1/>', s)
|
||||||
# The ' in the onClick attributes are not escaped, it couldn't be parsed
|
# The ' in the onClick attributes are not escaped, it couldn't be parsed
|
||||||
# like: http://trailers.apple.com/trailers/wb/gravity/
|
# like: http://trailers.apple.com/trailers/wb/gravity/
|
||||||
def _clean_json(m):
|
def _clean_json(m):
|
||||||
return u'iTunes.playURL(%s);' % m.group(1).replace('\'', ''')
|
return 'iTunes.playURL(%s);' % m.group(1).replace('\'', ''')
|
||||||
s = re.sub(self._JSON_RE, _clean_json, s)
|
s = re.sub(self._JSON_RE, _clean_json, s)
|
||||||
s = u'<html>' + s + u'</html>'
|
s = '<html>' + s + u'</html>'
|
||||||
return s
|
return s
|
||||||
doc = self._download_xml(playlist_url, movie, transform_source=fix_html)
|
doc = self._download_xml(playlist_url, movie, transform_source=fix_html)
|
||||||
|
|
||||||
@ -82,7 +85,7 @@ class AppleTrailersIE(InfoExtractor):
|
|||||||
for li in doc.findall('./div/ul/li'):
|
for li in doc.findall('./div/ul/li'):
|
||||||
on_click = li.find('.//a').attrib['onClick']
|
on_click = li.find('.//a').attrib['onClick']
|
||||||
trailer_info_json = self._search_regex(self._JSON_RE,
|
trailer_info_json = self._search_regex(self._JSON_RE,
|
||||||
on_click, u'trailer info')
|
on_click, 'trailer info')
|
||||||
trailer_info = json.loads(trailer_info_json)
|
trailer_info = json.loads(trailer_info_json)
|
||||||
title = trailer_info['title']
|
title = trailer_info['title']
|
||||||
video_id = movie + '-' + re.sub(r'[^a-zA-Z0-9]', '', title).lower()
|
video_id = movie + '-' + re.sub(r'[^a-zA-Z0-9]', '', title).lower()
|
||||||
@ -98,8 +101,7 @@ class AppleTrailersIE(InfoExtractor):
|
|||||||
first_url = trailer_info['url']
|
first_url = trailer_info['url']
|
||||||
trailer_id = first_url.split('/')[-1].rpartition('_')[0].lower()
|
trailer_id = first_url.split('/')[-1].rpartition('_')[0].lower()
|
||||||
settings_json_url = compat_urlparse.urljoin(url, 'includes/settings/%s.json' % trailer_id)
|
settings_json_url = compat_urlparse.urljoin(url, 'includes/settings/%s.json' % trailer_id)
|
||||||
settings_json = self._download_webpage(settings_json_url, trailer_id, u'Downloading settings json')
|
settings = self._download_json(settings_json_url, trailer_id, 'Downloading settings json')
|
||||||
settings = json.loads(settings_json)
|
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
for format in settings['metadata']['sizes']:
|
for format in settings['metadata']['sizes']:
|
||||||
@ -107,7 +109,6 @@ class AppleTrailersIE(InfoExtractor):
|
|||||||
format_url = re.sub(r'_(\d*p.mov)', r'_h\1', format['src'])
|
format_url = re.sub(r'_(\d*p.mov)', r'_h\1', format['src'])
|
||||||
formats.append({
|
formats.append({
|
||||||
'url': format_url,
|
'url': format_url,
|
||||||
'ext': determine_ext(format_url),
|
|
||||||
'format': format['type'],
|
'format': format['type'],
|
||||||
'width': format['width'],
|
'width': format['width'],
|
||||||
'height': int(format['height']),
|
'height': int(format['height']),
|
||||||
|
@ -38,7 +38,9 @@ class ARDIE(InfoExtractor):
|
|||||||
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'<h1(?:\s+class="boxTopHeadline")?>(.*?)</h1>', webpage, 'title')
|
[r'<h1(?:\s+class="boxTopHeadline")?>(.*?)</h1>',
|
||||||
|
r'<h4 class="headline">(.*?)</h4>'],
|
||||||
|
webpage, 'title')
|
||||||
description = self._html_search_meta(
|
description = self._html_search_meta(
|
||||||
'dcterms.abstract', webpage, 'description')
|
'dcterms.abstract', webpage, 'description')
|
||||||
thumbnail = self._og_search_thumbnail(webpage)
|
thumbnail = self._og_search_thumbnail(webpage)
|
||||||
|
@ -74,7 +74,8 @@ class ArteTVPlus7IE(InfoExtractor):
|
|||||||
return self._extract_from_webpage(webpage, video_id, lang)
|
return self._extract_from_webpage(webpage, video_id, lang)
|
||||||
|
|
||||||
def _extract_from_webpage(self, webpage, video_id, lang):
|
def _extract_from_webpage(self, webpage, video_id, lang):
|
||||||
json_url = self._html_search_regex(r'arte_vp_url="(.*?)"', webpage, 'json url')
|
json_url = self._html_search_regex(
|
||||||
|
r'arte_vp_url="(.*?)"', webpage, 'json vp url')
|
||||||
return self._extract_from_json_url(json_url, video_id, lang)
|
return self._extract_from_json_url(json_url, video_id, lang)
|
||||||
|
|
||||||
def _extract_from_json_url(self, json_url, video_id, lang):
|
def _extract_from_json_url(self, json_url, video_id, lang):
|
||||||
@ -120,14 +121,17 @@ class ArteTVPlus7IE(InfoExtractor):
|
|||||||
return ['HQ', 'MQ', 'EQ', 'SQ'].index(f['quality'])
|
return ['HQ', 'MQ', 'EQ', 'SQ'].index(f['quality'])
|
||||||
else:
|
else:
|
||||||
def sort_key(f):
|
def sort_key(f):
|
||||||
|
versionCode = f.get('versionCode')
|
||||||
|
if versionCode is None:
|
||||||
|
versionCode = ''
|
||||||
return (
|
return (
|
||||||
# Sort first by quality
|
# Sort first by quality
|
||||||
int(f.get('height',-1)),
|
int(f.get('height', -1)),
|
||||||
int(f.get('bitrate',-1)),
|
int(f.get('bitrate', -1)),
|
||||||
# The original version with subtitles has lower relevance
|
# The original version with subtitles has lower relevance
|
||||||
re.match(r'VO-ST(F|A)', f.get('versionCode', '')) is None,
|
re.match(r'VO-ST(F|A)', versionCode) is None,
|
||||||
# The version with sourds/mal subtitles has also lower relevance
|
# The version with sourds/mal subtitles has also lower relevance
|
||||||
re.match(r'VO?(F|A)-STM\1', f.get('versionCode', '')) is None,
|
re.match(r'VO?(F|A)-STM\1', versionCode) is None,
|
||||||
# Prefer http downloads over m3u8
|
# Prefer http downloads over m3u8
|
||||||
0 if f['url'].endswith('m3u8') else 1,
|
0 if f['url'].endswith('m3u8') else 1,
|
||||||
)
|
)
|
||||||
|
@ -12,14 +12,14 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class BandcampIE(InfoExtractor):
|
class BandcampIE(InfoExtractor):
|
||||||
_VALID_URL = r'http://.*?\.bandcamp\.com/track/(?P<title>.*)'
|
_VALID_URL = r'https?://.*?\.bandcamp\.com/track/(?P<title>.*)'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://youtube-dl.bandcamp.com/track/youtube-dl-test-song',
|
'url': 'http://youtube-dl.bandcamp.com/track/youtube-dl-test-song',
|
||||||
'file': '1812978515.mp3',
|
'file': '1812978515.mp3',
|
||||||
'md5': 'c557841d5e50261777a6585648adf439',
|
'md5': 'c557841d5e50261777a6585648adf439',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
"title": "youtube-dl \"'/\\\u00e4\u21ad - youtube-dl test song \"'/\\\u00e4\u21ad",
|
"title": "youtube-dl \"'/\\\u00e4\u21ad - youtube-dl test song \"'/\\\u00e4\u21ad",
|
||||||
"duration": 10,
|
"duration": 9.8485,
|
||||||
},
|
},
|
||||||
'_skip': 'There is a limit of 200 free downloads / month for the test song'
|
'_skip': 'There is a limit of 200 free downloads / month for the test song'
|
||||||
}]
|
}]
|
||||||
@ -28,36 +28,32 @@ class BandcampIE(InfoExtractor):
|
|||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
title = mobj.group('title')
|
title = mobj.group('title')
|
||||||
webpage = self._download_webpage(url, title)
|
webpage = self._download_webpage(url, title)
|
||||||
# We get the link to the free download page
|
|
||||||
m_download = re.search(r'freeDownloadPage: "(.*?)"', webpage)
|
m_download = re.search(r'freeDownloadPage: "(.*?)"', webpage)
|
||||||
if m_download is None:
|
if not m_download:
|
||||||
m_trackinfo = re.search(r'trackinfo: (.+),\s*?\n', webpage)
|
m_trackinfo = re.search(r'trackinfo: (.+),\s*?\n', webpage)
|
||||||
if m_trackinfo:
|
if m_trackinfo:
|
||||||
json_code = m_trackinfo.group(1)
|
json_code = m_trackinfo.group(1)
|
||||||
data = json.loads(json_code)
|
data = json.loads(json_code)[0]
|
||||||
d = data[0]
|
|
||||||
|
|
||||||
duration = int(round(d['duration']))
|
|
||||||
formats = []
|
formats = []
|
||||||
for format_id, format_url in d['file'].items():
|
for format_id, format_url in data['file'].items():
|
||||||
ext, _, abr_str = format_id.partition('-')
|
ext, abr_str = format_id.split('-', 1)
|
||||||
|
|
||||||
formats.append({
|
formats.append({
|
||||||
'format_id': format_id,
|
'format_id': format_id,
|
||||||
'url': format_url,
|
'url': format_url,
|
||||||
'ext': format_id.partition('-')[0],
|
'ext': ext,
|
||||||
'vcodec': 'none',
|
'vcodec': 'none',
|
||||||
'acodec': format_id.partition('-')[0],
|
'acodec': ext,
|
||||||
'abr': int(format_id.partition('-')[2]),
|
'abr': int(abr_str),
|
||||||
})
|
})
|
||||||
|
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': compat_str(d['id']),
|
'id': compat_str(data['id']),
|
||||||
'title': d['title'],
|
'title': data['title'],
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
'duration': duration,
|
'duration': float(data['duration']),
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
raise ExtractorError('No free songs found')
|
raise ExtractorError('No free songs found')
|
||||||
@ -67,11 +63,9 @@ class BandcampIE(InfoExtractor):
|
|||||||
r'var TralbumData = {(.*?)id: (?P<id>\d*?)$',
|
r'var TralbumData = {(.*?)id: (?P<id>\d*?)$',
|
||||||
webpage, re.MULTILINE | re.DOTALL).group('id')
|
webpage, re.MULTILINE | re.DOTALL).group('id')
|
||||||
|
|
||||||
download_webpage = self._download_webpage(download_link, video_id,
|
download_webpage = self._download_webpage(download_link, video_id, 'Downloading free downloads page')
|
||||||
'Downloading free downloads page')
|
# We get the dictionary of the track from some javascript code
|
||||||
# We get the dictionary of the track from some javascrip code
|
info = re.search(r'items: (.*?),$', download_webpage, re.MULTILINE).group(1)
|
||||||
info = re.search(r'items: (.*?),$',
|
|
||||||
download_webpage, re.MULTILINE).group(1)
|
|
||||||
info = json.loads(info)[0]
|
info = json.loads(info)[0]
|
||||||
# We pick mp3-320 for now, until format selection can be easily implemented.
|
# We pick mp3-320 for now, until format selection can be easily implemented.
|
||||||
mp3_info = info['downloads']['mp3-320']
|
mp3_info = info['downloads']['mp3-320']
|
||||||
@ -100,7 +94,7 @@ class BandcampIE(InfoExtractor):
|
|||||||
|
|
||||||
class BandcampAlbumIE(InfoExtractor):
|
class BandcampAlbumIE(InfoExtractor):
|
||||||
IE_NAME = 'Bandcamp:album'
|
IE_NAME = 'Bandcamp:album'
|
||||||
_VALID_URL = r'http://.*?\.bandcamp\.com/album/(?P<title>.*)'
|
_VALID_URL = r'https?://(?:(?P<subdomain>[^.]+)\.)?bandcamp\.com(?:/album/(?P<title>[^?#]+))'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://blazo.bandcamp.com/album/jazz-format-mixtape-vol-1',
|
'url': 'http://blazo.bandcamp.com/album/jazz-format-mixtape-vol-1',
|
||||||
@ -123,13 +117,15 @@ class BandcampAlbumIE(InfoExtractor):
|
|||||||
'params': {
|
'params': {
|
||||||
'playlistend': 2
|
'playlistend': 2
|
||||||
},
|
},
|
||||||
'skip': 'Bancamp imposes download limits. See test_playlists:test_bandcamp_album for the playlist test'
|
'skip': 'Bandcamp imposes download limits. See test_playlists:test_bandcamp_album for the playlist test'
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
playlist_id = mobj.group('subdomain')
|
||||||
title = mobj.group('title')
|
title = mobj.group('title')
|
||||||
webpage = self._download_webpage(url, title)
|
display_id = title or playlist_id
|
||||||
|
webpage = self._download_webpage(url, display_id)
|
||||||
tracks_paths = re.findall(r'<a href="(.*?)" itemprop="url">', webpage)
|
tracks_paths = re.findall(r'<a href="(.*?)" itemprop="url">', webpage)
|
||||||
if not tracks_paths:
|
if not tracks_paths:
|
||||||
raise ExtractorError('The page doesn\'t contain any tracks')
|
raise ExtractorError('The page doesn\'t contain any tracks')
|
||||||
@ -139,6 +135,8 @@ class BandcampAlbumIE(InfoExtractor):
|
|||||||
title = self._search_regex(r'album_title : "(.*?)"', webpage, 'title')
|
title = self._search_regex(r'album_title : "(.*?)"', webpage, 'title')
|
||||||
return {
|
return {
|
||||||
'_type': 'playlist',
|
'_type': 'playlist',
|
||||||
|
'id': playlist_id,
|
||||||
|
'display_id': display_id,
|
||||||
'title': title,
|
'title': title,
|
||||||
'entries': entries,
|
'entries': entries,
|
||||||
}
|
}
|
||||||
|
106
youtube_dl/extractor/bilibili.py
Normal file
106
youtube_dl/extractor/bilibili.py
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
compat_parse_qs,
|
||||||
|
ExtractorError,
|
||||||
|
int_or_none,
|
||||||
|
unified_strdate,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BiliBiliIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'http://www\.bilibili\.tv/video/av(?P<id>[0-9]+)/'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.bilibili.tv/video/av1074402/',
|
||||||
|
'md5': '2c301e4dab317596e837c3e7633e7d86',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '1074402',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': '【金坷垃】金泡沫',
|
||||||
|
'duration': 308,
|
||||||
|
'upload_date': '20140420',
|
||||||
|
'thumbnail': 're:^https?://.+\.jpg',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
video_code = self._search_regex(
|
||||||
|
r'(?s)<div itemprop="video".*?>(.*?)</div>', webpage, 'video code')
|
||||||
|
|
||||||
|
title = self._html_search_meta(
|
||||||
|
'media:title', video_code, 'title', fatal=True)
|
||||||
|
duration_str = self._html_search_meta(
|
||||||
|
'duration', video_code, 'duration')
|
||||||
|
if duration_str is None:
|
||||||
|
duration = None
|
||||||
|
else:
|
||||||
|
duration_mobj = re.match(
|
||||||
|
r'^T(?:(?P<hours>[0-9]+)H)?(?P<minutes>[0-9]+)M(?P<seconds>[0-9]+)S$',
|
||||||
|
duration_str)
|
||||||
|
duration = (
|
||||||
|
int_or_none(duration_mobj.group('hours'), default=0) * 3600 +
|
||||||
|
int(duration_mobj.group('minutes')) * 60 +
|
||||||
|
int(duration_mobj.group('seconds')))
|
||||||
|
upload_date = unified_strdate(self._html_search_meta(
|
||||||
|
'uploadDate', video_code, fatal=False))
|
||||||
|
thumbnail = self._html_search_meta(
|
||||||
|
'thumbnailUrl', video_code, 'thumbnail', fatal=False)
|
||||||
|
|
||||||
|
player_params = compat_parse_qs(self._html_search_regex(
|
||||||
|
r'<iframe .*?class="player" src="https://secure.bilibili.tv/secure,([^"]+)"',
|
||||||
|
webpage, 'player params'))
|
||||||
|
|
||||||
|
if 'cid' in player_params:
|
||||||
|
cid = player_params['cid'][0]
|
||||||
|
|
||||||
|
lq_doc = self._download_xml(
|
||||||
|
'http://interface.bilibili.cn/v_cdn_play?cid=%s' % cid,
|
||||||
|
video_id,
|
||||||
|
note='Downloading LQ video info'
|
||||||
|
)
|
||||||
|
lq_durl = lq_doc.find('.//durl')
|
||||||
|
formats = [{
|
||||||
|
'format_id': 'lq',
|
||||||
|
'quality': 1,
|
||||||
|
'url': lq_durl.find('./url').text,
|
||||||
|
'filesize': int_or_none(
|
||||||
|
lq_durl.find('./size'), get_attr='text'),
|
||||||
|
}]
|
||||||
|
|
||||||
|
hq_doc = self._download_xml(
|
||||||
|
'http://interface.bilibili.cn/playurl?cid=%s' % cid,
|
||||||
|
video_id,
|
||||||
|
note='Downloading HQ video info',
|
||||||
|
fatal=False,
|
||||||
|
)
|
||||||
|
if hq_doc is not False:
|
||||||
|
hq_durl = hq_doc.find('.//durl')
|
||||||
|
formats.append({
|
||||||
|
'format_id': 'hq',
|
||||||
|
'quality': 2,
|
||||||
|
'ext': 'flv',
|
||||||
|
'url': hq_durl.find('./url').text,
|
||||||
|
'filesize': int_or_none(
|
||||||
|
hq_durl.find('./size'), get_attr='text'),
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
raise ExtractorError('Unsupported player parameters: %r' % (player_params,))
|
||||||
|
|
||||||
|
self._sort_formats(formats)
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'formats': formats,
|
||||||
|
'duration': duration,
|
||||||
|
'upload_date': upload_date,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
}
|
@ -1,6 +1,5 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import datetime
|
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
|
||||||
@ -19,15 +18,16 @@ class BlinkxIE(InfoExtractor):
|
|||||||
'file': '8aQUy7GV.mp4',
|
'file': '8aQUy7GV.mp4',
|
||||||
'md5': '2e9a07364af40163a908edbf10bb2492',
|
'md5': '2e9a07364af40163a908edbf10bb2492',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
"title": "Police Car Rolls Away",
|
'title': 'Police Car Rolls Away',
|
||||||
"uploader": "stupidvideos.com",
|
'uploader': 'stupidvideos.com',
|
||||||
"upload_date": "20131215",
|
'upload_date': '20131215',
|
||||||
"description": "A police car gently rolls away from a fight. Maybe it felt weird being around a confrontation and just had to get out of there!",
|
'timestamp': 1387068000,
|
||||||
"duration": 14.886,
|
'description': 'A police car gently rolls away from a fight. Maybe it felt weird being around a confrontation and just had to get out of there!',
|
||||||
"thumbnails": [{
|
'duration': 14.886,
|
||||||
"width": 100,
|
'thumbnails': [{
|
||||||
"height": 76,
|
'width': 100,
|
||||||
"url": "http://cdn.blinkx.com/stream/b/41/StupidVideos/20131215/1873969261/1873969261_tn_0.jpg",
|
'height': 76,
|
||||||
|
'url': 'http://cdn.blinkx.com/stream/b/41/StupidVideos/20131215/1873969261/1873969261_tn_0.jpg',
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -41,9 +41,6 @@ class BlinkxIE(InfoExtractor):
|
|||||||
'video=%s' % video_id)
|
'video=%s' % video_id)
|
||||||
data_json = self._download_webpage(api_url, display_id)
|
data_json = self._download_webpage(api_url, display_id)
|
||||||
data = json.loads(data_json)['api']['results'][0]
|
data = json.loads(data_json)['api']['results'][0]
|
||||||
dt = datetime.datetime.fromtimestamp(data['pubdate_epoch'])
|
|
||||||
pload_date = dt.strftime('%Y%m%d')
|
|
||||||
|
|
||||||
duration = None
|
duration = None
|
||||||
thumbnails = []
|
thumbnails = []
|
||||||
formats = []
|
formats = []
|
||||||
@ -64,10 +61,7 @@ class BlinkxIE(InfoExtractor):
|
|||||||
vcodec = remove_start(m['vcodec'], 'ff')
|
vcodec = remove_start(m['vcodec'], 'ff')
|
||||||
acodec = remove_start(m['acodec'], 'ff')
|
acodec = remove_start(m['acodec'], 'ff')
|
||||||
tbr = (int(m['vbr']) + int(m['abr'])) // 1000
|
tbr = (int(m['vbr']) + int(m['abr'])) // 1000
|
||||||
format_id = (u'%s-%sk-%s' %
|
format_id = u'%s-%sk-%s' % (vcodec, tbr, m['w'])
|
||||||
(vcodec,
|
|
||||||
tbr,
|
|
||||||
m['w']))
|
|
||||||
formats.append({
|
formats.append({
|
||||||
'format_id': format_id,
|
'format_id': format_id,
|
||||||
'url': m['link'],
|
'url': m['link'],
|
||||||
@ -88,7 +82,7 @@ class BlinkxIE(InfoExtractor):
|
|||||||
'title': data['title'],
|
'title': data['title'],
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
'uploader': data['channel_name'],
|
'uploader': data['channel_name'],
|
||||||
'upload_date': pload_date,
|
'timestamp': data['pubdate_epoch'],
|
||||||
'description': data.get('description'),
|
'description': data.get('description'),
|
||||||
'thumbnails': thumbnails,
|
'thumbnails': thumbnails,
|
||||||
'duration': duration,
|
'duration': duration,
|
||||||
|
@ -1,102 +1,124 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import datetime
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from .subtitles import SubtitlesInfoExtractor
|
from .subtitles import SubtitlesInfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
compat_str,
|
|
||||||
compat_urllib_request,
|
compat_urllib_request,
|
||||||
|
|
||||||
unescapeHTML,
|
unescapeHTML,
|
||||||
|
parse_iso8601,
|
||||||
|
compat_urlparse,
|
||||||
|
clean_html,
|
||||||
|
compat_str,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class BlipTVIE(SubtitlesInfoExtractor):
|
class BlipTVIE(SubtitlesInfoExtractor):
|
||||||
"""Information extractor for blip.tv"""
|
_VALID_URL = r'https?://(?:\w+\.)?blip\.tv/(?:(?:.+-|rss/flash/)(?P<id>\d+)|((?:play/|api\.swf#)(?P<lookup_id>[\da-zA-Z]+)))'
|
||||||
|
|
||||||
_VALID_URL = r'https?://(?:\w+\.)?blip\.tv/((.+/)|(play/)|(api\.swf#))(?P<presumptive_id>.+)$'
|
_TESTS = [
|
||||||
|
{
|
||||||
_TESTS = [{
|
'url': 'http://blip.tv/cbr/cbr-exclusive-gotham-city-imposters-bats-vs-jokerz-short-3-5796352',
|
||||||
'url': 'http://blip.tv/cbr/cbr-exclusive-gotham-city-imposters-bats-vs-jokerz-short-3-5796352',
|
'md5': 'c6934ad0b6acf2bd920720ec888eb812',
|
||||||
'md5': 'c6934ad0b6acf2bd920720ec888eb812',
|
'info_dict': {
|
||||||
'info_dict': {
|
'id': '5779306',
|
||||||
'id': '5779306',
|
'ext': 'mov',
|
||||||
'ext': 'mov',
|
'title': 'CBR EXCLUSIVE: "Gotham City Imposters" Bats VS Jokerz Short 3',
|
||||||
'upload_date': '20111205',
|
'description': 'md5:9bc31f227219cde65e47eeec8d2dc596',
|
||||||
'description': 'md5:9bc31f227219cde65e47eeec8d2dc596',
|
'timestamp': 1323138843,
|
||||||
'uploader': 'Comic Book Resources - CBR TV',
|
'upload_date': '20111206',
|
||||||
'title': 'CBR EXCLUSIVE: "Gotham City Imposters" Bats VS Jokerz Short 3',
|
'uploader': 'cbr',
|
||||||
|
'uploader_id': '679425',
|
||||||
|
'duration': 81,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
# https://github.com/rg3/youtube-dl/pull/2274
|
||||||
|
'note': 'Video with subtitles',
|
||||||
|
'url': 'http://blip.tv/play/h6Uag5OEVgI.html',
|
||||||
|
'md5': '309f9d25b820b086ca163ffac8031806',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '6586561',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Red vs. Blue Season 11 Episode 1',
|
||||||
|
'description': 'One-Zero-One',
|
||||||
|
'timestamp': 1371261608,
|
||||||
|
'upload_date': '20130615',
|
||||||
|
'uploader': 'redvsblue',
|
||||||
|
'uploader_id': '792887',
|
||||||
|
'duration': 279,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, {
|
]
|
||||||
# https://github.com/rg3/youtube-dl/pull/2274
|
|
||||||
'note': 'Video with subtitles',
|
|
||||||
'url': 'http://blip.tv/play/h6Uag5OEVgI.html',
|
|
||||||
'md5': '309f9d25b820b086ca163ffac8031806',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '6586561',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'uploader': 'Red vs. Blue',
|
|
||||||
'description': 'One-Zero-One',
|
|
||||||
'upload_date': '20130614',
|
|
||||||
'title': 'Red vs. Blue Season 11 Episode 1',
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
presumptive_id = mobj.group('presumptive_id')
|
lookup_id = mobj.group('lookup_id')
|
||||||
|
|
||||||
# See https://github.com/rg3/youtube-dl/issues/857
|
# See https://github.com/rg3/youtube-dl/issues/857
|
||||||
embed_mobj = re.match(r'https?://(?:\w+\.)?blip\.tv/(?:play/|api\.swf#)([a-zA-Z0-9]+)', url)
|
if lookup_id:
|
||||||
if embed_mobj:
|
info_page = self._download_webpage(
|
||||||
info_url = 'http://blip.tv/play/%s.x?p=1' % embed_mobj.group(1)
|
'http://blip.tv/play/%s.x?p=1' % lookup_id, lookup_id, 'Resolving lookup id')
|
||||||
info_page = self._download_webpage(info_url, embed_mobj.group(1))
|
video_id = self._search_regex(r'data-episode-id="([0-9]+)', info_page, 'video_id')
|
||||||
video_id = self._search_regex(
|
|
||||||
r'data-episode-id="([0-9]+)', info_page, 'video_id')
|
|
||||||
return self.url_result('http://blip.tv/a/a-' + video_id, 'BlipTV')
|
|
||||||
|
|
||||||
cchar = '&' if '?' in url else '?'
|
|
||||||
json_url = url + cchar + 'skin=json&version=2&no_wrap=1'
|
|
||||||
request = compat_urllib_request.Request(json_url)
|
|
||||||
request.add_header('User-Agent', 'iTunes/10.6.1')
|
|
||||||
|
|
||||||
json_data = self._download_json(request, video_id=presumptive_id)
|
|
||||||
|
|
||||||
if 'Post' in json_data:
|
|
||||||
data = json_data['Post']
|
|
||||||
else:
|
else:
|
||||||
data = json_data
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
rss = self._download_xml('http://blip.tv/rss/flash/%s' % video_id, video_id, 'Downloading video RSS')
|
||||||
|
|
||||||
|
def blip(s):
|
||||||
|
return '{http://blip.tv/dtd/blip/1.0}%s' % s
|
||||||
|
|
||||||
|
def media(s):
|
||||||
|
return '{http://search.yahoo.com/mrss/}%s' % s
|
||||||
|
|
||||||
|
def itunes(s):
|
||||||
|
return '{http://www.itunes.com/dtds/podcast-1.0.dtd}%s' % s
|
||||||
|
|
||||||
|
item = rss.find('channel/item')
|
||||||
|
|
||||||
|
video_id = item.find(blip('item_id')).text
|
||||||
|
title = item.find('./title').text
|
||||||
|
description = clean_html(compat_str(item.find(blip('puredescription')).text))
|
||||||
|
timestamp = parse_iso8601(item.find(blip('datestamp')).text)
|
||||||
|
uploader = item.find(blip('user')).text
|
||||||
|
uploader_id = item.find(blip('userid')).text
|
||||||
|
duration = int(item.find(blip('runtime')).text)
|
||||||
|
media_thumbnail = item.find(media('thumbnail'))
|
||||||
|
thumbnail = media_thumbnail.get('url') if media_thumbnail is not None else item.find(itunes('image')).text
|
||||||
|
categories = [category.text for category in item.findall('category')]
|
||||||
|
|
||||||
video_id = compat_str(data['item_id'])
|
|
||||||
upload_date = datetime.datetime.strptime(data['datestamp'], '%m-%d-%y %H:%M%p').strftime('%Y%m%d')
|
|
||||||
subtitles = {}
|
|
||||||
formats = []
|
formats = []
|
||||||
if 'additionalMedia' in data:
|
subtitles = {}
|
||||||
for f in data['additionalMedia']:
|
|
||||||
if f.get('file_type_srt') == 1:
|
media_group = item.find(media('group'))
|
||||||
LANGS = {
|
for media_content in media_group.findall(media('content')):
|
||||||
'english': 'en',
|
url = media_content.get('url')
|
||||||
}
|
role = media_content.get(blip('role'))
|
||||||
lang = f['role'].rpartition('-')[-1].strip().lower()
|
msg = self._download_webpage(
|
||||||
langcode = LANGS.get(lang, lang)
|
url + '?showplayer=20140425131715&referrer=http://blip.tv&mask=7&skin=flashvars&view=url',
|
||||||
subtitles[langcode] = f['url']
|
video_id, 'Resolving URL for %s' % role)
|
||||||
continue
|
real_url = compat_urlparse.parse_qs(msg)['message'][0]
|
||||||
if not int(f['media_width']): # filter m3u8
|
|
||||||
continue
|
media_type = media_content.get('type')
|
||||||
|
if media_type == 'text/srt' or url.endswith('.srt'):
|
||||||
|
LANGS = {
|
||||||
|
'english': 'en',
|
||||||
|
}
|
||||||
|
lang = role.rpartition('-')[-1].strip().lower()
|
||||||
|
langcode = LANGS.get(lang, lang)
|
||||||
|
subtitles[langcode] = url
|
||||||
|
elif media_type.startswith('video/'):
|
||||||
formats.append({
|
formats.append({
|
||||||
'url': f['url'],
|
'url': real_url,
|
||||||
'format_id': f['role'],
|
'format_id': role,
|
||||||
'width': int(f['media_width']),
|
'format_note': media_type,
|
||||||
'height': int(f['media_height']),
|
'vcodec': media_content.get(blip('vcodec')),
|
||||||
|
'acodec': media_content.get(blip('acodec')),
|
||||||
|
'filesize': media_content.get('filesize'),
|
||||||
|
'width': int(media_content.get('width')),
|
||||||
|
'height': int(media_content.get('height')),
|
||||||
})
|
})
|
||||||
else:
|
|
||||||
formats.append({
|
|
||||||
'url': data['media']['url'],
|
|
||||||
'width': int(data['media']['width']),
|
|
||||||
'height': int(data['media']['height']),
|
|
||||||
})
|
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
# subtitles
|
# subtitles
|
||||||
@ -107,12 +129,14 @@ class BlipTVIE(SubtitlesInfoExtractor):
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'uploader': data['display_name'],
|
'title': title,
|
||||||
'upload_date': upload_date,
|
'description': description,
|
||||||
'title': data['title'],
|
'timestamp': timestamp,
|
||||||
'thumbnail': data['thumbnailUrl'],
|
'uploader': uploader,
|
||||||
'description': data['description'],
|
'uploader_id': uploader_id,
|
||||||
'user_agent': 'iTunes/10.6.1',
|
'duration': duration,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'categories': categories,
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
'subtitles': video_subtitles,
|
'subtitles': video_subtitles,
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,21 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from .ooyala import OoyalaIE
|
|
||||||
|
|
||||||
|
|
||||||
class BloombergIE(InfoExtractor):
|
class BloombergIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://www\.bloomberg\.com/video/(?P<name>.+?)\.html'
|
_VALID_URL = r'https?://www\.bloomberg\.com/video/(?P<name>.+?)\.html'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
u'url': u'http://www.bloomberg.com/video/shah-s-presentation-on-foreign-exchange-strategies-qurhIVlJSB6hzkVi229d8g.html',
|
'url': 'http://www.bloomberg.com/video/shah-s-presentation-on-foreign-exchange-strategies-qurhIVlJSB6hzkVi229d8g.html',
|
||||||
u'file': u'12bzhqZTqQHmmlA8I-i0NpzJgcG5NNYX.mp4',
|
'md5': '7bf08858ff7c203c870e8a6190e221e5',
|
||||||
u'info_dict': {
|
'info_dict': {
|
||||||
u'title': u'Shah\'s Presentation on Foreign-Exchange Strategies',
|
'id': 'qurhIVlJSB6hzkVi229d8g',
|
||||||
u'description': u'md5:abc86e5236f9f0e4866c59ad36736686',
|
'ext': 'flv',
|
||||||
},
|
'title': 'Shah\'s Presentation on Foreign-Exchange Strategies',
|
||||||
u'params': {
|
'description': 'md5:0681e0d30dcdfc6abf34594961d8ea88',
|
||||||
# Requires ffmpeg (m3u8 manifest)
|
|
||||||
u'skip_download': True,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,7 +23,16 @@ class BloombergIE(InfoExtractor):
|
|||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
name = mobj.group('name')
|
name = mobj.group('name')
|
||||||
webpage = self._download_webpage(url, name)
|
webpage = self._download_webpage(url, name)
|
||||||
embed_code = self._search_regex(
|
f4m_url = self._search_regex(
|
||||||
r'<source src="https?://[^/]+/[^/]+/[^/]+/([^/]+)', webpage,
|
r'<source src="(https?://[^"]+\.f4m.*?)"', webpage,
|
||||||
'embed code')
|
'f4m url')
|
||||||
return OoyalaIE._build_url_result(embed_code)
|
title = re.sub(': Video$', '', self._og_search_title(webpage))
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': name.split('-')[-1],
|
||||||
|
'title': title,
|
||||||
|
'url': f4m_url,
|
||||||
|
'ext': 'flv',
|
||||||
|
'description': self._og_search_description(webpage),
|
||||||
|
'thumbnail': self._og_search_thumbnail(webpage),
|
||||||
|
}
|
||||||
|
@ -4,39 +4,72 @@ from __future__ import unicode_literals
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import ExtractorError
|
from ..utils import (
|
||||||
|
ExtractorError,
|
||||||
|
int_or_none,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class BRIE(InfoExtractor):
|
class BRIE(InfoExtractor):
|
||||||
IE_DESC = "Bayerischer Rundfunk Mediathek"
|
IE_DESC = 'Bayerischer Rundfunk Mediathek'
|
||||||
_VALID_URL = r"^https?://(?:www\.)?br\.de/mediathek/video/(?:sendungen/)?(?:[a-z0-9\-/]+/)?(?P<id>[a-z0-9\-]+)\.html$"
|
_VALID_URL = r'https?://(?:www\.)?br\.de/(?:[a-z0-9\-]+/)+(?P<id>[a-z0-9\-]+)\.html'
|
||||||
_BASE_URL = "http://www.br.de"
|
_BASE_URL = 'http://www.br.de'
|
||||||
|
|
||||||
_TESTS = [
|
_TESTS = [
|
||||||
{
|
{
|
||||||
"url": "http://www.br.de/mediathek/video/anselm-gruen-114.html",
|
'url': 'http://www.br.de/mediathek/video/anselm-gruen-114.html',
|
||||||
"md5": "c4f83cf0f023ba5875aba0bf46860df2",
|
'md5': 'c4f83cf0f023ba5875aba0bf46860df2',
|
||||||
"info_dict": {
|
'info_dict': {
|
||||||
"id": "2c8d81c5-6fb7-4a74-88d4-e768e5856532",
|
'id': '2c8d81c5-6fb7-4a74-88d4-e768e5856532',
|
||||||
"ext": "mp4",
|
'ext': 'mp4',
|
||||||
"title": "Feiern und Verzichten",
|
'title': 'Feiern und Verzichten',
|
||||||
"description": "Anselm Grün: Feiern und Verzichten",
|
'description': 'Anselm Grün: Feiern und Verzichten',
|
||||||
"uploader": "BR/Birgit Baier",
|
'uploader': 'BR/Birgit Baier',
|
||||||
"upload_date": "20140301"
|
'upload_date': '20140301',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "http://www.br.de/mediathek/video/sendungen/unter-unserem-himmel/unter-unserem-himmel-alpen-ueber-den-pass-100.html",
|
'url': 'http://www.br.de/mediathek/video/sendungen/unter-unserem-himmel/unter-unserem-himmel-alpen-ueber-den-pass-100.html',
|
||||||
"md5": "ab451b09d861dbed7d7cc9ab0be19ebe",
|
'md5': 'ab451b09d861dbed7d7cc9ab0be19ebe',
|
||||||
"info_dict": {
|
'info_dict': {
|
||||||
"id": "2c060e69-3a27-4e13-b0f0-668fac17d812",
|
'id': '2c060e69-3a27-4e13-b0f0-668fac17d812',
|
||||||
"ext": "mp4",
|
'ext': 'mp4',
|
||||||
"title": "Über den Pass",
|
'title': 'Über den Pass',
|
||||||
"description": "Die Eroberung der Alpen: Über den Pass",
|
'description': 'Die Eroberung der Alpen: Über den Pass',
|
||||||
"uploader": None,
|
|
||||||
"upload_date": None
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://www.br.de/nachrichten/schaeuble-haushaltsentwurf-bundestag-100.html',
|
||||||
|
'md5': '3db0df1a9a9cd9fa0c70e6ea8aa8e820',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'c6aae3de-2cf9-43f2-957f-f17fef9afaab',
|
||||||
|
'ext': 'aac',
|
||||||
|
'title': '"Keine neuen Schulden im nächsten Jahr"',
|
||||||
|
'description': 'Haushaltsentwurf: "Keine neuen Schulden im nächsten Jahr"',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://www.br.de/radio/bayern1/service/team/videos/team-video-erdelt100.html',
|
||||||
|
'md5': 'dbab0aef2e047060ea7a21fc1ce1078a',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '6ba73750-d405-45d3-861d-1ce8c524e059',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Umweltbewusster Häuslebauer',
|
||||||
|
'description': 'Uwe Erdelt: Umweltbewusster Häuslebauer',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://www.br.de/fernsehen/br-alpha/sendungen/kant-fuer-anfaenger/kritik-der-reinen-vernunft/kant-kritik-01-metaphysik100.html',
|
||||||
|
'md5': '23bca295f1650d698f94fc570977dae3',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'd982c9ce-8648-4753-b358-98abb8aec43d',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Folge 1 - Metaphysik',
|
||||||
|
'description': 'Kant für Anfänger: Folge 1 - Metaphysik',
|
||||||
|
'uploader': 'Eva Maria Steimle',
|
||||||
|
'upload_date': '20140117',
|
||||||
|
}
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
@ -44,56 +77,63 @@ class BRIE(InfoExtractor):
|
|||||||
display_id = mobj.group('id')
|
display_id = mobj.group('id')
|
||||||
page = self._download_webpage(url, display_id)
|
page = self._download_webpage(url, display_id)
|
||||||
xml_url = self._search_regex(
|
xml_url = self._search_regex(
|
||||||
r"return BRavFramework\.register\(BRavFramework\('avPlayer_(?:[a-f0-9-]{36})'\)\.setup\({dataURL:'(/mediathek/video/[a-z0-9/~_.-]+)'}\)\);", page, "XMLURL")
|
r"return BRavFramework\.register\(BRavFramework\('avPlayer_(?:[a-f0-9-]{36})'\)\.setup\({dataURL:'(/(?:[a-z0-9\-]+/)+[a-z0-9/~_.-]+)'}\)\);", page, 'XMLURL')
|
||||||
xml = self._download_xml(self._BASE_URL + xml_url, None)
|
xml = self._download_xml(self._BASE_URL + xml_url, None)
|
||||||
|
|
||||||
videos = []
|
medias = []
|
||||||
for xml_video in xml.findall("video"):
|
|
||||||
video = {
|
|
||||||
"id": xml_video.get("externalId"),
|
|
||||||
"title": xml_video.find("title").text,
|
|
||||||
"formats": self._extract_formats(xml_video.find("assets")),
|
|
||||||
"thumbnails": self._extract_thumbnails(xml_video.find("teaserImage/variants")),
|
|
||||||
"description": " ".join(xml_video.find("shareTitle").text.splitlines()),
|
|
||||||
"webpage_url": xml_video.find("permalink").text
|
|
||||||
}
|
|
||||||
if xml_video.find("author").text:
|
|
||||||
video["uploader"] = xml_video.find("author").text
|
|
||||||
if xml_video.find("broadcastDate").text:
|
|
||||||
video["upload_date"] = "".join(reversed(xml_video.find("broadcastDate").text.split(".")))
|
|
||||||
videos.append(video)
|
|
||||||
|
|
||||||
if len(videos) > 1:
|
for xml_media in xml.findall('video') + xml.findall('audio'):
|
||||||
|
media = {
|
||||||
|
'id': xml_media.get('externalId'),
|
||||||
|
'title': xml_media.find('title').text,
|
||||||
|
'formats': self._extract_formats(xml_media.find('assets')),
|
||||||
|
'thumbnails': self._extract_thumbnails(xml_media.find('teaserImage/variants')),
|
||||||
|
'description': ' '.join(xml_media.find('shareTitle').text.splitlines()),
|
||||||
|
'webpage_url': xml_media.find('permalink').text
|
||||||
|
}
|
||||||
|
if xml_media.find('author').text:
|
||||||
|
media['uploader'] = xml_media.find('author').text
|
||||||
|
if xml_media.find('broadcastDate').text:
|
||||||
|
media['upload_date'] = ''.join(reversed(xml_media.find('broadcastDate').text.split('.')))
|
||||||
|
medias.append(media)
|
||||||
|
|
||||||
|
if len(medias) > 1:
|
||||||
self._downloader.report_warning(
|
self._downloader.report_warning(
|
||||||
'found multiple videos; please '
|
'found multiple medias; please '
|
||||||
'report this with the video URL to http://yt-dl.org/bug')
|
'report this with the video URL to http://yt-dl.org/bug')
|
||||||
if not videos:
|
if not medias:
|
||||||
raise ExtractorError('No video entries found')
|
raise ExtractorError('No media entries found')
|
||||||
return videos[0]
|
return medias[0]
|
||||||
|
|
||||||
def _extract_formats(self, assets):
|
def _extract_formats(self, assets):
|
||||||
|
|
||||||
|
def text_or_none(asset, tag):
|
||||||
|
elem = asset.find(tag)
|
||||||
|
return None if elem is None else elem.text
|
||||||
|
|
||||||
formats = [{
|
formats = [{
|
||||||
"url": asset.find("downloadUrl").text,
|
'url': text_or_none(asset, 'downloadUrl'),
|
||||||
"ext": asset.find("mediaType").text,
|
'ext': text_or_none(asset, 'mediaType'),
|
||||||
"format_id": asset.get("type"),
|
'format_id': asset.get('type'),
|
||||||
"width": int(asset.find("frameWidth").text),
|
'width': int_or_none(text_or_none(asset, 'frameWidth')),
|
||||||
"height": int(asset.find("frameHeight").text),
|
'height': int_or_none(text_or_none(asset, 'frameHeight')),
|
||||||
"tbr": int(asset.find("bitrateVideo").text),
|
'tbr': int_or_none(text_or_none(asset, 'bitrateVideo')),
|
||||||
"abr": int(asset.find("bitrateAudio").text),
|
'abr': int_or_none(text_or_none(asset, 'bitrateAudio')),
|
||||||
"vcodec": asset.find("codecVideo").text,
|
'vcodec': text_or_none(asset, 'codecVideo'),
|
||||||
"container": asset.find("mediaType").text,
|
'acodec': text_or_none(asset, 'codecAudio'),
|
||||||
"filesize": int(asset.find("size").text),
|
'container': text_or_none(asset, 'mediaType'),
|
||||||
} for asset in assets.findall("asset")
|
'filesize': int_or_none(text_or_none(asset, 'size')),
|
||||||
if asset.find("downloadUrl") is not None]
|
} for asset in assets.findall('asset')
|
||||||
|
if asset.find('downloadUrl') is not None]
|
||||||
|
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
return formats
|
return formats
|
||||||
|
|
||||||
def _extract_thumbnails(self, variants):
|
def _extract_thumbnails(self, variants):
|
||||||
thumbnails = [{
|
thumbnails = [{
|
||||||
"url": self._BASE_URL + variant.find("url").text,
|
'url': self._BASE_URL + variant.find('url').text,
|
||||||
"width": int(variant.find("width").text),
|
'width': int_or_none(variant.find('width').text),
|
||||||
"height": int(variant.find("height").text),
|
'height': int_or_none(variant.find('height').text),
|
||||||
} for variant in variants.findall("variant")]
|
} for variant in variants.findall('variant')]
|
||||||
thumbnails.sort(key=lambda x: x["width"] * x["height"], reverse=True)
|
thumbnails.sort(key=lambda x: x['width'] * x['height'], reverse=True)
|
||||||
return thumbnails
|
return thumbnails
|
||||||
|
@ -27,9 +27,10 @@ class BreakIE(InfoExtractor):
|
|||||||
webpage, 'info json', flags=re.DOTALL)
|
webpage, 'info json', flags=re.DOTALL)
|
||||||
info = json.loads(info_json)
|
info = json.loads(info_json)
|
||||||
video_url = info['videoUri']
|
video_url = info['videoUri']
|
||||||
m_youtube = re.search(r'(https?://www\.youtube\.com/watch\?v=.*)', video_url)
|
youtube_id = info.get('youtubeId')
|
||||||
if m_youtube is not None:
|
if youtube_id:
|
||||||
return self.url_result(m_youtube.group(1), 'Youtube')
|
return self.url_result(youtube_id, 'Youtube')
|
||||||
|
|
||||||
final_url = video_url + '?' + info['AuthToken']
|
final_url = video_url + '?' + info['AuthToken']
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
|
@ -87,7 +87,7 @@ class BrightcoveIE(InfoExtractor):
|
|||||||
object_str = object_str.replace('<--', '<!--')
|
object_str = object_str.replace('<--', '<!--')
|
||||||
object_str = fix_xml_ampersands(object_str)
|
object_str = fix_xml_ampersands(object_str)
|
||||||
|
|
||||||
object_doc = xml.etree.ElementTree.fromstring(object_str)
|
object_doc = xml.etree.ElementTree.fromstring(object_str.encode('utf-8'))
|
||||||
|
|
||||||
fv_el = find_xpath_attr(object_doc, './param', 'name', 'flashVars')
|
fv_el = find_xpath_attr(object_doc, './param', 'name', 'flashVars')
|
||||||
if fv_el is not None:
|
if fv_el is not None:
|
||||||
@ -140,7 +140,11 @@ class BrightcoveIE(InfoExtractor):
|
|||||||
|
|
||||||
url_m = re.search(r'<meta\s+property="og:video"\s+content="(http://c.brightcove.com/[^"]+)"', webpage)
|
url_m = re.search(r'<meta\s+property="og:video"\s+content="(http://c.brightcove.com/[^"]+)"', webpage)
|
||||||
if url_m:
|
if url_m:
|
||||||
return [unescapeHTML(url_m.group(1))]
|
url = unescapeHTML(url_m.group(1))
|
||||||
|
# Some sites don't add it, we can't download with this url, for example:
|
||||||
|
# http://www.ktvu.com/videos/news/raw-video-caltrain-releases-video-of-man-almost/vCTZdY/
|
||||||
|
if 'playerKey' in url:
|
||||||
|
return [url]
|
||||||
|
|
||||||
matches = re.findall(
|
matches = re.findall(
|
||||||
r'''(?sx)<object
|
r'''(?sx)<object
|
||||||
|
@ -4,9 +4,7 @@ import json
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import ExtractorError
|
||||||
ExtractorError,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class BYUtvIE(InfoExtractor):
|
class BYUtvIE(InfoExtractor):
|
||||||
@ -16,7 +14,7 @@ class BYUtvIE(InfoExtractor):
|
|||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'granite-flats-talking',
|
'id': 'granite-flats-talking',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'description': 'md5:1a7ae3e153359b7cc355ef3963441e5f',
|
'description': 'md5:4e9a7ce60f209a33eca0ac65b4918e1c',
|
||||||
'title': 'Talking',
|
'title': 'Talking',
|
||||||
'thumbnail': 're:^https?://.*promo.*'
|
'thumbnail': 're:^https?://.*promo.*'
|
||||||
},
|
},
|
||||||
|
@ -2,39 +2,46 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import json
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
class C56IE(InfoExtractor):
|
class C56IE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://((www|player)\.)?56\.com/(.+?/)?(v_|(play_album.+-))(?P<textid>.+?)\.(html|swf)'
|
_VALID_URL = r'https?://(?:(?:www|player)\.)?56\.com/(?:.+?/)?(?:v_|(?:play_album.+-))(?P<textid>.+?)\.(?:html|swf)'
|
||||||
IE_NAME = '56.com'
|
IE_NAME = '56.com'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.56.com/u39/v_OTM0NDA3MTY.html',
|
'url': 'http://www.56.com/u39/v_OTM0NDA3MTY.html',
|
||||||
'file': '93440716.flv',
|
|
||||||
'md5': 'e59995ac63d0457783ea05f93f12a866',
|
'md5': 'e59995ac63d0457783ea05f93f12a866',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'id': '93440716',
|
||||||
|
'ext': 'flv',
|
||||||
'title': '网事知多少 第32期:车怒',
|
'title': '网事知多少 第32期:车怒',
|
||||||
|
'duration': 283.813,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
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, flags=re.VERBOSE)
|
||||||
text_id = mobj.group('textid')
|
text_id = mobj.group('textid')
|
||||||
info_page = self._download_webpage('http://vxml.56.com/json/%s/' % text_id,
|
|
||||||
text_id, 'Downloading video info')
|
page = self._download_json(
|
||||||
info = json.loads(info_page)['info']
|
'http://vxml.56.com/json/%s/' % text_id, text_id, 'Downloading video info')
|
||||||
formats = [{
|
|
||||||
'format_id': f['type'],
|
info = page['info']
|
||||||
'filesize': int(f['filesize']),
|
|
||||||
'url': f['url']
|
formats = [
|
||||||
} for f in info['rfiles']]
|
{
|
||||||
|
'format_id': f['type'],
|
||||||
|
'filesize': int(f['filesize']),
|
||||||
|
'url': f['url']
|
||||||
|
} for f in info['rfiles']
|
||||||
|
]
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': info['vid'],
|
'id': info['vid'],
|
||||||
'title': info['Subject'],
|
'title': info['Subject'],
|
||||||
|
'duration': int(info['duration']) / 1000.0,
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
'thumbnail': info.get('bimg') or info.get('img'),
|
'thumbnail': info.get('bimg') or info.get('img'),
|
||||||
}
|
}
|
||||||
|
@ -1,53 +1,72 @@
|
|||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import unified_strdate
|
from ..utils import (
|
||||||
|
unified_strdate,
|
||||||
|
url_basename,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CanalplusIE(InfoExtractor):
|
class CanalplusIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(www\.canalplus\.fr/.*?/(?P<path>.*)|player\.canalplus\.fr/#/(?P<id>\d+))'
|
_VALID_URL = r'https?://(?:www\.canalplus\.fr/.*?/(?P<path>.*)|player\.canalplus\.fr/#/(?P<id>[0-9]+))'
|
||||||
_VIDEO_INFO_TEMPLATE = 'http://service.canal-plus.com/video/rest/getVideosLiees/cplus/%s'
|
_VIDEO_INFO_TEMPLATE = 'http://service.canal-plus.com/video/rest/getVideosLiees/cplus/%s'
|
||||||
IE_NAME = u'canalplus.fr'
|
IE_NAME = 'canalplus.fr'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
u'url': u'http://www.canalplus.fr/c-infos-documentaires/pid1830-c-zapping.html?vid=922470',
|
'url': 'http://www.canalplus.fr/c-infos-documentaires/pid1830-c-zapping.html?vid=922470',
|
||||||
u'file': u'922470.flv',
|
'md5': '3db39fb48b9685438ecf33a1078023e4',
|
||||||
u'info_dict': {
|
'info_dict': {
|
||||||
u'title': u'Zapping - 26/08/13',
|
'id': '922470',
|
||||||
u'description': u'Le meilleur de toutes les chaînes, tous les jours.\nEmission du 26 août 2013',
|
'ext': 'flv',
|
||||||
u'upload_date': u'20130826',
|
'title': 'Zapping - 26/08/13',
|
||||||
},
|
'description': 'Le meilleur de toutes les chaînes, tous les jours.\nEmission du 26 août 2013',
|
||||||
u'params': {
|
'upload_date': '20130826',
|
||||||
u'skip_download': 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)
|
||||||
video_id = mobj.groupdict().get('id')
|
video_id = mobj.groupdict().get('id')
|
||||||
|
|
||||||
|
# Beware, some subclasses do not define an id group
|
||||||
|
display_id = url_basename(mobj.group('path'))
|
||||||
|
|
||||||
if video_id is None:
|
if video_id is None:
|
||||||
webpage = self._download_webpage(url, mobj.group('path'))
|
webpage = self._download_webpage(url, display_id)
|
||||||
video_id = self._search_regex(r'videoId = "(\d+)";', webpage, u'video id')
|
video_id = self._search_regex(r'<canal:player videoId="(\d+)"', webpage, 'video id')
|
||||||
|
|
||||||
info_url = self._VIDEO_INFO_TEMPLATE % video_id
|
info_url = self._VIDEO_INFO_TEMPLATE % video_id
|
||||||
doc = self._download_xml(info_url,video_id,
|
doc = self._download_xml(info_url, video_id, 'Downloading video XML')
|
||||||
u'Downloading video info')
|
|
||||||
|
|
||||||
self.report_extraction(video_id)
|
|
||||||
video_info = [video for video in doc if video.find('ID').text == video_id][0]
|
video_info = [video for video in doc if video.find('ID').text == video_id][0]
|
||||||
infos = video_info.find('INFOS')
|
|
||||||
media = video_info.find('MEDIA')
|
media = video_info.find('MEDIA')
|
||||||
formats = [media.find('VIDEOS/%s' % format)
|
infos = video_info.find('INFOS')
|
||||||
for format in ['BAS_DEBIT', 'HAUT_DEBIT', 'HD']]
|
|
||||||
video_url = [format.text for format in formats if format is not None][-1]
|
|
||||||
|
|
||||||
return {'id': video_id,
|
preferences = ['MOBILE', 'BAS_DEBIT', 'HAUT_DEBIT', 'HD', 'HLS', 'HDS']
|
||||||
'title': u'%s - %s' % (infos.find('TITRAGE/TITRE').text,
|
|
||||||
infos.find('TITRAGE/SOUS_TITRE').text),
|
formats = [
|
||||||
'url': video_url,
|
{
|
||||||
'ext': 'flv',
|
'url': fmt.text + '?hdcore=2.11.3' if fmt.tag == 'HDS' else fmt.text,
|
||||||
'upload_date': unified_strdate(infos.find('PUBLICATION/DATE').text),
|
'format_id': fmt.tag,
|
||||||
'thumbnail': media.find('IMAGES/GRAND').text,
|
'ext': 'mp4' if fmt.tag == 'HLS' else 'flv',
|
||||||
'description': infos.find('DESCRIPTION').text,
|
'preference': preferences.index(fmt.tag) if fmt.tag in preferences else -1,
|
||||||
'view_count': int(infos.find('NB_VUES').text),
|
} for fmt in media.find('VIDEOS') if fmt.text
|
||||||
}
|
]
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'display_id': display_id,
|
||||||
|
'title': '%s - %s' % (infos.find('TITRAGE/TITRE').text,
|
||||||
|
infos.find('TITRAGE/SOUS_TITRE').text),
|
||||||
|
'upload_date': unified_strdate(infos.find('PUBLICATION/DATE').text),
|
||||||
|
'thumbnail': media.find('IMAGES/GRAND').text,
|
||||||
|
'description': infos.find('DESCRIPTION').text,
|
||||||
|
'view_count': int(infos.find('NB_VUES').text),
|
||||||
|
'like_count': int(infos.find('NB_LIKES').text),
|
||||||
|
'comment_count': int(infos.find('NB_COMMENTS').text),
|
||||||
|
'formats': formats,
|
||||||
|
}
|
87
youtube_dl/extractor/cbsnews.py
Normal file
87
youtube_dl/extractor/cbsnews.py
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
# encoding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
|
class CBSNewsIE(InfoExtractor):
|
||||||
|
IE_DESC = 'CBS News'
|
||||||
|
_VALID_URL = r'http://(?:www\.)?cbsnews\.com/(?:[^/]+/)+(?P<id>[\da-z_-]+)'
|
||||||
|
|
||||||
|
_TESTS = [
|
||||||
|
{
|
||||||
|
'url': 'http://www.cbsnews.com/news/tesla-and-spacex-elon-musks-industrial-empire/',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'tesla-and-spacex-elon-musks-industrial-empire',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Tesla and SpaceX: Elon Musk\'s industrial empire',
|
||||||
|
'thumbnail': 'http://beta.img.cbsnews.com/i/2014/03/30/60147937-2f53-4565-ad64-1bdd6eb64679/60-0330-pelley-640x360.jpg',
|
||||||
|
'duration': 791,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# rtmp download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://www.cbsnews.com/videos/fort-hood-shooting-army-downplays-mental-illness-as-cause-of-attack/',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'fort-hood-shooting-army-downplays-mental-illness-as-cause-of-attack',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Fort Hood shooting: Army downplays mental illness as cause of attack',
|
||||||
|
'thumbnail': 'http://cbsnews2.cbsistatic.com/hub/i/r/2014/04/04/0c9fbc66-576b-41ca-8069-02d122060dd2/thumbnail/140x90/6dad7a502f88875ceac38202984b6d58/en-0404-werner-replace-640x360.jpg',
|
||||||
|
'duration': 205,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# rtmp download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
video_info = json.loads(self._html_search_regex(
|
||||||
|
r'(?:<ul class="media-list items" id="media-related-items"><li data-video-info|<div id="cbsNewsVideoPlayer" data-video-player-options)=\'({.+?})\'',
|
||||||
|
webpage, 'video JSON info'))
|
||||||
|
|
||||||
|
item = video_info['item'] if 'item' in video_info else video_info
|
||||||
|
title = item.get('articleTitle') or item.get('hed')
|
||||||
|
duration = item.get('duration')
|
||||||
|
thumbnail = item.get('mediaImage') or item.get('thumbnail')
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
for format_id in ['RtmpMobileLow', 'RtmpMobileHigh', 'Hls', 'RtmpDesktop']:
|
||||||
|
uri = item.get('media' + format_id + 'URI')
|
||||||
|
if not uri:
|
||||||
|
continue
|
||||||
|
fmt = {
|
||||||
|
'url': uri,
|
||||||
|
'format_id': format_id,
|
||||||
|
}
|
||||||
|
if uri.startswith('rtmp'):
|
||||||
|
fmt.update({
|
||||||
|
'app': 'ondemand?auth=cbs',
|
||||||
|
'play_path': 'mp4:' + uri.split('<break>')[-1],
|
||||||
|
'player_url': 'http://www.cbsnews.com/[[IMPORT]]/vidtech.cbsinteractive.com/player/3_3_0/CBSI_PLAYER_HD.swf',
|
||||||
|
'page_url': 'http://www.cbsnews.com',
|
||||||
|
'ext': 'flv',
|
||||||
|
})
|
||||||
|
elif uri.endswith('.m3u8'):
|
||||||
|
fmt['ext'] = 'mp4'
|
||||||
|
formats.append(fmt)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'duration': duration,
|
||||||
|
'formats': formats,
|
||||||
|
}
|
@ -1,10 +1,12 @@
|
|||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
|
int_or_none,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -13,9 +15,10 @@ class CinemassacreIE(InfoExtractor):
|
|||||||
_TESTS = [
|
_TESTS = [
|
||||||
{
|
{
|
||||||
'url': 'http://cinemassacre.com/2012/11/10/avgn-the-movie-trailer/',
|
'url': 'http://cinemassacre.com/2012/11/10/avgn-the-movie-trailer/',
|
||||||
'file': '19911.mp4',
|
'md5': 'fde81fbafaee331785f58cd6c0d46190',
|
||||||
'md5': '782f8504ca95a0eba8fc9177c373eec7',
|
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'id': '19911',
|
||||||
|
'ext': 'mp4',
|
||||||
'upload_date': '20121110',
|
'upload_date': '20121110',
|
||||||
'title': '“Angry Video Game Nerd: The Movie” – Trailer',
|
'title': '“Angry Video Game Nerd: The Movie” – Trailer',
|
||||||
'description': 'md5:fb87405fcb42a331742a0dce2708560b',
|
'description': 'md5:fb87405fcb42a331742a0dce2708560b',
|
||||||
@ -23,9 +26,10 @@ class CinemassacreIE(InfoExtractor):
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
'url': 'http://cinemassacre.com/2013/10/02/the-mummys-hand-1940',
|
'url': 'http://cinemassacre.com/2013/10/02/the-mummys-hand-1940',
|
||||||
'file': '521be8ef82b16.mp4',
|
'md5': 'd72f10cd39eac4215048f62ab477a511',
|
||||||
'md5': 'dec39ee5118f8d9cc067f45f9cbe3a35',
|
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'id': '521be8ef82b16',
|
||||||
|
'ext': 'mp4',
|
||||||
'upload_date': '20131002',
|
'upload_date': '20131002',
|
||||||
'title': 'The Mummy’s Hand (1940)',
|
'title': 'The Mummy’s Hand (1940)',
|
||||||
},
|
},
|
||||||
@ -50,29 +54,40 @@ class CinemassacreIE(InfoExtractor):
|
|||||||
r'<div class="entry-content">(?P<description>.+?)</div>',
|
r'<div class="entry-content">(?P<description>.+?)</div>',
|
||||||
webpage, 'description', flags=re.DOTALL, fatal=False)
|
webpage, 'description', flags=re.DOTALL, fatal=False)
|
||||||
|
|
||||||
playerdata = self._download_webpage(playerdata_url, video_id)
|
playerdata = self._download_webpage(playerdata_url, video_id, 'Downloading player webpage')
|
||||||
|
video_thumbnail = self._search_regex(
|
||||||
|
r'image: \'(?P<thumbnail>[^\']+)\'', playerdata, 'thumbnail', fatal=False)
|
||||||
|
sd_url = self._search_regex(r'file: \'([^\']+)\', label: \'SD\'', playerdata, 'sd_file')
|
||||||
|
videolist_url = self._search_regex(r'file: \'([^\']+\.smil)\'}', playerdata, 'videolist_url')
|
||||||
|
|
||||||
sd_url = self._html_search_regex(r'file: \'([^\']+)\', label: \'SD\'', playerdata, 'sd_file')
|
videolist = self._download_xml(videolist_url, video_id, 'Downloading videolist XML')
|
||||||
hd_url = self._html_search_regex(
|
|
||||||
r'file: \'([^\']+)\', label: \'HD\'', playerdata, 'hd_file',
|
|
||||||
default=None)
|
|
||||||
video_thumbnail = self._html_search_regex(r'image: \'(?P<thumbnail>[^\']+)\'', playerdata, 'thumbnail', fatal=False)
|
|
||||||
|
|
||||||
formats = [{
|
formats = []
|
||||||
'url': sd_url,
|
baseurl = sd_url[:sd_url.rfind('/')+1]
|
||||||
'ext': 'mp4',
|
for video in videolist.findall('.//video'):
|
||||||
'format': 'sd',
|
src = video.get('src')
|
||||||
'format_id': 'sd',
|
if not src:
|
||||||
'quality': 1,
|
continue
|
||||||
}]
|
file_ = src.partition(':')[-1]
|
||||||
if hd_url:
|
width = int_or_none(video.get('width'))
|
||||||
formats.append({
|
height = int_or_none(video.get('height'))
|
||||||
'url': hd_url,
|
bitrate = int_or_none(video.get('system-bitrate'))
|
||||||
'ext': 'mp4',
|
format = {
|
||||||
'format': 'hd',
|
'url': baseurl + file_,
|
||||||
'format_id': 'hd',
|
'format_id': src.rpartition('.')[0].rpartition('_')[-1],
|
||||||
'quality': 2,
|
}
|
||||||
})
|
if width or height:
|
||||||
|
format.update({
|
||||||
|
'tbr': bitrate // 1000 if bitrate else None,
|
||||||
|
'width': width,
|
||||||
|
'height': height,
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
format.update({
|
||||||
|
'abr': bitrate // 1000 if bitrate else None,
|
||||||
|
'vcodec': 'none',
|
||||||
|
})
|
||||||
|
formats.append(format)
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
58
youtube_dl/extractor/clubic.py
Normal file
58
youtube_dl/extractor/clubic.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
clean_html,
|
||||||
|
qualities,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ClubicIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'http://(?:www\.)?clubic\.com/video/[^/]+/video.*-(?P<id>[0-9]+)\.html'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.clubic.com/video/clubic-week/video-clubic-week-2-0-le-fbi-se-lance-dans-la-photo-d-identite-448474.html',
|
||||||
|
'md5': '1592b694ba586036efac1776b0b43cd3',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '448474',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Clubic Week 2.0 : le FBI se lance dans la photo d\u0092identité',
|
||||||
|
'description': 're:Gueule de bois chez Nokia. Le constructeur a indiqué cette.*',
|
||||||
|
'thumbnail': 're:^http://img\.clubic\.com/.*\.jpg$',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
player_url = 'http://player.m6web.fr/v1/player/clubic/%s.html' % video_id
|
||||||
|
player_page = self._download_webpage(player_url, video_id)
|
||||||
|
|
||||||
|
config_json = self._search_regex(
|
||||||
|
r'(?m)M6\.Player\.config\s*=\s*(\{.+?\});$', player_page,
|
||||||
|
'configuration')
|
||||||
|
config = json.loads(config_json)
|
||||||
|
|
||||||
|
video_info = config['videoInfo']
|
||||||
|
sources = config['sources']
|
||||||
|
quality_order = qualities(['sd', 'hq'])
|
||||||
|
|
||||||
|
formats = [{
|
||||||
|
'format_id': src['streamQuality'],
|
||||||
|
'url': src['src'],
|
||||||
|
'quality': quality_order(src['streamQuality']),
|
||||||
|
} for src in sources]
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': video_info['title'],
|
||||||
|
'formats': formats,
|
||||||
|
'description': clean_html(video_info.get('description')),
|
||||||
|
'thumbnail': config.get('poster'),
|
||||||
|
}
|
75
youtube_dl/extractor/cnet.py
Normal file
75
youtube_dl/extractor/cnet.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
ExtractorError,
|
||||||
|
int_or_none,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CNETIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?cnet\.com/videos/(?P<id>[^/]+)/'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.cnet.com/videos/hands-on-with-microsofts-windows-8-1-update/',
|
||||||
|
'md5': '041233212a0d06b179c87cbcca1577b8',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '56f4ea68-bd21-4852-b08c-4de5b8354c60',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Hands-on with Microsoft Windows 8.1 Update',
|
||||||
|
'description': 'The new update to the Windows 8 OS brings improved performance for mouse and keyboard users.',
|
||||||
|
'thumbnail': 're:^http://.*/flmswindows8.jpg$',
|
||||||
|
'uploader_id': 'sarah.mitroff@cbsinteractive.com',
|
||||||
|
'uploader': 'Sarah Mitroff',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
display_id = mobj.group('id')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
data_json = self._html_search_regex(
|
||||||
|
r"<div class=\"cnetVideoPlayer\"\s+.*?data-cnet-video-options='([^']+)'",
|
||||||
|
webpage, 'data json')
|
||||||
|
data = json.loads(data_json)
|
||||||
|
vdata = data['video']
|
||||||
|
if not vdata:
|
||||||
|
vdata = data['videos'][0]
|
||||||
|
if not vdata:
|
||||||
|
raise ExtractorError('Cannot find video data')
|
||||||
|
|
||||||
|
video_id = vdata['id']
|
||||||
|
title = vdata['headline']
|
||||||
|
description = vdata.get('dek')
|
||||||
|
thumbnail = vdata.get('image', {}).get('path')
|
||||||
|
author = vdata.get('author')
|
||||||
|
if author:
|
||||||
|
uploader = '%s %s' % (author['firstName'], author['lastName'])
|
||||||
|
uploader_id = author.get('email')
|
||||||
|
else:
|
||||||
|
uploader = None
|
||||||
|
uploader_id = None
|
||||||
|
|
||||||
|
formats = [{
|
||||||
|
'format_id': '%s-%s-%s' % (
|
||||||
|
f['type'], f['format'],
|
||||||
|
int_or_none(f.get('bitrate'), 1000, default='')),
|
||||||
|
'url': f['uri'],
|
||||||
|
'tbr': int_or_none(f.get('bitrate'), 1000),
|
||||||
|
} for f in vdata['files']['data']]
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'display_id': display_id,
|
||||||
|
'title': title,
|
||||||
|
'formats': formats,
|
||||||
|
'description': description,
|
||||||
|
'uploader': uploader,
|
||||||
|
'uploader_id': uploader_id,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
}
|
@ -8,7 +8,7 @@ from ..utils import (
|
|||||||
compat_str,
|
compat_str,
|
||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
int_or_none,
|
float_or_none,
|
||||||
unified_strdate,
|
unified_strdate,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -21,7 +21,7 @@ class ComedyCentralIE(MTVServicesInfoExtractor):
|
|||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.comedycentral.com/video-clips/kllhuv/stand-up-greg-fitzsimmons--uncensored---too-good-of-a-mother',
|
'url': 'http://www.comedycentral.com/video-clips/kllhuv/stand-up-greg-fitzsimmons--uncensored---too-good-of-a-mother',
|
||||||
'md5': '4167875aae411f903b751a21f357f1ee',
|
'md5': 'c4f48e9eda1b16dd10add0744344b6d8',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'cef0cbb3-e776-4bc9-b62e-8016deccb354',
|
'id': 'cef0cbb3-e776-4bc9-b62e-8016deccb354',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
@ -41,13 +41,15 @@ class ComedyCentralShowsIE(InfoExtractor):
|
|||||||
_VALID_URL = r'''(?x)^(:(?P<shortname>tds|thedailyshow|cr|colbert|colbertnation|colbertreport)
|
_VALID_URL = r'''(?x)^(:(?P<shortname>tds|thedailyshow|cr|colbert|colbertnation|colbertreport)
|
||||||
|https?://(:www\.)?
|
|https?://(:www\.)?
|
||||||
(?P<showname>thedailyshow|thecolbertreport)\.(?:cc\.)?com/
|
(?P<showname>thedailyshow|thecolbertreport)\.(?:cc\.)?com/
|
||||||
(full-episodes/(?P<episode>.*)|
|
((?:full-)?episodes/(?:[0-9a-z]{6}/)?(?P<episode>.*)|
|
||||||
(?P<clip>
|
(?P<clip>
|
||||||
(the-colbert-report-(videos|collections)/(?P<clipID>[0-9]+)/[^/]*/(?P<cntitle>.*?))
|
(?:(?:guests/[^/]+|videos|video-playlists|special-editions)/[^/]+/(?P<videotitle>[^/?#]+))
|
||||||
|(watch/(?P<date>[^/]*)/(?P<tdstitle>.*)))|
|
|(the-colbert-report-(videos|collections)/(?P<clipID>[0-9]+)/[^/]*/(?P<cntitle>.*?))
|
||||||
|
|(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>.*?)(/.*?)?)))
|
||||||
$'''
|
(?:[?#].*|$)'''
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://thedailyshow.cc.com/watch/thu-december-13-2012/kristen-stewart',
|
'url': 'http://thedailyshow.cc.com/watch/thu-december-13-2012/kristen-stewart',
|
||||||
'md5': '4e2f5cb088a83cd8cdb7756132f9739d',
|
'md5': '4e2f5cb088a83cd8cdb7756132f9739d',
|
||||||
@ -57,7 +59,7 @@ class ComedyCentralShowsIE(InfoExtractor):
|
|||||||
'upload_date': '20121213',
|
'upload_date': '20121213',
|
||||||
'description': 'Kristen Stewart learns to let loose in "On the Road."',
|
'description': 'Kristen Stewart learns to let loose in "On the Road."',
|
||||||
'uploader': 'thedailyshow',
|
'uploader': 'thedailyshow',
|
||||||
'title': 'thedailyshow-kristen-stewart part 1',
|
'title': 'thedailyshow kristen-stewart part 1',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,7 +104,9 @@ class ComedyCentralShowsIE(InfoExtractor):
|
|||||||
assert mobj is not None
|
assert mobj is not None
|
||||||
|
|
||||||
if mobj.group('clip'):
|
if mobj.group('clip'):
|
||||||
if mobj.group('showname') == 'thedailyshow':
|
if mobj.group('videotitle'):
|
||||||
|
epTitle = mobj.group('videotitle')
|
||||||
|
elif mobj.group('showname') == 'thedailyshow':
|
||||||
epTitle = mobj.group('tdstitle')
|
epTitle = mobj.group('tdstitle')
|
||||||
else:
|
else:
|
||||||
epTitle = mobj.group('cntitle')
|
epTitle = mobj.group('cntitle')
|
||||||
@ -159,9 +163,9 @@ class ComedyCentralShowsIE(InfoExtractor):
|
|||||||
thumbnail = itemEl.find('.//{http://search.yahoo.com/mrss/}thumbnail').attrib.get('url')
|
thumbnail = itemEl.find('.//{http://search.yahoo.com/mrss/}thumbnail').attrib.get('url')
|
||||||
|
|
||||||
content = itemEl.find('.//{http://search.yahoo.com/mrss/}content')
|
content = itemEl.find('.//{http://search.yahoo.com/mrss/}content')
|
||||||
duration = int_or_none(content.attrib.get('duration'))
|
duration = float_or_none(content.attrib.get('duration'))
|
||||||
mediagen_url = content.attrib['url']
|
mediagen_url = content.attrib['url']
|
||||||
guid = itemEl.find('.//guid').text.rpartition(':')[-1]
|
guid = itemEl.find('./guid').text.rpartition(':')[-1]
|
||||||
|
|
||||||
cdoc = self._download_xml(
|
cdoc = self._download_xml(
|
||||||
mediagen_url, epTitle,
|
mediagen_url, epTitle,
|
||||||
@ -184,7 +188,7 @@ class ComedyCentralShowsIE(InfoExtractor):
|
|||||||
})
|
})
|
||||||
formats.append({
|
formats.append({
|
||||||
'format_id': 'rtmp-%s' % format,
|
'format_id': 'rtmp-%s' % format,
|
||||||
'url': rtmp_video_url,
|
'url': rtmp_video_url.replace('viacomccstrm', 'viacommtvstrm'),
|
||||||
'ext': self._video_extensions.get(format, 'mp4'),
|
'ext': self._video_extensions.get(format, 'mp4'),
|
||||||
'height': h,
|
'height': h,
|
||||||
'width': w,
|
'width': w,
|
||||||
|
@ -113,6 +113,8 @@ class InfoExtractor(object):
|
|||||||
webpage_url: The url to the video webpage, if given to youtube-dl it
|
webpage_url: The url to the video webpage, if given to youtube-dl it
|
||||||
should allow to get the same result again. (It will be set
|
should allow to get the same result again. (It will be set
|
||||||
by YoutubeDL if it's missing)
|
by YoutubeDL if it's missing)
|
||||||
|
categories: A list of categories that the video falls in, for example
|
||||||
|
["Sports", "Berlin"]
|
||||||
|
|
||||||
Unless mentioned otherwise, the fields should be Unicode strings.
|
Unless mentioned otherwise, the fields should be Unicode strings.
|
||||||
|
|
||||||
@ -242,16 +244,31 @@ class InfoExtractor(object):
|
|||||||
url = url_or_request.get_full_url()
|
url = url_or_request.get_full_url()
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
url = url_or_request
|
url = url_or_request
|
||||||
if len(url) > 200:
|
basen = '%s_%s' % (video_id, url)
|
||||||
h = u'___' + hashlib.md5(url.encode('utf-8')).hexdigest()
|
if len(basen) > 240:
|
||||||
url = url[:200 - len(h)] + h
|
h = u'___' + hashlib.md5(basen.encode('utf-8')).hexdigest()
|
||||||
raw_filename = ('%s_%s.dump' % (video_id, url))
|
basen = basen[:240 - len(h)] + h
|
||||||
|
raw_filename = basen + '.dump'
|
||||||
filename = sanitize_filename(raw_filename, restricted=True)
|
filename = sanitize_filename(raw_filename, restricted=True)
|
||||||
self.to_screen(u'Saving request to ' + filename)
|
self.to_screen(u'Saving request to ' + filename)
|
||||||
with open(filename, 'wb') as outf:
|
with open(filename, 'wb') as outf:
|
||||||
outf.write(webpage_bytes)
|
outf.write(webpage_bytes)
|
||||||
|
|
||||||
content = webpage_bytes.decode(encoding, 'replace')
|
try:
|
||||||
|
content = webpage_bytes.decode(encoding, 'replace')
|
||||||
|
except LookupError:
|
||||||
|
content = webpage_bytes.decode('utf-8', 'replace')
|
||||||
|
|
||||||
|
if (u'<title>Access to this site is blocked</title>' in content and
|
||||||
|
u'Websense' in content[:512]):
|
||||||
|
msg = u'Access to this webpage has been blocked by Websense filtering software in your network.'
|
||||||
|
blocked_iframe = self._html_search_regex(
|
||||||
|
r'<iframe src="([^"]+)"', content,
|
||||||
|
u'Websense information URL', default=None)
|
||||||
|
if blocked_iframe:
|
||||||
|
msg += u' Visit %s for more details' % blocked_iframe
|
||||||
|
raise ExtractorError(msg, expected=True)
|
||||||
|
|
||||||
return (content, urlh)
|
return (content, urlh)
|
||||||
|
|
||||||
def _download_webpage(self, url_or_request, video_id, note=None, errnote=None, fatal=True):
|
def _download_webpage(self, url_or_request, video_id, note=None, errnote=None, fatal=True):
|
||||||
@ -265,9 +282,12 @@ class InfoExtractor(object):
|
|||||||
|
|
||||||
def _download_xml(self, url_or_request, video_id,
|
def _download_xml(self, url_or_request, video_id,
|
||||||
note=u'Downloading XML', errnote=u'Unable to download XML',
|
note=u'Downloading XML', errnote=u'Unable to download XML',
|
||||||
transform_source=None):
|
transform_source=None, fatal=True):
|
||||||
"""Return the xml as an xml.etree.ElementTree.Element"""
|
"""Return the xml as an xml.etree.ElementTree.Element"""
|
||||||
xml_string = self._download_webpage(url_or_request, video_id, note, errnote)
|
xml_string = self._download_webpage(
|
||||||
|
url_or_request, video_id, note, errnote, fatal=fatal)
|
||||||
|
if xml_string is False:
|
||||||
|
return xml_string
|
||||||
if transform_source:
|
if transform_source:
|
||||||
xml_string = transform_source(xml_string)
|
xml_string = transform_source(xml_string)
|
||||||
return xml.etree.ElementTree.fromstring(xml_string.encode('utf-8'))
|
return xml.etree.ElementTree.fromstring(xml_string.encode('utf-8'))
|
||||||
@ -531,6 +551,23 @@ class InfoExtractor(object):
|
|||||||
)
|
)
|
||||||
formats.sort(key=_formats_key)
|
formats.sort(key=_formats_key)
|
||||||
|
|
||||||
|
def http_scheme(self):
|
||||||
|
""" Either "https:" or "https:", depending on the user's preferences """
|
||||||
|
return (
|
||||||
|
'http:'
|
||||||
|
if self._downloader.params.get('prefer_insecure', False)
|
||||||
|
else 'https:')
|
||||||
|
|
||||||
|
def _proto_relative_url(self, url, scheme=None):
|
||||||
|
if url is None:
|
||||||
|
return url
|
||||||
|
if url.startswith('//'):
|
||||||
|
if scheme is None:
|
||||||
|
scheme = self.http_scheme()
|
||||||
|
return scheme + url
|
||||||
|
else:
|
||||||
|
return url
|
||||||
|
|
||||||
|
|
||||||
class SearchInfoExtractor(InfoExtractor):
|
class SearchInfoExtractor(InfoExtractor):
|
||||||
"""
|
"""
|
||||||
@ -574,3 +611,4 @@ class SearchInfoExtractor(InfoExtractor):
|
|||||||
@property
|
@property
|
||||||
def SEARCH_KEY(self):
|
def SEARCH_KEY(self):
|
||||||
return self._SEARCH_KEY
|
return self._SEARCH_KEY
|
||||||
|
|
||||||
|
@ -28,16 +28,18 @@ class CondeNastIE(InfoExtractor):
|
|||||||
'glamour': 'Glamour',
|
'glamour': 'Glamour',
|
||||||
'wmagazine': 'W Magazine',
|
'wmagazine': 'W Magazine',
|
||||||
'vanityfair': 'Vanity Fair',
|
'vanityfair': 'Vanity Fair',
|
||||||
|
'cnevids': 'Condé Nast',
|
||||||
}
|
}
|
||||||
|
|
||||||
_VALID_URL = r'http://(video|www)\.(?P<site>%s)\.com/(?P<type>watch|series|video)/(?P<id>.+)' % '|'.join(_SITES.keys())
|
_VALID_URL = r'http://(video|www|player)\.(?P<site>%s)\.com/(?P<type>watch|series|video|embed)/(?P<id>[^/?#]+)' % '|'.join(_SITES.keys())
|
||||||
IE_DESC = 'Condé Nast media group: %s' % ', '.join(sorted(_SITES.values()))
|
IE_DESC = 'Condé Nast media group: %s' % ', '.join(sorted(_SITES.values()))
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://video.wired.com/watch/3d-printed-speakers-lit-with-led',
|
'url': 'http://video.wired.com/watch/3d-printed-speakers-lit-with-led',
|
||||||
'file': '5171b343c2b4c00dd0c1ccb3.mp4',
|
|
||||||
'md5': '1921f713ed48aabd715691f774c451f7',
|
'md5': '1921f713ed48aabd715691f774c451f7',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'id': '5171b343c2b4c00dd0c1ccb3',
|
||||||
|
'ext': 'mp4',
|
||||||
'title': '3D Printed Speakers Lit With LED',
|
'title': '3D Printed Speakers Lit With LED',
|
||||||
'description': 'Check out these beautiful 3D printed LED speakers. You can\'t actually buy them, but LumiGeek is working on a board that will let you make you\'re own.',
|
'description': 'Check out these beautiful 3D printed LED speakers. You can\'t actually buy them, but LumiGeek is working on a board that will let you make you\'re own.',
|
||||||
}
|
}
|
||||||
@ -55,12 +57,16 @@ class CondeNastIE(InfoExtractor):
|
|||||||
entries = [self.url_result(build_url(path), 'CondeNast') for path in paths]
|
entries = [self.url_result(build_url(path), 'CondeNast') for path in paths]
|
||||||
return self.playlist_result(entries, playlist_title=title)
|
return self.playlist_result(entries, playlist_title=title)
|
||||||
|
|
||||||
def _extract_video(self, webpage):
|
def _extract_video(self, webpage, url_type):
|
||||||
description = self._html_search_regex([r'<div class="cne-video-description">(.+?)</div>',
|
if url_type != 'embed':
|
||||||
r'<div class="video-post-content">(.+?)</div>',
|
description = self._html_search_regex(
|
||||||
],
|
[
|
||||||
webpage, 'description',
|
r'<div class="cne-video-description">(.+?)</div>',
|
||||||
fatal=False, flags=re.DOTALL)
|
r'<div class="video-post-content">(.+?)</div>',
|
||||||
|
],
|
||||||
|
webpage, 'description', fatal=False, flags=re.DOTALL)
|
||||||
|
else:
|
||||||
|
description = None
|
||||||
params = self._search_regex(r'var params = {(.+?)}[;,]', webpage,
|
params = self._search_regex(r'var params = {(.+?)}[;,]', webpage,
|
||||||
'player params', flags=re.DOTALL)
|
'player params', flags=re.DOTALL)
|
||||||
video_id = self._search_regex(r'videoId: [\'"](.+?)[\'"]', params, 'video id')
|
video_id = self._search_regex(r'videoId: [\'"](.+?)[\'"]', params, 'video id')
|
||||||
@ -99,12 +105,12 @@ class CondeNastIE(InfoExtractor):
|
|||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
site = mobj.group('site')
|
site = mobj.group('site')
|
||||||
url_type = mobj.group('type')
|
url_type = mobj.group('type')
|
||||||
id = mobj.group('id')
|
item_id = mobj.group('id')
|
||||||
|
|
||||||
self.to_screen(u'Extracting from %s with the Condé Nast extractor' % self._SITES[site])
|
self.to_screen('Extracting from %s with the Condé Nast extractor' % self._SITES[site])
|
||||||
webpage = self._download_webpage(url, id)
|
webpage = self._download_webpage(url, item_id)
|
||||||
|
|
||||||
if url_type == 'series':
|
if url_type == 'series':
|
||||||
return self._extract_series(url, webpage)
|
return self._extract_series(url, webpage)
|
||||||
else:
|
else:
|
||||||
return self._extract_video(webpage)
|
return self._extract_video(webpage, url_type)
|
||||||
|
@ -4,6 +4,7 @@ import re
|
|||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
|
int_or_none,
|
||||||
unescapeHTML,
|
unescapeHTML,
|
||||||
find_xpath_attr,
|
find_xpath_attr,
|
||||||
)
|
)
|
||||||
@ -54,18 +55,29 @@ class CSpanIE(InfoExtractor):
|
|||||||
info_url = 'http://c-spanvideo.org/videoLibrary/assets/player/ajax-player.php?os=android&html5=program&id=' + video_id
|
info_url = 'http://c-spanvideo.org/videoLibrary/assets/player/ajax-player.php?os=android&html5=program&id=' + video_id
|
||||||
data = self._download_json(info_url, video_id)
|
data = self._download_json(info_url, video_id)
|
||||||
|
|
||||||
url = unescapeHTML(data['video']['files'][0]['path']['#text'])
|
doc = self._download_xml(
|
||||||
|
'http://www.c-span.org/common/services/flashXml.php?programid=' + video_id,
|
||||||
doc = self._download_xml('http://www.c-span.org/common/services/flashXml.php?programid=' + video_id,
|
|
||||||
video_id)
|
video_id)
|
||||||
|
|
||||||
def find_string(s):
|
title = find_xpath_attr(doc, './/string', 'name', 'title').text
|
||||||
return find_xpath_attr(doc, './/string', 'name', s).text
|
thumbnail = find_xpath_attr(doc, './/string', 'name', 'poster').text
|
||||||
|
|
||||||
|
files = data['video']['files']
|
||||||
|
|
||||||
|
entries = [{
|
||||||
|
'id': '%s_%d' % (video_id, partnum + 1),
|
||||||
|
'title': (
|
||||||
|
title if len(files) == 1 else
|
||||||
|
'%s part %d' % (title, partnum + 1)),
|
||||||
|
'url': unescapeHTML(f['path']['#text']),
|
||||||
|
'description': description,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'duration': int_or_none(f.get('length', {}).get('#text')),
|
||||||
|
} for partnum, f in enumerate(files)]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
'_type': 'playlist',
|
||||||
|
'entries': entries,
|
||||||
|
'title': title,
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': find_string('title'),
|
|
||||||
'url': url,
|
|
||||||
'description': description,
|
|
||||||
'thumbnail': find_string('poster'),
|
|
||||||
}
|
}
|
||||||
|
@ -8,13 +8,11 @@ from .subtitles import SubtitlesInfoExtractor
|
|||||||
from ..utils import (
|
from ..utils import (
|
||||||
compat_urllib_request,
|
compat_urllib_request,
|
||||||
compat_str,
|
compat_str,
|
||||||
get_element_by_attribute,
|
|
||||||
get_element_by_id,
|
|
||||||
orderedSet,
|
orderedSet,
|
||||||
str_to_int,
|
str_to_int,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
|
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
|
unescapeHTML,
|
||||||
)
|
)
|
||||||
|
|
||||||
class DailymotionBaseInfoExtractor(InfoExtractor):
|
class DailymotionBaseInfoExtractor(InfoExtractor):
|
||||||
@ -180,7 +178,7 @@ class DailymotionIE(DailymotionBaseInfoExtractor, SubtitlesInfoExtractor):
|
|||||||
class DailymotionPlaylistIE(DailymotionBaseInfoExtractor):
|
class DailymotionPlaylistIE(DailymotionBaseInfoExtractor):
|
||||||
IE_NAME = u'dailymotion:playlist'
|
IE_NAME = u'dailymotion:playlist'
|
||||||
_VALID_URL = r'(?:https?://)?(?:www\.)?dailymotion\.[a-z]{2,3}/playlist/(?P<id>.+?)/'
|
_VALID_URL = r'(?:https?://)?(?:www\.)?dailymotion\.[a-z]{2,3}/playlist/(?P<id>.+?)/'
|
||||||
_MORE_PAGES_INDICATOR = r'<div class="next">.*?<a.*?href="/playlist/.+?".*?>.*?</a>.*?</div>'
|
_MORE_PAGES_INDICATOR = r'(?s)<div class="pages[^"]*">.*?<a\s+class="[^"]*?icon-arrow_right[^"]*?"'
|
||||||
_PAGE_TEMPLATE = 'https://www.dailymotion.com/playlist/%s/%s'
|
_PAGE_TEMPLATE = 'https://www.dailymotion.com/playlist/%s/%s'
|
||||||
|
|
||||||
def _extract_entries(self, id):
|
def _extract_entries(self, id):
|
||||||
@ -190,10 +188,9 @@ class DailymotionPlaylistIE(DailymotionBaseInfoExtractor):
|
|||||||
webpage = self._download_webpage(request,
|
webpage = self._download_webpage(request,
|
||||||
id, u'Downloading page %s' % pagenum)
|
id, u'Downloading page %s' % pagenum)
|
||||||
|
|
||||||
playlist_el = get_element_by_attribute(u'class', u'row video_list', webpage)
|
video_ids.extend(re.findall(r'data-xid="(.+?)"', webpage))
|
||||||
video_ids.extend(re.findall(r'data-id="(.+?)"', playlist_el))
|
|
||||||
|
|
||||||
if re.search(self._MORE_PAGES_INDICATOR, webpage, re.DOTALL) is None:
|
if re.search(self._MORE_PAGES_INDICATOR, webpage) is None:
|
||||||
break
|
break
|
||||||
return [self.url_result('http://www.dailymotion.com/video/%s' % video_id, 'Dailymotion')
|
return [self.url_result('http://www.dailymotion.com/video/%s' % video_id, 'Dailymotion')
|
||||||
for video_id in orderedSet(video_ids)]
|
for video_id in orderedSet(video_ids)]
|
||||||
@ -203,26 +200,26 @@ class DailymotionPlaylistIE(DailymotionBaseInfoExtractor):
|
|||||||
playlist_id = mobj.group('id')
|
playlist_id = mobj.group('id')
|
||||||
webpage = self._download_webpage(url, playlist_id)
|
webpage = self._download_webpage(url, playlist_id)
|
||||||
|
|
||||||
return {'_type': 'playlist',
|
return {
|
||||||
'id': playlist_id,
|
'_type': 'playlist',
|
||||||
'title': get_element_by_id(u'playlist_name', webpage),
|
'id': playlist_id,
|
||||||
'entries': self._extract_entries(playlist_id),
|
'title': self._og_search_title(webpage),
|
||||||
}
|
'entries': self._extract_entries(playlist_id),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class DailymotionUserIE(DailymotionPlaylistIE):
|
class DailymotionUserIE(DailymotionPlaylistIE):
|
||||||
IE_NAME = u'dailymotion:user'
|
IE_NAME = u'dailymotion:user'
|
||||||
_VALID_URL = r'(?:https?://)?(?:www\.)?dailymotion\.[a-z]{2,3}/user/(?P<user>[^/]+)'
|
_VALID_URL = r'https?://(?:www\.)?dailymotion\.[a-z]{2,3}/user/(?P<user>[^/]+)'
|
||||||
_MORE_PAGES_INDICATOR = r'<div class="next">.*?<a.*?href="/user/.+?".*?>.*?</a>.*?</div>'
|
|
||||||
_PAGE_TEMPLATE = 'http://www.dailymotion.com/user/%s/%s'
|
_PAGE_TEMPLATE = 'http://www.dailymotion.com/user/%s/%s'
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
user = mobj.group('user')
|
user = mobj.group('user')
|
||||||
webpage = self._download_webpage(url, user)
|
webpage = self._download_webpage(url, user)
|
||||||
full_user = self._html_search_regex(
|
full_user = unescapeHTML(self._html_search_regex(
|
||||||
r'<a class="label" href="/%s".*?>(.*?)</' % re.escape(user),
|
r'<a class="nav-image" title="([^"]+)" href="/%s">' % re.escape(user),
|
||||||
webpage, u'user', flags=re.DOTALL)
|
webpage, u'user', flags=re.DOTALL))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'_type': 'playlist',
|
'_type': 'playlist',
|
||||||
|
@ -10,9 +10,10 @@ class DiscoveryIE(InfoExtractor):
|
|||||||
_VALID_URL = r'http://dsc\.discovery\.com\/[a-zA-Z0-9\-]*/[a-zA-Z0-9\-]*/videos/(?P<id>[a-zA-Z0-9\-]*)(.htm)?'
|
_VALID_URL = r'http://dsc\.discovery\.com\/[a-zA-Z0-9\-]*/[a-zA-Z0-9\-]*/videos/(?P<id>[a-zA-Z0-9\-]*)(.htm)?'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://dsc.discovery.com/tv-shows/mythbusters/videos/mission-impossible-outtakes.htm',
|
'url': 'http://dsc.discovery.com/tv-shows/mythbusters/videos/mission-impossible-outtakes.htm',
|
||||||
'file': '614784.mp4',
|
|
||||||
'md5': 'e12614f9ee303a6ccef415cb0793eba2',
|
'md5': 'e12614f9ee303a6ccef415cb0793eba2',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'id': '614784',
|
||||||
|
'ext': 'mp4',
|
||||||
'title': 'MythBusters: Mission Impossible Outtakes',
|
'title': 'MythBusters: Mission Impossible Outtakes',
|
||||||
'description': ('Watch Jamie Hyneman and Adam Savage practice being'
|
'description': ('Watch Jamie Hyneman and Adam Savage practice being'
|
||||||
' each other -- to the point of confusing Jamie\'s dog -- and '
|
' each other -- to the point of confusing Jamie\'s dog -- and '
|
||||||
@ -34,7 +35,7 @@ class DiscoveryIE(InfoExtractor):
|
|||||||
formats = []
|
formats = []
|
||||||
for f in info['mp4']:
|
for f in info['mp4']:
|
||||||
formats.append(
|
formats.append(
|
||||||
{'url': f['src'], r'ext': r'mp4', 'tbr': int(f['bitrate'][:-1])})
|
{'url': f['src'], 'ext': 'mp4', 'tbr': int(f['bitrate'][:-1])})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': info['contentId'],
|
'id': info['contentId'],
|
||||||
|
27
youtube_dl/extractor/divxstage.py
Normal file
27
youtube_dl/extractor/divxstage.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .novamov import NovaMovIE
|
||||||
|
|
||||||
|
|
||||||
|
class DivxStageIE(NovaMovIE):
|
||||||
|
IE_NAME = 'divxstage'
|
||||||
|
IE_DESC = 'DivxStage'
|
||||||
|
|
||||||
|
_VALID_URL = NovaMovIE._VALID_URL_TEMPLATE % {'host': 'divxstage\.(?:eu|net|ch|co|at|ag)'}
|
||||||
|
|
||||||
|
_HOST = 'www.divxstage.eu'
|
||||||
|
|
||||||
|
_FILE_DELETED_REGEX = r'>This file no longer exists on our servers.<'
|
||||||
|
_TITLE_REGEX = r'<div class="video_det">\s*<strong>([^<]+)</strong>'
|
||||||
|
_DESCRIPTION_REGEX = r'<div class="video_det">\s*<strong>[^<]+</strong>\s*<p>([^<]+)</p>'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.divxstage.eu/video/57f238e2e5e01',
|
||||||
|
'md5': '63969f6eb26533a1968c4d325be63e72',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '57f238e2e5e01',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'youtubedl test video',
|
||||||
|
'description': 'This is a test video for youtubedl.',
|
||||||
|
}
|
||||||
|
}
|
@ -1,23 +1,25 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
determine_ext
|
|
||||||
)
|
)
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
class EHowIE(InfoExtractor):
|
class EHowIE(InfoExtractor):
|
||||||
IE_NAME = u'eHow'
|
IE_NAME = 'eHow'
|
||||||
_VALID_URL = r'(?:https?://)?(?:www\.)?ehow\.com/[^/_?]*_(?P<id>[0-9]+)'
|
_VALID_URL = r'https?://(?:www\.)?ehow\.com/[^/_?]*_(?P<id>[0-9]+)'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
u'url': u'http://www.ehow.com/video_12245069_hardwood-flooring-basics.html',
|
'url': 'http://www.ehow.com/video_12245069_hardwood-flooring-basics.html',
|
||||||
u'file': u'12245069.flv',
|
'md5': '9809b4e3f115ae2088440bcb4efbf371',
|
||||||
u'md5': u'9809b4e3f115ae2088440bcb4efbf371',
|
'info_dict': {
|
||||||
u'info_dict': {
|
'id': '12245069',
|
||||||
u"title": u"Hardwood Flooring Basics",
|
'ext': 'flv',
|
||||||
u"description": u"Hardwood flooring may be time consuming, but its ultimately a pretty straightforward concept. Learn about hardwood flooring basics with help from a hardware flooring business owner in this free video...",
|
'title': 'Hardwood Flooring Basics',
|
||||||
u"uploader": u"Erick Nathan"
|
'description': 'Hardwood flooring may be time consuming, but its ultimately a pretty straightforward concept. Learn about hardwood flooring basics with help from a hardware flooring business owner in this free video...',
|
||||||
|
'uploader': 'Erick Nathan',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,21 +28,16 @@ class EHowIE(InfoExtractor):
|
|||||||
video_id = mobj.group('id')
|
video_id = mobj.group('id')
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
video_url = self._search_regex(r'(?:file|source)=(http[^\'"&]*)',
|
video_url = self._search_regex(r'(?:file|source)=(http[^\'"&]*)',
|
||||||
webpage, u'video URL')
|
webpage, 'video URL')
|
||||||
final_url = compat_urllib_parse.unquote(video_url)
|
final_url = compat_urllib_parse.unquote(video_url)
|
||||||
uploader = self._search_regex(r'<meta name="uploader" content="(.+?)" />',
|
uploader = self._html_search_meta('uploader', webpage)
|
||||||
webpage, u'uploader')
|
|
||||||
title = self._og_search_title(webpage).replace(' | eHow', '')
|
title = self._og_search_title(webpage).replace(' | eHow', '')
|
||||||
ext = determine_ext(final_url)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'_type': 'video',
|
'id': video_id,
|
||||||
'id': video_id,
|
'url': final_url,
|
||||||
'url': final_url,
|
'title': title,
|
||||||
'ext': ext,
|
'thumbnail': self._og_search_thumbnail(webpage),
|
||||||
'title': title,
|
|
||||||
'thumbnail': self._og_search_thumbnail(webpage),
|
|
||||||
'description': self._og_search_description(webpage),
|
'description': self._og_search_description(webpage),
|
||||||
'uploader': uploader,
|
'uploader': uploader,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
54
youtube_dl/extractor/empflix.py
Normal file
54
youtube_dl/extractor/empflix.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
|
class EmpflixIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'^https?://www\.empflix\.com/videos/.*?-(?P<id>[0-9]+)\.html'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.empflix.com/videos/Amateur-Finger-Fuck-33051.html',
|
||||||
|
'md5': 'b1bc15b6412d33902d6e5952035fcabc',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '33051',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Amateur Finger Fuck',
|
||||||
|
'description': 'Amateur solo finger fucking.',
|
||||||
|
'age_limit': 18,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
age_limit = self._rta_search(webpage)
|
||||||
|
|
||||||
|
video_title = self._html_search_regex(
|
||||||
|
r'name="title" value="(?P<title>[^"]*)"', webpage, 'title')
|
||||||
|
video_description = self._html_search_regex(
|
||||||
|
r'name="description" value="([^"]*)"', webpage, 'description', fatal=False)
|
||||||
|
|
||||||
|
cfg_url = self._html_search_regex(
|
||||||
|
r'flashvars\.config = escape\("([^"]+)"',
|
||||||
|
webpage, 'flashvars.config')
|
||||||
|
|
||||||
|
cfg_xml = self._download_xml(
|
||||||
|
cfg_url, video_id, note='Downloading metadata')
|
||||||
|
|
||||||
|
formats = [
|
||||||
|
{
|
||||||
|
'url': item.find('videoLink').text,
|
||||||
|
'format_id': item.find('res').text,
|
||||||
|
} for item in cfg_xml.findall('./quality/item')
|
||||||
|
]
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': video_title,
|
||||||
|
'description': video_description,
|
||||||
|
'formats': formats,
|
||||||
|
'age_limit': age_limit,
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
import os
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
@ -8,18 +9,23 @@ from ..utils import (
|
|||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ExtremeTubeIE(InfoExtractor):
|
class ExtremeTubeIE(InfoExtractor):
|
||||||
_VALID_URL = r'^(?:https?://)?(?:www\.)?(?P<url>extremetube\.com/video/.+?(?P<videoid>[0-9]+))(?:[/?&]|$)'
|
_VALID_URL = r'^(?:https?://)?(?:www\.)?(?P<url>extremetube\.com/.*?video/.+?(?P<videoid>[0-9]+))(?:[/?&]|$)'
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
u'url': u'http://www.extremetube.com/video/music-video-14-british-euro-brit-european-cumshots-swallow-652431',
|
'url': 'http://www.extremetube.com/video/music-video-14-british-euro-brit-european-cumshots-swallow-652431',
|
||||||
u'file': u'652431.mp4',
|
'md5': '1fb9228f5e3332ec8c057d6ac36f33e0',
|
||||||
u'md5': u'1fb9228f5e3332ec8c057d6ac36f33e0',
|
'info_dict': {
|
||||||
u'info_dict': {
|
'id': '652431',
|
||||||
u"title": u"Music Video 14 british euro brit european cumshots swallow",
|
'ext': 'mp4',
|
||||||
u"uploader": u"unknown",
|
'title': 'Music Video 14 british euro brit european cumshots swallow',
|
||||||
u"age_limit": 18,
|
'uploader': 'unknown',
|
||||||
|
'age_limit': 18,
|
||||||
}
|
}
|
||||||
}
|
}, {
|
||||||
|
'url': 'http://www.extremetube.com/gay/video/abcde-1234',
|
||||||
|
'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)
|
||||||
@ -30,11 +36,14 @@ class ExtremeTubeIE(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)
|
||||||
|
|
||||||
video_title = self._html_search_regex(r'<h1 [^>]*?title="([^"]+)"[^>]*>\1<', webpage, u'title')
|
video_title = self._html_search_regex(
|
||||||
uploader = self._html_search_regex(r'>Posted by:(?=<)(?:\s|<[^>]*>)*(.+?)\|', webpage, u'uploader', fatal=False)
|
r'<h1 [^>]*?title="([^"]+)"[^>]*>\1<', webpage, 'title')
|
||||||
video_url = compat_urllib_parse.unquote(self._html_search_regex(r'video_url=(.+?)&', webpage, u'video_url'))
|
uploader = self._html_search_regex(
|
||||||
|
r'>Posted by:(?=<)(?:\s|<[^>]*>)*(.+?)\|', webpage, 'uploader',
|
||||||
|
fatal=False)
|
||||||
|
video_url = compat_urllib_parse.unquote(self._html_search_regex(
|
||||||
|
r'video_url=(.+?)&', webpage, 'video_url'))
|
||||||
path = compat_urllib_parse_urlparse(video_url).path
|
path = compat_urllib_parse_urlparse(video_url).path
|
||||||
extension = os.path.splitext(path)[1][1:]
|
|
||||||
format = path.split('/')[5].split('_')[:2]
|
format = path.split('/')[5].split('_')[:2]
|
||||||
format = "-".join(format)
|
format = "-".join(format)
|
||||||
|
|
||||||
@ -43,7 +52,6 @@ class ExtremeTubeIE(InfoExtractor):
|
|||||||
'title': video_title,
|
'title': video_title,
|
||||||
'uploader': uploader,
|
'uploader': uploader,
|
||||||
'url': video_url,
|
'url': video_url,
|
||||||
'ext': extension,
|
|
||||||
'format': format,
|
'format': format,
|
||||||
'format_id': format,
|
'format_id': format,
|
||||||
'age_limit': 18,
|
'age_limit': 18,
|
||||||
|
@ -76,9 +76,8 @@ class FacebookIE(InfoExtractor):
|
|||||||
|
|
||||||
check_form = {
|
check_form = {
|
||||||
'fb_dtsg': self._search_regex(r'name="fb_dtsg" value="(.+?)"', login_results, 'fb_dtsg'),
|
'fb_dtsg': self._search_regex(r'name="fb_dtsg" value="(.+?)"', login_results, 'fb_dtsg'),
|
||||||
'nh': self._search_regex(r'name="nh" value="(\w*?)"', login_results, 'nh'),
|
'h': self._search_regex(r'name="h" value="(\w*?)"', login_results, 'h'),
|
||||||
'name_action_selected': 'dont_save',
|
'name_action_selected': 'dont_save',
|
||||||
'submit[Continue]': self._search_regex(r'<button[^>]+value="(.*?)"[^>]+name="submit\[Continue\]"', login_results, 'continue'),
|
|
||||||
}
|
}
|
||||||
check_req = compat_urllib_request.Request(self._CHECKPOINT_URL, urlencode_postdata(check_form))
|
check_req = compat_urllib_request.Request(self._CHECKPOINT_URL, urlencode_postdata(check_form))
|
||||||
check_req.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
check_req.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
||||||
|
60
youtube_dl/extractor/fc2.py
Normal file
60
youtube_dl/extractor/fc2.py
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
#! -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
ExtractorError,
|
||||||
|
compat_urllib_request,
|
||||||
|
compat_urlparse,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class FC2IE(InfoExtractor):
|
||||||
|
_VALID_URL = r'^http://video\.fc2\.com/((?P<lang>[^/]+)/)?content/(?P<id>[^/]+)'
|
||||||
|
IE_NAME = 'fc2'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://video.fc2.com/en/content/20121103kUan1KHs',
|
||||||
|
'md5': 'a6ebe8ebe0396518689d963774a54eb7',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '20121103kUan1KHs',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Boxing again with Puff',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
self._downloader.cookiejar.clear_session_cookies() # must clear
|
||||||
|
|
||||||
|
title = self._og_search_title(webpage)
|
||||||
|
thumbnail = self._og_search_thumbnail(webpage)
|
||||||
|
refer = url.replace('/content/', '/a/content/')
|
||||||
|
|
||||||
|
mimi = hashlib.md5((video_id + '_gGddgPfeaf_gzyr').encode('utf-8')).hexdigest()
|
||||||
|
|
||||||
|
info_url = (
|
||||||
|
"http://video.fc2.com/ginfo.php?mimi={1:s}&href={2:s}&v={0:s}&fversion=WIN%2011%2C6%2C602%2C180&from=2&otag=0&upid={0:s}&tk=null&".
|
||||||
|
format(video_id, mimi, compat_urllib_request.quote(refer, safe='').replace('.','%2E')))
|
||||||
|
|
||||||
|
info_webpage = self._download_webpage(
|
||||||
|
info_url, video_id, note='Downloading info page')
|
||||||
|
info = compat_urlparse.parse_qs(info_webpage)
|
||||||
|
|
||||||
|
if 'err_code' in info:
|
||||||
|
raise ExtractorError('Error code: %s' % info['err_code'][0])
|
||||||
|
|
||||||
|
video_url = info['filepath'][0] + '?mid=' + info['mid'][0]
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': info['title'][0],
|
||||||
|
'url': video_url,
|
||||||
|
'ext': 'flv',
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
}
|
@ -6,7 +6,6 @@ from .common import InfoExtractor
|
|||||||
|
|
||||||
|
|
||||||
class FirstpostIE(InfoExtractor):
|
class FirstpostIE(InfoExtractor):
|
||||||
IE_NAME = 'Firstpost.com'
|
|
||||||
_VALID_URL = r'http://(?:www\.)?firstpost\.com/[^/]+/.*-(?P<id>[0-9]+)\.html'
|
_VALID_URL = r'http://(?:www\.)?firstpost\.com/[^/]+/.*-(?P<id>[0-9]+)\.html'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
@ -16,7 +15,6 @@ class FirstpostIE(InfoExtractor):
|
|||||||
'id': '1025403',
|
'id': '1025403',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'India to launch indigenous aircraft carrier INS Vikrant today',
|
'title': 'India to launch indigenous aircraft carrier INS Vikrant today',
|
||||||
'description': 'Its flight deck is over twice the size of a football field, its power unit can light up the entire Kochi city and the cabling is enough to cover the distance between here to Delhi.',
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,15 +22,26 @@ class FirstpostIE(InfoExtractor):
|
|||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
video_id = mobj.group('id')
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
data = self._download_xml(
|
||||||
video_url = self._html_search_regex(
|
'http://www.firstpost.com/getvideoxml-%s.xml' % video_id, video_id,
|
||||||
r'<div.*?name="div_video".*?flashvars="([^"]+)">',
|
'Downloading video XML')
|
||||||
webpage, 'video URL')
|
|
||||||
|
item = data.find('./playlist/item')
|
||||||
|
thumbnail = item.find('./image').text
|
||||||
|
title = item.find('./title').text
|
||||||
|
|
||||||
|
formats = [
|
||||||
|
{
|
||||||
|
'url': details.find('./file').text,
|
||||||
|
'format_id': details.find('./label').text.strip(),
|
||||||
|
'width': int(details.find('./width').text.strip()),
|
||||||
|
'height': int(details.find('./height').text.strip()),
|
||||||
|
} for details in item.findall('./source/file_details') if details.find('./file').text
|
||||||
|
]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'url': video_url,
|
'title': title,
|
||||||
'title': self._og_search_title(webpage),
|
'thumbnail': thumbnail,
|
||||||
'description': self._og_search_description(webpage),
|
'formats': formats,
|
||||||
'thumbnail': self._og_search_thumbnail(webpage),
|
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@ import re
|
|||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
compat_str,
|
compat_str,
|
||||||
|
compat_urllib_parse,
|
||||||
|
ExtractorError,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -16,16 +18,28 @@ class FiveMinIE(InfoExtractor):
|
|||||||
(?P<id>\d+)
|
(?P<id>\d+)
|
||||||
'''
|
'''
|
||||||
|
|
||||||
_TEST = {
|
_TESTS = [
|
||||||
# From http://www.engadget.com/2013/11/15/ipad-mini-retina-display-review/
|
{
|
||||||
'url': 'http://pshared.5min.com/Scripts/PlayerSeed.js?sid=281&width=560&height=345&playList=518013791',
|
# From http://www.engadget.com/2013/11/15/ipad-mini-retina-display-review/
|
||||||
'md5': '4f7b0b79bf1a470e5004f7112385941d',
|
'url': 'http://pshared.5min.com/Scripts/PlayerSeed.js?sid=281&width=560&height=345&playList=518013791',
|
||||||
'info_dict': {
|
'md5': '4f7b0b79bf1a470e5004f7112385941d',
|
||||||
'id': '518013791',
|
'info_dict': {
|
||||||
'ext': 'mp4',
|
'id': '518013791',
|
||||||
'title': 'iPad Mini with Retina Display Review',
|
'ext': 'mp4',
|
||||||
|
'title': 'iPad Mini with Retina Display Review',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
{
|
||||||
|
# From http://on.aol.com/video/how-to-make-a-next-level-fruit-salad-518086247
|
||||||
|
'url': '5min:518086247',
|
||||||
|
'md5': 'e539a9dd682c288ef5a498898009f69e',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '518086247',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'How to Make a Next-Level Fruit Salad',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _build_result(cls, video_id):
|
def _build_result(cls, video_id):
|
||||||
@ -34,10 +48,28 @@ class FiveMinIE(InfoExtractor):
|
|||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
video_id = mobj.group('id')
|
video_id = mobj.group('id')
|
||||||
info = self._download_json(
|
embed_url = 'https://embed.5min.com/playerseed/?playList=%s' % video_id
|
||||||
'https://syn.5min.com/handlers/SenseHandler.ashx?func=GetResults&'
|
embed_page = self._download_webpage(embed_url, video_id,
|
||||||
'playlist=%s&url=https' % video_id,
|
'Downloading embed page')
|
||||||
video_id)['binding'][0]
|
sid = self._search_regex(r'sid=(\d+)', embed_page, 'sid')
|
||||||
|
query = compat_urllib_parse.urlencode({
|
||||||
|
'func': 'GetResults',
|
||||||
|
'playlist': video_id,
|
||||||
|
'sid': sid,
|
||||||
|
'isPlayerSeed': 'true',
|
||||||
|
'url': embed_url,
|
||||||
|
})
|
||||||
|
response = self._download_json(
|
||||||
|
'https://syn.5min.com/handlers/SenseHandler.ashx?' + query,
|
||||||
|
video_id)
|
||||||
|
if not response['success']:
|
||||||
|
err_msg = response['errorMessage']
|
||||||
|
if err_msg == 'ErrorVideoUserNotGeo':
|
||||||
|
msg = 'Video not available from your location'
|
||||||
|
else:
|
||||||
|
msg = 'Aol said: %s' % err_msg
|
||||||
|
raise ExtractorError(msg, expected=True, video_id=video_id)
|
||||||
|
info = response['binding'][0]
|
||||||
|
|
||||||
second_id = compat_str(int(video_id[:-2]) + 1)
|
second_id = compat_str(int(video_id[:-2]) + 1)
|
||||||
formats = []
|
formats = []
|
||||||
|
77
youtube_dl/extractor/franceculture.py
Normal file
77
youtube_dl/extractor/franceculture.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
compat_parse_qs,
|
||||||
|
compat_urlparse,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class FranceCultureIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'(?P<baseurl>http://(?:www\.)?franceculture\.fr/)player/reecouter\?play=(?P<id>[0-9]+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.franceculture.fr/player/reecouter?play=4795174',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '4795174',
|
||||||
|
'ext': 'mp3',
|
||||||
|
'title': 'Rendez-vous au pays des geeks',
|
||||||
|
'vcodec': 'none',
|
||||||
|
'uploader': 'Colette Fellous',
|
||||||
|
'upload_date': '20140301',
|
||||||
|
'duration': 3601,
|
||||||
|
'thumbnail': r're:^http://www\.franceculture\.fr/.*/images/player/Carnet-nomade\.jpg$',
|
||||||
|
'description': 'Avec :Jean-Baptiste Péretié pour son documentaire sur Arte "La revanche des « geeks », une enquête menée aux Etats-Unis dans la S ...',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
baseurl = mobj.group('baseurl')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
params_code = self._search_regex(
|
||||||
|
r"<param name='movie' value='/sites/all/modules/rf/rf_player/swf/loader.swf\?([^']+)' />",
|
||||||
|
webpage, 'parameter code')
|
||||||
|
params = compat_parse_qs(params_code)
|
||||||
|
video_url = compat_urlparse.urljoin(baseurl, params['urlAOD'][0])
|
||||||
|
|
||||||
|
title = self._html_search_regex(
|
||||||
|
r'<h1 class="title[^"]+">(.+?)</h1>', webpage, 'title')
|
||||||
|
uploader = self._html_search_regex(
|
||||||
|
r'(?s)<div id="emission".*?<span class="author">(.*?)</span>',
|
||||||
|
webpage, 'uploader', fatal=False)
|
||||||
|
thumbnail_part = self._html_search_regex(
|
||||||
|
r'(?s)<div id="emission".*?<img src="([^"]+)"', webpage,
|
||||||
|
'thumbnail', fatal=False)
|
||||||
|
if thumbnail_part is None:
|
||||||
|
thumbnail = None
|
||||||
|
else:
|
||||||
|
thumbnail = compat_urlparse.urljoin(baseurl, thumbnail_part)
|
||||||
|
description = self._html_search_regex(
|
||||||
|
r'(?s)<p class="desc">(.*?)</p>', webpage, 'description')
|
||||||
|
|
||||||
|
info = json.loads(params['infoData'][0])[0]
|
||||||
|
duration = info.get('media_length')
|
||||||
|
upload_date_candidate = info.get('media_section5')
|
||||||
|
upload_date = (
|
||||||
|
upload_date_candidate
|
||||||
|
if (upload_date_candidate is not None and
|
||||||
|
re.match(r'[0-9]{8}$', upload_date_candidate))
|
||||||
|
else None)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'url': video_url,
|
||||||
|
'vcodec': 'none' if video_url.lower().endswith('.mp3') else None,
|
||||||
|
'duration': duration,
|
||||||
|
'uploader': uploader,
|
||||||
|
'upload_date': upload_date,
|
||||||
|
'title': title,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'description': description,
|
||||||
|
}
|
@ -48,24 +48,36 @@ class PluzzIE(FranceTVBaseInfoExtractor):
|
|||||||
|
|
||||||
class FranceTvInfoIE(FranceTVBaseInfoExtractor):
|
class FranceTvInfoIE(FranceTVBaseInfoExtractor):
|
||||||
IE_NAME = 'francetvinfo.fr'
|
IE_NAME = 'francetvinfo.fr'
|
||||||
_VALID_URL = r'https?://www\.francetvinfo\.fr/replay.*/(?P<title>.+)\.html'
|
_VALID_URL = r'https?://www\.francetvinfo\.fr/.*/(?P<title>.+)\.html'
|
||||||
|
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
'url': 'http://www.francetvinfo.fr/replay-jt/france-3/soir-3/jt-grand-soir-3-lundi-26-aout-2013_393427.html',
|
'url': 'http://www.francetvinfo.fr/replay-jt/france-3/soir-3/jt-grand-soir-3-lundi-26-aout-2013_393427.html',
|
||||||
'file': '84981923.mp4',
|
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'id': '84981923',
|
||||||
|
'ext': 'mp4',
|
||||||
'title': 'Soir 3',
|
'title': 'Soir 3',
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
},
|
},
|
||||||
}
|
}, {
|
||||||
|
'url': 'http://www.francetvinfo.fr/elections/europeennes/direct-europeennes-regardez-le-debat-entre-les-candidats-a-la-presidence-de-la-commission_600639.html',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'EV_20019',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Débat des candidats à la Commission européenne',
|
||||||
|
'description': 'Débat des candidats à la Commission européenne',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': 'HLS (reqires ffmpeg)'
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
page_title = mobj.group('title')
|
page_title = mobj.group('title')
|
||||||
webpage = self._download_webpage(url, page_title)
|
webpage = self._download_webpage(url, page_title)
|
||||||
video_id = self._search_regex(r'id-video=(\d+?)[@"]', webpage, 'video id')
|
video_id = self._search_regex(r'id-video=((?:[^0-9]*?_)?[0-9]+)[@"]', webpage, 'video id')
|
||||||
return self._extract_video(video_id)
|
return self._extract_video(video_id)
|
||||||
|
|
||||||
|
|
||||||
|
@ -4,22 +4,32 @@ import json
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from ..utils import ExtractorError
|
||||||
|
|
||||||
|
|
||||||
class FunnyOrDieIE(InfoExtractor):
|
class FunnyOrDieIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?funnyordie\.com/(?P<type>embed|videos)/(?P<id>[0-9a-f]+)(?:$|[?#/])'
|
_VALID_URL = r'https?://(?:www\.)?funnyordie\.com/(?P<type>embed|videos)/(?P<id>[0-9a-f]+)(?:$|[?#/])'
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
'url': 'http://www.funnyordie.com/videos/0732f586d7/heart-shaped-box-literal-video-version',
|
'url': 'http://www.funnyordie.com/videos/0732f586d7/heart-shaped-box-literal-video-version',
|
||||||
'file': '0732f586d7.mp4',
|
'md5': 'bcd81e0c4f26189ee09be362ad6e6ba9',
|
||||||
'md5': 'f647e9e90064b53b6e046e75d0241fbd',
|
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'description': ('Lyrics changed to match the video. Spoken cameo '
|
'id': '0732f586d7',
|
||||||
'by Obscurus Lupa (from ThatGuyWithTheGlasses.com). Based on a '
|
'ext': 'mp4',
|
||||||
'concept by Dustin McLean (DustFilms.com). Performed, edited, '
|
|
||||||
'and written by David A. Scott.'),
|
|
||||||
'title': 'Heart-Shaped Box: Literal Video Version',
|
'title': 'Heart-Shaped Box: Literal Video Version',
|
||||||
|
'description': 'md5:ea09a01bc9a1c46d9ab696c01747c338',
|
||||||
|
'thumbnail': 're:^http:.*\.jpg$',
|
||||||
},
|
},
|
||||||
}
|
}, {
|
||||||
|
'url': 'http://www.funnyordie.com/embed/e402820827',
|
||||||
|
'md5': 'ff4d83318f89776ed0250634cfaa8d36',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'e402820827',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Please Use This Song (Jon Lajoie)',
|
||||||
|
'description': 'md5:2ed27d364f5a805a6dba199faaf6681d',
|
||||||
|
'thumbnail': 're:^http:.*\.jpg$',
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
@ -27,27 +37,34 @@ class FunnyOrDieIE(InfoExtractor):
|
|||||||
video_id = mobj.group('id')
|
video_id = mobj.group('id')
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
video_url = self._search_regex(
|
links = re.findall(r'<source src="([^"]+/v)\d+\.([^"]+)" type=\'video', webpage)
|
||||||
[r'type="video/mp4" src="(.*?)"', r'src="([^>]*?)" type=\'video/mp4\''],
|
if not links:
|
||||||
webpage, 'video URL', flags=re.DOTALL)
|
raise ExtractorError('No media links available for %s' % video_id)
|
||||||
|
|
||||||
if mobj.group('type') == 'embed':
|
links.sort(key=lambda link: 1 if link[1] == 'mp4' else 0)
|
||||||
post_json = self._search_regex(
|
|
||||||
r'fb_post\s*=\s*(\{.*?\});', webpage, 'post details')
|
bitrates = self._html_search_regex(r'<source src="[^"]+/v,((?:\d+,)+)\.mp4\.csmil', webpage, 'video bitrates')
|
||||||
post = json.loads(post_json)
|
bitrates = [int(b) for b in bitrates.rstrip(',').split(',')]
|
||||||
title = post['name']
|
bitrates.sort()
|
||||||
description = post.get('description')
|
|
||||||
thumbnail = post.get('picture')
|
formats = []
|
||||||
else:
|
|
||||||
title = self._og_search_title(webpage)
|
for bitrate in bitrates:
|
||||||
description = self._og_search_description(webpage)
|
for link in links:
|
||||||
thumbnail = None
|
formats.append({
|
||||||
|
'url': '%s%d.%s' % (link[0], bitrate, link[1]),
|
||||||
|
'format_id': '%s-%d' % (link[1], bitrate),
|
||||||
|
'vbr': bitrate,
|
||||||
|
})
|
||||||
|
|
||||||
|
post_json = self._search_regex(
|
||||||
|
r'fb_post\s*=\s*(\{.*?\});', webpage, 'post details')
|
||||||
|
post = json.loads(post_json)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'url': video_url,
|
'title': post['name'],
|
||||||
'ext': 'mp4',
|
'description': post.get('description'),
|
||||||
'title': title,
|
'thumbnail': post.get('picture'),
|
||||||
'description': description,
|
'formats': formats,
|
||||||
'thumbnail': thumbnail,
|
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ class GamekingsIE(InfoExtractor):
|
|||||||
'id': '20130811',
|
'id': '20130811',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Phoenix Wright: Ace Attorney \u2013 Dual Destinies Review',
|
'title': 'Phoenix Wright: Ace Attorney \u2013 Dual Destinies Review',
|
||||||
'description': 'md5:632e61a9f97d700e83f43d77ddafb6a4',
|
'description': 'md5:36fd701e57e8c15ac8682a2374c99731',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,11 +15,12 @@ from ..utils import (
|
|||||||
class GameSpotIE(InfoExtractor):
|
class GameSpotIE(InfoExtractor):
|
||||||
_VALID_URL = r'(?:http://)?(?:www\.)?gamespot\.com/.*-(?P<page_id>\d+)/?'
|
_VALID_URL = r'(?:http://)?(?:www\.)?gamespot\.com/.*-(?P<page_id>\d+)/?'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
"url": "http://www.gamespot.com/arma-iii/videos/arma-iii-community-guide-sitrep-i-6410818/",
|
'url': 'http://www.gamespot.com/videos/arma-3-community-guide-sitrep-i/2300-6410818/',
|
||||||
"file": "gs-2300-6410818.mp4",
|
'md5': 'b2a30deaa8654fcccd43713a6b6a4825',
|
||||||
"md5": "b2a30deaa8654fcccd43713a6b6a4825",
|
'info_dict': {
|
||||||
"info_dict": {
|
'id': 'gs-2300-6410818',
|
||||||
"title": "Arma 3 - Community Guide: SITREP I",
|
'ext': 'mp4',
|
||||||
|
'title': 'Arma 3 - Community Guide: SITREP I',
|
||||||
'description': 'Check out this video where some of the basics of Arma 3 is explained.',
|
'description': 'Check out this video where some of the basics of Arma 3 is explained.',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ from ..utils import (
|
|||||||
from .brightcove import BrightcoveIE
|
from .brightcove import BrightcoveIE
|
||||||
from .ooyala import OoyalaIE
|
from .ooyala import OoyalaIE
|
||||||
from .rutv import RUTVIE
|
from .rutv import RUTVIE
|
||||||
|
from .smotri import SmotriIE
|
||||||
|
|
||||||
|
|
||||||
class GenericIE(InfoExtractor):
|
class GenericIE(InfoExtractor):
|
||||||
@ -34,9 +35,10 @@ class GenericIE(InfoExtractor):
|
|||||||
_TESTS = [
|
_TESTS = [
|
||||||
{
|
{
|
||||||
'url': 'http://www.hodiho.fr/2013/02/regis-plante-sa-jeep.html',
|
'url': 'http://www.hodiho.fr/2013/02/regis-plante-sa-jeep.html',
|
||||||
'file': '13601338388002.mp4',
|
'md5': '85b90ccc9d73b4acd9138d3af4c27f89',
|
||||||
'md5': '6e15c93721d7ec9e9ca3fdbf07982cfd',
|
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'id': '13601338388002',
|
||||||
|
'ext': 'mp4',
|
||||||
'uploader': 'www.hodiho.fr',
|
'uploader': 'www.hodiho.fr',
|
||||||
'title': 'R\u00e9gis plante sa Jeep',
|
'title': 'R\u00e9gis plante sa Jeep',
|
||||||
}
|
}
|
||||||
@ -45,8 +47,9 @@ class GenericIE(InfoExtractor):
|
|||||||
{
|
{
|
||||||
'add_ie': ['Bandcamp'],
|
'add_ie': ['Bandcamp'],
|
||||||
'url': 'http://bronyrock.com/track/the-pony-mash',
|
'url': 'http://bronyrock.com/track/the-pony-mash',
|
||||||
'file': '3235767654.mp3',
|
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'id': '3235767654',
|
||||||
|
'ext': 'mp3',
|
||||||
'title': 'The Pony Mash',
|
'title': 'The Pony Mash',
|
||||||
'uploader': 'M_Pallante',
|
'uploader': 'M_Pallante',
|
||||||
},
|
},
|
||||||
@ -72,15 +75,27 @@ class GenericIE(InfoExtractor):
|
|||||||
{
|
{
|
||||||
# https://github.com/rg3/youtube-dl/issues/2253
|
# https://github.com/rg3/youtube-dl/issues/2253
|
||||||
'url': 'http://bcove.me/i6nfkrc3',
|
'url': 'http://bcove.me/i6nfkrc3',
|
||||||
'file': '3101154703001.mp4',
|
|
||||||
'md5': '0ba9446db037002366bab3b3eb30c88c',
|
'md5': '0ba9446db037002366bab3b3eb30c88c',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'id': '3101154703001',
|
||||||
|
'ext': 'mp4',
|
||||||
'title': 'Still no power',
|
'title': 'Still no power',
|
||||||
'uploader': 'thestar.com',
|
'uploader': 'thestar.com',
|
||||||
'description': 'Mississauga resident David Farmer is still out of power as a result of the ice storm a month ago. To keep the house warm, Farmer cuts wood from his property for a wood burning stove downstairs.',
|
'description': 'Mississauga resident David Farmer is still out of power as a result of the ice storm a month ago. To keep the house warm, Farmer cuts wood from his property for a wood burning stove downstairs.',
|
||||||
},
|
},
|
||||||
'add_ie': ['Brightcove'],
|
'add_ie': ['Brightcove'],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://www.championat.com/video/football/v/87/87499.html',
|
||||||
|
'md5': 'fb973ecf6e4a78a67453647444222983',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '3414141473001',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Видео. Удаление Дзагоева (ЦСКА)',
|
||||||
|
'description': 'Онлайн-трансляция матча ЦСКА - "Волга"',
|
||||||
|
'uploader': 'Championat',
|
||||||
|
},
|
||||||
|
},
|
||||||
# Direct link to a video
|
# Direct link to a video
|
||||||
{
|
{
|
||||||
'url': 'http://media.w3.org/2010/05/sintel/trailer.mp4',
|
'url': 'http://media.w3.org/2010/05/sintel/trailer.mp4',
|
||||||
@ -102,20 +117,6 @@ class GenericIE(InfoExtractor):
|
|||||||
'title': '2cc213299525360.mov', # that's what we get
|
'title': '2cc213299525360.mov', # that's what we get
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
# second style of embedded ooyala videos
|
|
||||||
{
|
|
||||||
'url': 'http://www.smh.com.au/tv/business/show/financial-review-sunday/behind-the-scenes-financial-review-sunday--4350201.html',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '13djJjYjptA1XpPx8r9kuzPyj3UZH0Uk',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': 'Behind-the-scenes: Financial Review Sunday ',
|
|
||||||
'description': 'Step inside Channel Nine studios for an exclusive tour of its upcoming financial business show.',
|
|
||||||
},
|
|
||||||
'params': {
|
|
||||||
# m3u8 download
|
|
||||||
'skip_download': True,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
# google redirect
|
# google redirect
|
||||||
{
|
{
|
||||||
'url': 'http://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&ved=0CCUQtwIwAA&url=http%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DcmQHVoWB5FY&ei=F-sNU-LLCaXk4QT52ICQBQ&usg=AFQjCNEw4hL29zgOohLXvpJ-Bdh2bils1Q&bvm=bv.61965928,d.bGE',
|
'url': 'http://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&ved=0CCUQtwIwAA&url=http%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DcmQHVoWB5FY&ei=F-sNU-LLCaXk4QT52ICQBQ&usg=AFQjCNEw4hL29zgOohLXvpJ-Bdh2bils1Q&bvm=bv.61965928,d.bGE',
|
||||||
@ -186,6 +187,17 @@ class GenericIE(InfoExtractor):
|
|||||||
'description': 'md5:ddb2a40ecd6b6a147e400e535874947b',
|
'description': 'md5:ddb2a40ecd6b6a147e400e535874947b',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
# Embeded Ustream video
|
||||||
|
{
|
||||||
|
'url': 'http://www.american.edu/spa/pti/nsa-privacy-janus-2014.cfm',
|
||||||
|
'md5': '27b99cdb639c9b12a79bca876a073417',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '45734260',
|
||||||
|
'ext': 'flv',
|
||||||
|
'uploader': 'AU SPA: The NSA and Privacy',
|
||||||
|
'title': 'NSA and Privacy Forum Debate featuring General Hayden and Barton Gellman'
|
||||||
|
}
|
||||||
|
},
|
||||||
# nowvideo embed hidden behind percent encoding
|
# nowvideo embed hidden behind percent encoding
|
||||||
{
|
{
|
||||||
'url': 'http://www.waoanime.tv/the-super-dimension-fortress-macross-episode-1/',
|
'url': 'http://www.waoanime.tv/the-super-dimension-fortress-macross-episode-1/',
|
||||||
@ -212,6 +224,43 @@ class GenericIE(InfoExtractor):
|
|||||||
'skip_download': 'Requires rtmpdump'
|
'skip_download': 'Requires rtmpdump'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
# smotri embed
|
||||||
|
{
|
||||||
|
'url': 'http://rbctv.rbc.ru/archive/news/562949990879132.shtml',
|
||||||
|
'md5': 'ec40048448e9284c9a1de77bb188108b',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'v27008541fad',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Крым и Севастополь вошли в состав России',
|
||||||
|
'description': 'md5:fae01b61f68984c7bd2fa741e11c3175',
|
||||||
|
'duration': 900,
|
||||||
|
'upload_date': '20140318',
|
||||||
|
'uploader': 'rbctv_2012_4',
|
||||||
|
'uploader_id': 'rbctv_2012_4',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
# Condé Nast embed
|
||||||
|
{
|
||||||
|
'url': 'http://www.wired.com/2014/04/honda-asimo/',
|
||||||
|
'md5': 'ba0dfe966fa007657bd1443ee672db0f',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '53501be369702d3275860000',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Honda’s New Asimo Robot Is More Human Than Ever',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
# Dailymotion embed
|
||||||
|
{
|
||||||
|
'url': 'http://www.spi0n.com/zap-spi0n-com-n216/',
|
||||||
|
'md5': '441aeeb82eb72c422c7f14ec533999cd',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'k2mm4bCdJ6CQ2i7c8o2',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Le Zap de Spi0n n°216 - Zapping du Web',
|
||||||
|
'uploader': 'Spi0n',
|
||||||
|
},
|
||||||
|
'add_ie': ['Dailymotion'],
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
def report_download_webpage(self, video_id):
|
def report_download_webpage(self, video_id):
|
||||||
@ -296,17 +345,31 @@ class GenericIE(InfoExtractor):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
|
if url.startswith('//'):
|
||||||
|
return {
|
||||||
|
'_type': 'url',
|
||||||
|
'url': self.http_scheme() + url,
|
||||||
|
}
|
||||||
|
|
||||||
parsed_url = compat_urlparse.urlparse(url)
|
parsed_url = compat_urlparse.urlparse(url)
|
||||||
if not parsed_url.scheme:
|
if not parsed_url.scheme:
|
||||||
default_search = self._downloader.params.get('default_search')
|
default_search = self._downloader.params.get('default_search')
|
||||||
if default_search is None:
|
if default_search is None:
|
||||||
default_search = 'auto'
|
default_search = 'auto_warning'
|
||||||
|
|
||||||
if default_search == 'auto':
|
if default_search in ('auto', 'auto_warning'):
|
||||||
if '/' in url:
|
if '/' in url:
|
||||||
self._downloader.report_warning('The url doesn\'t specify the protocol, trying with http')
|
self._downloader.report_warning('The url doesn\'t specify the protocol, trying with http')
|
||||||
return self.url_result('http://' + url)
|
return self.url_result('http://' + url)
|
||||||
else:
|
else:
|
||||||
|
if default_search == 'auto_warning':
|
||||||
|
if re.match(r'^(?:url|URL)$', url):
|
||||||
|
raise ExtractorError(
|
||||||
|
'Invalid URL: %r . Call youtube-dl like this: youtube-dl -v "https://www.youtube.com/watch?v=BaW_jenozKc" ' % url,
|
||||||
|
expected=True)
|
||||||
|
else:
|
||||||
|
self._downloader.report_warning(
|
||||||
|
'Falling back to youtube search for %s . Set --default-search to "auto" to suppress this warning.' % url)
|
||||||
return self.url_result('ytsearch:' + url)
|
return self.url_result('ytsearch:' + url)
|
||||||
else:
|
else:
|
||||||
assert ':' in default_search
|
assert ':' in default_search
|
||||||
@ -429,7 +492,7 @@ class GenericIE(InfoExtractor):
|
|||||||
matches = re.findall(
|
matches = re.findall(
|
||||||
r'<iframe[^>]+?src=(["\'])(?P<url>(?:https?:)?//(?:www\.)?dailymotion\.com/embed/video/.+?)\1', webpage)
|
r'<iframe[^>]+?src=(["\'])(?P<url>(?:https?:)?//(?:www\.)?dailymotion\.com/embed/video/.+?)\1', webpage)
|
||||||
if matches:
|
if matches:
|
||||||
urlrs = [self.url_result(unescapeHTML(tuppl[1]), 'Dailymotion')
|
urlrs = [self.url_result(unescapeHTML(tuppl[1]))
|
||||||
for tuppl in matches]
|
for tuppl in matches]
|
||||||
return self.playlist_result(
|
return self.playlist_result(
|
||||||
urlrs, playlist_id=video_id, playlist_title=video_title)
|
urlrs, playlist_id=video_id, playlist_title=video_title)
|
||||||
@ -455,6 +518,22 @@ class GenericIE(InfoExtractor):
|
|||||||
if mobj:
|
if mobj:
|
||||||
return self.url_result(mobj.group(1), 'BlipTV')
|
return self.url_result(mobj.group(1), 'BlipTV')
|
||||||
|
|
||||||
|
# Look for embedded condenast player
|
||||||
|
matches = re.findall(
|
||||||
|
r'<iframe\s+(?:[a-zA-Z-]+="[^"]+"\s+)*?src="(https?://player\.cnevids\.com/embed/[^"]+")',
|
||||||
|
webpage)
|
||||||
|
if matches:
|
||||||
|
return {
|
||||||
|
'_type': 'playlist',
|
||||||
|
'entries': [{
|
||||||
|
'_type': 'url',
|
||||||
|
'ie_key': 'CondeNast',
|
||||||
|
'url': ma,
|
||||||
|
} for ma in matches],
|
||||||
|
'title': video_title,
|
||||||
|
'id': video_id,
|
||||||
|
}
|
||||||
|
|
||||||
# Look for Bandcamp pages with custom domain
|
# Look for Bandcamp pages with custom domain
|
||||||
mobj = re.search(r'<meta property="og:url"[^>]*?content="(.*?bandcamp\.com.*?)"', webpage)
|
mobj = re.search(r'<meta property="og:url"[^>]*?content="(.*?bandcamp\.com.*?)"', webpage)
|
||||||
if mobj is not None:
|
if mobj is not None:
|
||||||
@ -475,7 +554,7 @@ class GenericIE(InfoExtractor):
|
|||||||
return OoyalaIE._build_url_result(mobj.group('ec'))
|
return OoyalaIE._build_url_result(mobj.group('ec'))
|
||||||
|
|
||||||
# Look for Aparat videos
|
# Look for Aparat videos
|
||||||
mobj = re.search(r'<iframe src="(http://www\.aparat\.com/video/[^"]+)"', webpage)
|
mobj = re.search(r'<iframe .*?src="(http://www\.aparat\.com/video/[^"]+)"', webpage)
|
||||||
if mobj is not None:
|
if mobj is not None:
|
||||||
return self.url_result(mobj.group(1), 'Aparat')
|
return self.url_result(mobj.group(1), 'Aparat')
|
||||||
|
|
||||||
@ -484,17 +563,18 @@ class GenericIE(InfoExtractor):
|
|||||||
if mobj is not None:
|
if mobj is not None:
|
||||||
return self.url_result(mobj.group(1), 'Mpora')
|
return self.url_result(mobj.group(1), 'Mpora')
|
||||||
|
|
||||||
# Look for embedded NovaMov player
|
# Look for embedded NovaMov-based player
|
||||||
mobj = re.search(
|
mobj = re.search(
|
||||||
r'<iframe[^>]+?src=(["\'])(?P<url>http://(?:(?:embed|www)\.)?novamov\.com/embed\.php.+?)\1', webpage)
|
r'''(?x)<(?:pagespeed_)?iframe[^>]+?src=(["\'])
|
||||||
|
(?P<url>http://(?:(?:embed|www)\.)?
|
||||||
|
(?:novamov\.com|
|
||||||
|
nowvideo\.(?:ch|sx|eu|at|ag|co)|
|
||||||
|
videoweed\.(?:es|com)|
|
||||||
|
movshare\.(?:net|sx|ag)|
|
||||||
|
divxstage\.(?:eu|net|ch|co|at|ag))
|
||||||
|
/embed\.php.+?)\1''', webpage)
|
||||||
if mobj is not None:
|
if mobj is not None:
|
||||||
return self.url_result(mobj.group('url'), 'NovaMov')
|
return self.url_result(mobj.group('url'))
|
||||||
|
|
||||||
# Look for embedded NowVideo player
|
|
||||||
mobj = re.search(
|
|
||||||
r'<iframe[^>]+?src=(["\'])(?P<url>http://(?:(?:embed|www)\.)?nowvideo\.(?:ch|sx|eu)/embed\.php.+?)\1', webpage)
|
|
||||||
if mobj is not None:
|
|
||||||
return self.url_result(mobj.group('url'), 'NowVideo')
|
|
||||||
|
|
||||||
# Look for embedded Facebook player
|
# Look for embedded Facebook player
|
||||||
mobj = re.search(
|
mobj = re.search(
|
||||||
@ -540,6 +620,12 @@ class GenericIE(InfoExtractor):
|
|||||||
if mobj is not None:
|
if mobj is not None:
|
||||||
return self.url_result(mobj.group('url'), 'TED')
|
return self.url_result(mobj.group('url'), 'TED')
|
||||||
|
|
||||||
|
# Look for embedded Ustream videos
|
||||||
|
mobj = re.search(
|
||||||
|
r'<iframe[^>]+?src=(["\'])(?P<url>http://www\.ustream\.tv/embed/.+?)\1', webpage)
|
||||||
|
if mobj is not None:
|
||||||
|
return self.url_result(mobj.group('url'), 'Ustream')
|
||||||
|
|
||||||
# Look for embedded arte.tv player
|
# Look for embedded arte.tv player
|
||||||
mobj = re.search(
|
mobj = re.search(
|
||||||
r'<script [^>]*?src="(?P<url>http://www\.arte\.tv/playerv2/embed[^"]+)"',
|
r'<script [^>]*?src="(?P<url>http://www\.arte\.tv/playerv2/embed[^"]+)"',
|
||||||
@ -547,65 +633,91 @@ class GenericIE(InfoExtractor):
|
|||||||
if mobj is not None:
|
if mobj is not None:
|
||||||
return self.url_result(mobj.group('url'), 'ArteTVEmbed')
|
return self.url_result(mobj.group('url'), 'ArteTVEmbed')
|
||||||
|
|
||||||
# Start with something easy: JW Player in SWFObject
|
# Look for embedded smotri.com player
|
||||||
mobj = re.search(r'flashvars: [\'"](?:.*&)?file=(http[^\'"&]*)', webpage)
|
smotri_url = SmotriIE._extract_url(webpage)
|
||||||
if mobj is None:
|
if smotri_url:
|
||||||
# Look for gorilla-vid style embedding
|
return self.url_result(smotri_url, 'Smotri')
|
||||||
mobj = re.search(r'(?s)(?:jw_plugins|JWPlayerOptions).*?file\s*:\s*["\'](.*?)["\']', webpage)
|
|
||||||
if mobj is None:
|
|
||||||
# Broaden the search a little bit
|
|
||||||
mobj = re.search(r'[^A-Za-z0-9]?(?:file|source)=(http[^\'"&]*)', webpage)
|
|
||||||
if mobj is None:
|
|
||||||
# Broaden the search a little bit: JWPlayer JS loader
|
|
||||||
mobj = re.search(r'[^A-Za-z0-9]?file["\']?:\s*["\'](http(?![^\'"]+\.[0-9]+[\'"])[^\'"]+)["\']', webpage)
|
|
||||||
|
|
||||||
if mobj is None:
|
# Look for embeded soundcloud player
|
||||||
|
mobj = re.search(
|
||||||
|
r'<iframe src="(?P<url>https?://(?:w\.)?soundcloud\.com/player[^"]+)"',
|
||||||
|
webpage)
|
||||||
|
if mobj is not None:
|
||||||
|
url = unescapeHTML(mobj.group('url'))
|
||||||
|
return self.url_result(url)
|
||||||
|
|
||||||
|
# Start with something easy: JW Player in SWFObject
|
||||||
|
found = re.findall(r'flashvars: [\'"](?:.*&)?file=(http[^\'"&]*)', webpage)
|
||||||
|
if not found:
|
||||||
|
# Look for gorilla-vid style embedding
|
||||||
|
found = re.findall(r'''(?sx)
|
||||||
|
(?:
|
||||||
|
jw_plugins|
|
||||||
|
JWPlayerOptions|
|
||||||
|
jwplayer\s*\(\s*["'][^'"]+["']\s*\)\s*\.setup
|
||||||
|
)
|
||||||
|
.*?file\s*:\s*["\'](.*?)["\']''', webpage)
|
||||||
|
if not found:
|
||||||
|
# Broaden the search a little bit
|
||||||
|
found = re.findall(r'[^A-Za-z0-9]?(?:file|source)=(http[^\'"&]*)', webpage)
|
||||||
|
if not found:
|
||||||
|
# Broaden the findall a little bit: JWPlayer JS loader
|
||||||
|
found = re.findall(r'[^A-Za-z0-9]?file["\']?:\s*["\'](http(?![^\'"]+\.[0-9]+[\'"])[^\'"]+)["\']', webpage)
|
||||||
|
if not found:
|
||||||
# Try to find twitter cards info
|
# Try to find twitter cards info
|
||||||
mobj = re.search(r'<meta (?:property|name)="twitter:player:stream" (?:content|value)="(.+?)"', webpage)
|
found = re.findall(r'<meta (?:property|name)="twitter:player:stream" (?:content|value)="(.+?)"', webpage)
|
||||||
if mobj is None:
|
if not found:
|
||||||
# We look for Open Graph info:
|
# We look for Open Graph info:
|
||||||
# We have to match any number spaces between elements, some sites try to align them (eg.: statigr.am)
|
# We have to match any number spaces between elements, some sites try to align them (eg.: statigr.am)
|
||||||
m_video_type = re.search(r'<meta.*?property="og:video:type".*?content="video/(.*?)"', webpage)
|
m_video_type = re.findall(r'<meta.*?property="og:video:type".*?content="video/(.*?)"', webpage)
|
||||||
# We only look in og:video if the MIME type is a video, don't try if it's a Flash player:
|
# We only look in og:video if the MIME type is a video, don't try if it's a Flash player:
|
||||||
if m_video_type is not None:
|
if m_video_type is not None:
|
||||||
mobj = re.search(r'<meta.*?property="og:video".*?content="(.*?)"', webpage)
|
found = re.findall(r'<meta.*?property="og:video".*?content="(.*?)"', webpage)
|
||||||
if mobj is None:
|
if not found:
|
||||||
# HTML5 video
|
# HTML5 video
|
||||||
mobj = re.search(r'<video[^<]*(?:>.*?<source.*?)? src="([^"]+)"', webpage, flags=re.DOTALL)
|
found = re.findall(r'(?s)<video[^<]*(?:>.*?<source.*?)? src="([^"]+)"', webpage)
|
||||||
if mobj is None:
|
if not found:
|
||||||
mobj = re.search(
|
found = re.search(
|
||||||
r'(?i)<meta\s+(?=(?:[a-z-]+="[^"]+"\s+)*http-equiv="refresh")'
|
r'(?i)<meta\s+(?=(?:[a-z-]+="[^"]+"\s+)*http-equiv="refresh")'
|
||||||
r'(?:[a-z-]+="[^"]+"\s+)*?content="[0-9]{,2};url=\'([^\']+)\'"',
|
r'(?:[a-z-]+="[^"]+"\s+)*?content="[0-9]{,2};url=\'([^\']+)\'"',
|
||||||
webpage)
|
webpage)
|
||||||
if mobj:
|
if found:
|
||||||
new_url = mobj.group(1)
|
new_url = found.group(1)
|
||||||
self.report_following_redirect(new_url)
|
self.report_following_redirect(new_url)
|
||||||
return {
|
return {
|
||||||
'_type': 'url',
|
'_type': 'url',
|
||||||
'url': new_url,
|
'url': new_url,
|
||||||
}
|
}
|
||||||
if mobj is None:
|
if not found:
|
||||||
raise ExtractorError('Unsupported URL: %s' % url)
|
raise ExtractorError('Unsupported URL: %s' % url)
|
||||||
|
|
||||||
# It's possible that one of the regexes
|
entries = []
|
||||||
# matched, but returned an empty group:
|
for video_url in found:
|
||||||
if mobj.group(1) is None:
|
video_url = compat_urlparse.urljoin(url, video_url)
|
||||||
raise ExtractorError('Did not find a valid video URL at %s' % url)
|
video_id = compat_urllib_parse.unquote(os.path.basename(video_url))
|
||||||
|
|
||||||
video_url = mobj.group(1)
|
# Sometimes, jwplayer extraction will result in a YouTube URL
|
||||||
video_url = compat_urlparse.urljoin(url, video_url)
|
if YoutubeIE.suitable(video_url):
|
||||||
video_id = compat_urllib_parse.unquote(os.path.basename(video_url))
|
entries.append(self.url_result(video_url, 'Youtube'))
|
||||||
|
continue
|
||||||
|
|
||||||
# Sometimes, jwplayer extraction will result in a YouTube URL
|
# here's a fun little line of code for you:
|
||||||
if YoutubeIE.suitable(video_url):
|
video_id = os.path.splitext(video_id)[0]
|
||||||
return self.url_result(video_url, 'Youtube')
|
|
||||||
|
|
||||||
# here's a fun little line of code for you:
|
entries.append({
|
||||||
video_id = os.path.splitext(video_id)[0]
|
'id': video_id,
|
||||||
|
'url': video_url,
|
||||||
|
'uploader': video_uploader,
|
||||||
|
'title': video_title,
|
||||||
|
})
|
||||||
|
|
||||||
|
if len(entries) == 1:
|
||||||
|
return entries[0]
|
||||||
|
else:
|
||||||
|
for num, e in enumerate(entries, start=1):
|
||||||
|
e['title'] = '%s (%d)' % (e['title'], num)
|
||||||
|
return {
|
||||||
|
'_type': 'playlist',
|
||||||
|
'entries': entries,
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
'id': video_id,
|
|
||||||
'url': video_url,
|
|
||||||
'uploader': video_uploader,
|
|
||||||
'title': video_title,
|
|
||||||
}
|
|
||||||
|
42
youtube_dl/extractor/hentaistigma.py
Normal file
42
youtube_dl/extractor/hentaistigma.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
|
class HentaiStigmaIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'^https?://hentai\.animestigma\.com/(?P<id>[^/]+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://hentai.animestigma.com/inyouchuu-etsu-bonus/',
|
||||||
|
'md5': '4e3d07422a68a4cc363d8f57c8bf0d23',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'inyouchuu-etsu-bonus',
|
||||||
|
'ext': 'mp4',
|
||||||
|
"title": "Inyouchuu Etsu Bonus",
|
||||||
|
"age_limit": 18,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
title = self._html_search_regex(
|
||||||
|
r'<h2 class="posttitle"><a[^>]*>([^<]+)</a>',
|
||||||
|
webpage, 'title')
|
||||||
|
wrap_url = self._html_search_regex(
|
||||||
|
r'<iframe src="([^"]+mp4)"', webpage, 'wrapper url')
|
||||||
|
wrap_webpage = self._download_webpage(wrap_url, video_id)
|
||||||
|
|
||||||
|
video_url = self._html_search_regex(
|
||||||
|
r'clip:\s*{\s*url: "([^"]*)"', wrap_webpage, 'video url')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'url': video_url,
|
||||||
|
'title': title,
|
||||||
|
'age_limit': 18,
|
||||||
|
}
|
@ -21,9 +21,10 @@ class HuffPostIE(InfoExtractor):
|
|||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://live.huffingtonpost.com/r/segment/legalese-it/52dd3e4b02a7602131000677',
|
'url': 'http://live.huffingtonpost.com/r/segment/legalese-it/52dd3e4b02a7602131000677',
|
||||||
'file': '52dd3e4b02a7602131000677.mp4',
|
|
||||||
'md5': '55f5e8981c1c80a64706a44b74833de8',
|
'md5': '55f5e8981c1c80a64706a44b74833de8',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'id': '52dd3e4b02a7602131000677',
|
||||||
|
'ext': 'mp4',
|
||||||
'title': 'Legalese It! with @MikeSacksHP',
|
'title': 'Legalese It! with @MikeSacksHP',
|
||||||
'description': 'This week on Legalese It, Mike talks to David Bosco about his new book on the ICC, "Rough Justice," he also discusses the Virginia AG\'s historic stance on gay marriage, the execution of Edgar Tamayo, the ICC\'s delay of Kenya\'s President and more. ',
|
'description': 'This week on Legalese It, Mike talks to David Bosco about his new book on the ICC, "Rough Justice," he also discusses the Virginia AG\'s historic stance on gay marriage, the execution of Edgar Tamayo, the ICC\'s delay of Kenya\'s President and more. ',
|
||||||
'duration': 1549,
|
'duration': 1549,
|
||||||
|
@ -5,8 +5,8 @@ import re
|
|||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
class StatigramIE(InfoExtractor):
|
class IconosquareIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(www\.)?statigr\.am/p/(?P<id>[^/]+)'
|
_VALID_URL = r'https?://(www\.)?(?:iconosquare\.com|statigr\.am)/p/(?P<id>[^/]+)'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://statigr.am/p/522207370455279102_24101272',
|
'url': 'http://statigr.am/p/522207370455279102_24101272',
|
||||||
'md5': '6eb93b882a3ded7c378ee1d6884b1814',
|
'md5': '6eb93b882a3ded7c378ee1d6884b1814',
|
||||||
@ -15,6 +15,7 @@ class StatigramIE(InfoExtractor):
|
|||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'uploader_id': 'aguynamedpatrick',
|
'uploader_id': 'aguynamedpatrick',
|
||||||
'title': 'Instagram photo by @aguynamedpatrick (Patrick Janelle)',
|
'title': 'Instagram photo by @aguynamedpatrick (Patrick Janelle)',
|
||||||
|
'description': 'md5:644406a9ec27457ed7aa7a9ebcd4ce3d',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,7 +26,7 @@ class StatigramIE(InfoExtractor):
|
|||||||
html_title = self._html_search_regex(
|
html_title = self._html_search_regex(
|
||||||
r'<title>(.+?)</title>',
|
r'<title>(.+?)</title>',
|
||||||
webpage, 'title')
|
webpage, 'title')
|
||||||
title = re.sub(r'(?: *\(Videos?\))? \| Statigram$', '', html_title)
|
title = re.sub(r'(?: *\(Videos?\))? \| (?:Iconosquare|Statigram)$', '', html_title)
|
||||||
uploader_id = self._html_search_regex(
|
uploader_id = self._html_search_regex(
|
||||||
r'@([^ ]+)', title, 'uploader name', fatal=False)
|
r'@([^ ]+)', title, 'uploader name', fatal=False)
|
||||||
|
|
||||||
@ -33,6 +34,7 @@ class StatigramIE(InfoExtractor):
|
|||||||
'id': video_id,
|
'id': video_id,
|
||||||
'url': self._og_search_video_url(webpage),
|
'url': self._og_search_video_url(webpage),
|
||||||
'title': title,
|
'title': title,
|
||||||
|
'description': self._og_search_description(webpage),
|
||||||
'thumbnail': self._og_search_thumbnail(webpage),
|
'thumbnail': self._og_search_thumbnail(webpage),
|
||||||
'uploader_id': uploader_id
|
'uploader_id': uploader_id
|
||||||
}
|
}
|
@ -1,10 +1,8 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import json
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
|
||||||
determine_ext,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class IGNIE(InfoExtractor):
|
class IGNIE(InfoExtractor):
|
||||||
@ -14,52 +12,57 @@ class IGNIE(InfoExtractor):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
_VALID_URL = r'https?://.+?\.ign\.com/(?P<type>videos|show_videos|articles|(?:[^/]*/feature))(/.+)?/(?P<name_or_id>.+)'
|
_VALID_URL = r'https?://.+?\.ign\.com/(?P<type>videos|show_videos|articles|(?:[^/]*/feature))(/.+)?/(?P<name_or_id>.+)'
|
||||||
IE_NAME = u'ign.com'
|
IE_NAME = 'ign.com'
|
||||||
|
|
||||||
_CONFIG_URL_TEMPLATE = 'http://www.ign.com/videos/configs/id/%s.config'
|
_CONFIG_URL_TEMPLATE = 'http://www.ign.com/videos/configs/id/%s.config'
|
||||||
_DESCRIPTION_RE = [r'<span class="page-object-description">(.+?)</span>',
|
_DESCRIPTION_RE = [
|
||||||
r'id="my_show_video">.*?<p>(.*?)</p>',
|
r'<span class="page-object-description">(.+?)</span>',
|
||||||
]
|
r'id="my_show_video">.*?<p>(.*?)</p>',
|
||||||
|
]
|
||||||
|
|
||||||
_TESTS = [
|
_TESTS = [
|
||||||
{
|
{
|
||||||
u'url': u'http://www.ign.com/videos/2013/06/05/the-last-of-us-review',
|
'url': 'http://www.ign.com/videos/2013/06/05/the-last-of-us-review',
|
||||||
u'file': u'8f862beef863986b2785559b9e1aa599.mp4',
|
'md5': 'eac8bdc1890980122c3b66f14bdd02e9',
|
||||||
u'md5': u'eac8bdc1890980122c3b66f14bdd02e9',
|
'info_dict': {
|
||||||
u'info_dict': {
|
'id': '8f862beef863986b2785559b9e1aa599',
|
||||||
u'title': u'The Last of Us Review',
|
'ext': 'mp4',
|
||||||
u'description': u'md5:c8946d4260a4d43a00d5ae8ed998870c',
|
'title': 'The Last of Us Review',
|
||||||
|
'description': 'md5:c8946d4260a4d43a00d5ae8ed998870c',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
u'url': u'http://me.ign.com/en/feature/15775/100-little-things-in-gta-5-that-will-blow-your-mind',
|
'url': 'http://me.ign.com/en/feature/15775/100-little-things-in-gta-5-that-will-blow-your-mind',
|
||||||
u'playlist': [
|
'playlist': [
|
||||||
{
|
{
|
||||||
u'file': u'5ebbd138523268b93c9141af17bec937.mp4',
|
'info_dict': {
|
||||||
u'info_dict': {
|
'id': '5ebbd138523268b93c9141af17bec937',
|
||||||
u'title': u'GTA 5 Video Review',
|
'ext': 'mp4',
|
||||||
u'description': u'Rockstar drops the mic on this generation of games. Watch our review of the masterly Grand Theft Auto V.',
|
'title': 'GTA 5 Video Review',
|
||||||
|
'description': 'Rockstar drops the mic on this generation of games. Watch our review of the masterly Grand Theft Auto V.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
u'file': u'638672ee848ae4ff108df2a296418ee2.mp4',
|
'info_dict': {
|
||||||
u'info_dict': {
|
'id': '638672ee848ae4ff108df2a296418ee2',
|
||||||
u'title': u'26 Twisted Moments from GTA 5 in Slow Motion',
|
'ext': 'mp4',
|
||||||
u'description': u'The twisted beauty of GTA 5 in stunning slow motion.',
|
'title': '26 Twisted Moments from GTA 5 in Slow Motion',
|
||||||
|
'description': 'The twisted beauty of GTA 5 in stunning slow motion.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
u'params': {
|
'params': {
|
||||||
u'skip_download': True,
|
'skip_download': True,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
def _find_video_id(self, webpage):
|
def _find_video_id(self, webpage):
|
||||||
res_id = [r'data-video-id="(.+?)"',
|
res_id = [
|
||||||
r'<object id="vid_(.+?)"',
|
r'data-video-id="(.+?)"',
|
||||||
r'<meta name="og:image" content=".*/(.+?)-(.+?)/.+.jpg"',
|
r'<object id="vid_(.+?)"',
|
||||||
]
|
r'<meta name="og:image" content=".*/(.+?)-(.+?)/.+.jpg"',
|
||||||
|
]
|
||||||
return self._search_regex(res_id, webpage, 'video id')
|
return self._search_regex(res_id, webpage, 'video id')
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
@ -68,7 +71,7 @@ class IGNIE(InfoExtractor):
|
|||||||
page_type = mobj.group('type')
|
page_type = mobj.group('type')
|
||||||
webpage = self._download_webpage(url, name_or_id)
|
webpage = self._download_webpage(url, name_or_id)
|
||||||
if page_type == 'articles':
|
if page_type == 'articles':
|
||||||
video_url = self._search_regex(r'var videoUrl = "(.+?)"', webpage, u'video url')
|
video_url = self._search_regex(r'var videoUrl = "(.+?)"', webpage, 'video url')
|
||||||
return self.url_result(video_url, ie='IGN')
|
return self.url_result(video_url, ie='IGN')
|
||||||
elif page_type != 'video':
|
elif page_type != 'video':
|
||||||
multiple_urls = re.findall(
|
multiple_urls = re.findall(
|
||||||
@ -80,50 +83,42 @@ class IGNIE(InfoExtractor):
|
|||||||
video_id = self._find_video_id(webpage)
|
video_id = self._find_video_id(webpage)
|
||||||
result = self._get_video_info(video_id)
|
result = self._get_video_info(video_id)
|
||||||
description = self._html_search_regex(self._DESCRIPTION_RE,
|
description = self._html_search_regex(self._DESCRIPTION_RE,
|
||||||
webpage, 'video description',
|
webpage, 'video description', flags=re.DOTALL)
|
||||||
flags=re.DOTALL)
|
|
||||||
result['description'] = description
|
result['description'] = description
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _get_video_info(self, video_id):
|
def _get_video_info(self, video_id):
|
||||||
config_url = self._CONFIG_URL_TEMPLATE % video_id
|
config_url = self._CONFIG_URL_TEMPLATE % video_id
|
||||||
config = json.loads(self._download_webpage(config_url, video_id,
|
config = self._download_json(config_url, video_id)
|
||||||
u'Downloading video info'))
|
|
||||||
media = config['playlist']['media']
|
media = config['playlist']['media']
|
||||||
video_url = media['url']
|
|
||||||
|
|
||||||
return {'id': media['metadata']['videoId'],
|
return {
|
||||||
'url': video_url,
|
'id': media['metadata']['videoId'],
|
||||||
'ext': determine_ext(video_url),
|
'url': media['url'],
|
||||||
'title': media['metadata']['title'],
|
'title': media['metadata']['title'],
|
||||||
'thumbnail': media['poster'][0]['url'].replace('{size}', 'grande'),
|
'thumbnail': media['poster'][0]['url'].replace('{size}', 'grande'),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class OneUPIE(IGNIE):
|
class OneUPIE(IGNIE):
|
||||||
"""Extractor for 1up.com, it uses the ign videos system."""
|
|
||||||
|
|
||||||
_VALID_URL = r'https?://gamevideos\.1up\.com/(?P<type>video)/id/(?P<name_or_id>.+)'
|
_VALID_URL = r'https?://gamevideos\.1up\.com/(?P<type>video)/id/(?P<name_or_id>.+)'
|
||||||
IE_NAME = '1up.com'
|
IE_NAME = '1up.com'
|
||||||
|
|
||||||
_DESCRIPTION_RE = r'<div id="vid_summary">(.+?)</div>'
|
_DESCRIPTION_RE = r'<div id="vid_summary">(.+?)</div>'
|
||||||
|
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
u'url': u'http://gamevideos.1up.com/video/id/34976',
|
'url': 'http://gamevideos.1up.com/video/id/34976',
|
||||||
u'file': u'34976.mp4',
|
'md5': '68a54ce4ebc772e4b71e3123d413163d',
|
||||||
u'md5': u'68a54ce4ebc772e4b71e3123d413163d',
|
'info_dict': {
|
||||||
u'info_dict': {
|
'id': '34976',
|
||||||
u'title': u'Sniper Elite V2 - Trailer',
|
'ext': 'mp4',
|
||||||
u'description': u'md5:5d289b722f5a6d940ca3136e9dae89cf',
|
'title': 'Sniper Elite V2 - Trailer',
|
||||||
|
'description': 'md5:5d289b722f5a6d940ca3136e9dae89cf',
|
||||||
}
|
}
|
||||||
}
|
}]
|
||||||
|
|
||||||
# Override IGN tests
|
|
||||||
_TESTS = []
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
id = mobj.group('name_or_id')
|
|
||||||
result = super(OneUPIE, self)._real_extract(url)
|
result = super(OneUPIE, self)._real_extract(url)
|
||||||
result['id'] = id
|
result['id'] = mobj.group('name_or_id')
|
||||||
return result
|
return result
|
||||||
|
@ -11,16 +11,15 @@ from ..utils import (
|
|||||||
|
|
||||||
class InfoQIE(InfoExtractor):
|
class InfoQIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?infoq\.com/[^/]+/(?P<id>[^/]+)$'
|
_VALID_URL = r'https?://(?:www\.)?infoq\.com/[^/]+/(?P<id>[^/]+)$'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
"name": "InfoQ",
|
'url': 'http://www.infoq.com/presentations/A-Few-of-My-Favorite-Python-Things',
|
||||||
"url": "http://www.infoq.com/presentations/A-Few-of-My-Favorite-Python-Things",
|
'md5': 'b5ca0e0a8c1fed93b0e65e48e462f9a2',
|
||||||
"file": "12-jan-pythonthings.mp4",
|
'info_dict': {
|
||||||
"info_dict": {
|
'id': '12-jan-pythonthings',
|
||||||
"description": "Mike Pirnat presents some tips and tricks, standard libraries and third party packages that make programming in Python a richer experience.",
|
'ext': 'mp4',
|
||||||
"title": "A Few of My Favorite [Python] Things",
|
'description': 'Mike Pirnat presents some tips and tricks, standard libraries and third party packages that make programming in Python a richer experience.',
|
||||||
},
|
'title': 'A Few of My Favorite [Python] Things',
|
||||||
"params": {
|
|
||||||
"skip_download": True,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,26 +29,39 @@ class InfoQIE(InfoExtractor):
|
|||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
video_title = self._html_search_regex(r'<title>(.*?)</title>', webpage, 'title')
|
||||||
|
video_description = self._html_search_meta('description', webpage, 'description')
|
||||||
|
|
||||||
|
# The server URL is hardcoded
|
||||||
|
video_url = 'rtmpe://video.infoq.com/cfx/st/'
|
||||||
|
|
||||||
# Extract video URL
|
# Extract video URL
|
||||||
encoded_id = self._search_regex(r"jsclassref ?= ?'([^']*)'", webpage, 'encoded id')
|
encoded_id = self._search_regex(
|
||||||
|
r"jsclassref\s*=\s*'([^']*)'", webpage, 'encoded id')
|
||||||
real_id = compat_urllib_parse.unquote(base64.b64decode(encoded_id.encode('ascii')).decode('utf-8'))
|
real_id = compat_urllib_parse.unquote(base64.b64decode(encoded_id.encode('ascii')).decode('utf-8'))
|
||||||
video_url = 'rtmpe://video.infoq.com/cfx/st/' + real_id
|
playpath = 'mp4:' + real_id
|
||||||
|
|
||||||
# Extract title
|
video_filename = playpath.split('/')[-1]
|
||||||
video_title = self._search_regex(r'contentTitle = "(.*?)";',
|
|
||||||
webpage, 'title')
|
|
||||||
|
|
||||||
# Extract description
|
|
||||||
video_description = self._html_search_regex(r'<meta name="description" content="(.*)"(?:\s*/)?>',
|
|
||||||
webpage, 'description', fatal=False)
|
|
||||||
|
|
||||||
video_filename = video_url.split('/')[-1]
|
|
||||||
video_id, extension = video_filename.split('.')
|
video_id, extension = video_filename.split('.')
|
||||||
|
|
||||||
|
http_base = self._search_regex(
|
||||||
|
r'EXPRESSINSTALL_SWF\s*=\s*"(https?://[^/"]+/)', webpage,
|
||||||
|
'HTTP base URL')
|
||||||
|
|
||||||
|
formats = [{
|
||||||
|
'format_id': 'rtmp',
|
||||||
|
'url': video_url,
|
||||||
|
'ext': extension,
|
||||||
|
'play_path': playpath,
|
||||||
|
}, {
|
||||||
|
'format_id': 'http',
|
||||||
|
'url': http_base + real_id,
|
||||||
|
}]
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'url': video_url,
|
|
||||||
'title': video_title,
|
'title': video_title,
|
||||||
'ext': extension, # Extension is always(?) mp4, but seems to be flv
|
|
||||||
'description': video_description,
|
'description': video_description,
|
||||||
|
'formats': formats,
|
||||||
}
|
}
|
||||||
|
@ -33,14 +33,14 @@ class IviIE(InfoExtractor):
|
|||||||
},
|
},
|
||||||
# Serial's serie
|
# Serial's serie
|
||||||
{
|
{
|
||||||
'url': 'http://www.ivi.ru/watch/dezhurnyi_angel/74791',
|
'url': 'http://www.ivi.ru/watch/dvoe_iz_lartsa/9549',
|
||||||
'md5': '3e6cc9a848c1d2ebcc6476444967baa9',
|
'md5': '221f56b35e3ed815fde2df71032f4b3e',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '74791',
|
'id': '9549',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Дежурный ангел - 1 серия',
|
'title': 'Двое из ларца - Серия 1',
|
||||||
'duration': 2490,
|
'duration': 2655,
|
||||||
'thumbnail': 'http://thumbs.ivi.ru/f7.vcp.digitalaccess.ru/contents/8/e/bc2f6c2b6e5d291152fdd32c059141.jpg',
|
'thumbnail': 'http://thumbs.ivi.ru/f15.vcp.digitalaccess.ru/contents/8/4/0068dc0677041f3336b7c2baad8fc0.jpg',
|
||||||
},
|
},
|
||||||
'skip': 'Only works from Russia',
|
'skip': 'Only works from Russia',
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ class JukeboxIE(InfoExtractor):
|
|||||||
_VALID_URL = r'^http://www\.jukebox?\..+?\/.+[,](?P<video_id>[a-z0-9\-]+)\.html'
|
_VALID_URL = r'^http://www\.jukebox?\..+?\/.+[,](?P<video_id>[a-z0-9\-]+)\.html'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.jukebox.es/kosheen/videoclip,pride,r303r.html',
|
'url': 'http://www.jukebox.es/kosheen/videoclip,pride,r303r.html',
|
||||||
'md5': '5dc6477e74b1e37042ac5acedd8413e5',
|
'md5': '1574e9b4d6438446d5b7dbcdf2786276',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'r303r',
|
'id': 'r303r',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
|
compat_str,
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
formatSeconds,
|
formatSeconds,
|
||||||
)
|
)
|
||||||
@ -24,34 +27,31 @@ class JustinTVIE(InfoExtractor):
|
|||||||
/?(?:\#.*)?$
|
/?(?:\#.*)?$
|
||||||
"""
|
"""
|
||||||
_JUSTIN_PAGE_LIMIT = 100
|
_JUSTIN_PAGE_LIMIT = 100
|
||||||
IE_NAME = u'justin.tv'
|
IE_NAME = 'justin.tv'
|
||||||
|
IE_DESC = 'justin.tv and twitch.tv'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
u'url': u'http://www.twitch.tv/thegamedevhub/b/296128360',
|
'url': 'http://www.twitch.tv/thegamedevhub/b/296128360',
|
||||||
u'file': u'296128360.flv',
|
'md5': 'ecaa8a790c22a40770901460af191c9a',
|
||||||
u'md5': u'ecaa8a790c22a40770901460af191c9a',
|
'info_dict': {
|
||||||
u'info_dict': {
|
'id': '296128360',
|
||||||
u"upload_date": u"20110927",
|
'ext': 'flv',
|
||||||
u"uploader_id": 25114803,
|
'upload_date': '20110927',
|
||||||
u"uploader": u"thegamedevhub",
|
'uploader_id': 25114803,
|
||||||
u"title": u"Beginner Series - Scripting With Python Pt.1"
|
'uploader': 'thegamedevhub',
|
||||||
|
'title': 'Beginner Series - Scripting With Python Pt.1'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def report_download_page(self, channel, offset):
|
|
||||||
"""Report attempt to download a single page of videos."""
|
|
||||||
self.to_screen(u'%s: Downloading video information from %d to %d' %
|
|
||||||
(channel, offset, offset + self._JUSTIN_PAGE_LIMIT))
|
|
||||||
|
|
||||||
# Return count of items, list of *valid* items
|
# Return count of items, list of *valid* items
|
||||||
def _parse_page(self, url, video_id):
|
def _parse_page(self, url, video_id):
|
||||||
info_json = self._download_webpage(url, video_id,
|
info_json = self._download_webpage(url, video_id,
|
||||||
u'Downloading video info JSON',
|
'Downloading video info JSON',
|
||||||
u'unable to download video info JSON')
|
'unable to download video info JSON')
|
||||||
|
|
||||||
response = json.loads(info_json)
|
response = json.loads(info_json)
|
||||||
if type(response) != list:
|
if type(response) != list:
|
||||||
error_text = response.get('error', 'unknown error')
|
error_text = response.get('error', 'unknown error')
|
||||||
raise ExtractorError(u'Justin.tv API: %s' % error_text)
|
raise ExtractorError('Justin.tv API: %s' % error_text)
|
||||||
info = []
|
info = []
|
||||||
for clip in response:
|
for clip in response:
|
||||||
video_url = clip['video_file_url']
|
video_url = clip['video_file_url']
|
||||||
@ -62,7 +62,7 @@ class JustinTVIE(InfoExtractor):
|
|||||||
video_id = clip['id']
|
video_id = clip['id']
|
||||||
video_title = clip.get('title', video_id)
|
video_title = clip.get('title', video_id)
|
||||||
info.append({
|
info.append({
|
||||||
'id': video_id,
|
'id': compat_str(video_id),
|
||||||
'url': video_url,
|
'url': video_url,
|
||||||
'title': video_title,
|
'title': video_title,
|
||||||
'uploader': clip.get('channel_name', video_uploader_id),
|
'uploader': clip.get('channel_name', video_uploader_id),
|
||||||
@ -74,8 +74,6 @@ class JustinTVIE(InfoExtractor):
|
|||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
if mobj is None:
|
|
||||||
raise ExtractorError(u'invalid URL: %s' % url)
|
|
||||||
|
|
||||||
api_base = 'http://api.justin.tv'
|
api_base = 'http://api.justin.tv'
|
||||||
paged = False
|
paged = False
|
||||||
@ -89,40 +87,41 @@ class JustinTVIE(InfoExtractor):
|
|||||||
webpage = self._download_webpage(url, chapter_id)
|
webpage = self._download_webpage(url, chapter_id)
|
||||||
m = re.search(r'PP\.archive_id = "([0-9]+)";', webpage)
|
m = re.search(r'PP\.archive_id = "([0-9]+)";', webpage)
|
||||||
if not m:
|
if not m:
|
||||||
raise ExtractorError(u'Cannot find archive of a chapter')
|
raise ExtractorError('Cannot find archive of a chapter')
|
||||||
archive_id = m.group(1)
|
archive_id = m.group(1)
|
||||||
|
|
||||||
api = api_base + '/broadcast/by_chapter/%s.xml' % chapter_id
|
api = api_base + '/broadcast/by_chapter/%s.xml' % chapter_id
|
||||||
doc = self._download_xml(api, chapter_id,
|
doc = self._download_xml(
|
||||||
note=u'Downloading chapter information',
|
api, chapter_id,
|
||||||
errnote=u'Chapter information download failed')
|
note='Downloading chapter information',
|
||||||
|
errnote='Chapter information download failed')
|
||||||
for a in doc.findall('.//archive'):
|
for a in doc.findall('.//archive'):
|
||||||
if archive_id == a.find('./id').text:
|
if archive_id == a.find('./id').text:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
raise ExtractorError(u'Could not find chapter in chapter information')
|
raise ExtractorError('Could not find chapter in chapter information')
|
||||||
|
|
||||||
video_url = a.find('./video_file_url').text
|
video_url = a.find('./video_file_url').text
|
||||||
video_ext = video_url.rpartition('.')[2] or u'flv'
|
video_ext = video_url.rpartition('.')[2] or 'flv'
|
||||||
|
|
||||||
chapter_api_url = u'https://api.twitch.tv/kraken/videos/c' + chapter_id
|
chapter_api_url = 'https://api.twitch.tv/kraken/videos/c' + chapter_id
|
||||||
chapter_info_json = self._download_webpage(chapter_api_url, u'c' + chapter_id,
|
chapter_info = self._download_json(
|
||||||
note='Downloading chapter metadata',
|
chapter_api_url, 'c' + chapter_id,
|
||||||
errnote='Download of chapter metadata failed')
|
note='Downloading chapter metadata',
|
||||||
chapter_info = json.loads(chapter_info_json)
|
errnote='Download of chapter metadata failed')
|
||||||
|
|
||||||
bracket_start = int(doc.find('.//bracket_start').text)
|
bracket_start = int(doc.find('.//bracket_start').text)
|
||||||
bracket_end = int(doc.find('.//bracket_end').text)
|
bracket_end = int(doc.find('.//bracket_end').text)
|
||||||
|
|
||||||
# TODO determine start (and probably fix up file)
|
# TODO determine start (and probably fix up file)
|
||||||
# youtube-dl -v http://www.twitch.tv/firmbelief/c/1757457
|
# youtube-dl -v http://www.twitch.tv/firmbelief/c/1757457
|
||||||
#video_url += u'?start=' + TODO:start_timestamp
|
#video_url += '?start=' + TODO:start_timestamp
|
||||||
# bracket_start is 13290, but we want 51670615
|
# bracket_start is 13290, but we want 51670615
|
||||||
self._downloader.report_warning(u'Chapter detected, but we can just download the whole file. '
|
self._downloader.report_warning('Chapter detected, but we can just download the whole file. '
|
||||||
u'Chapter starts at %s and ends at %s' % (formatSeconds(bracket_start), formatSeconds(bracket_end)))
|
'Chapter starts at %s and ends at %s' % (formatSeconds(bracket_start), formatSeconds(bracket_end)))
|
||||||
|
|
||||||
info = {
|
info = {
|
||||||
'id': u'c' + chapter_id,
|
'id': 'c' + chapter_id,
|
||||||
'url': video_url,
|
'url': video_url,
|
||||||
'ext': video_ext,
|
'ext': video_ext,
|
||||||
'title': chapter_info['title'],
|
'title': chapter_info['title'],
|
||||||
@ -131,14 +130,12 @@ class JustinTVIE(InfoExtractor):
|
|||||||
'uploader': chapter_info['channel']['display_name'],
|
'uploader': chapter_info['channel']['display_name'],
|
||||||
'uploader_id': chapter_info['channel']['name'],
|
'uploader_id': chapter_info['channel']['name'],
|
||||||
}
|
}
|
||||||
return [info]
|
return info
|
||||||
else:
|
else:
|
||||||
video_id = mobj.group('videoid')
|
video_id = mobj.group('videoid')
|
||||||
api = api_base + '/broadcast/by_archive/%s.json' % video_id
|
api = api_base + '/broadcast/by_archive/%s.json' % video_id
|
||||||
|
|
||||||
self.report_extraction(video_id)
|
entries = []
|
||||||
|
|
||||||
info = []
|
|
||||||
offset = 0
|
offset = 0
|
||||||
limit = self._JUSTIN_PAGE_LIMIT
|
limit = self._JUSTIN_PAGE_LIMIT
|
||||||
while True:
|
while True:
|
||||||
@ -146,8 +143,12 @@ class JustinTVIE(InfoExtractor):
|
|||||||
self.report_download_page(video_id, offset)
|
self.report_download_page(video_id, offset)
|
||||||
page_url = api + ('?offset=%d&limit=%d' % (offset, limit))
|
page_url = api + ('?offset=%d&limit=%d' % (offset, limit))
|
||||||
page_count, page_info = self._parse_page(page_url, video_id)
|
page_count, page_info = self._parse_page(page_url, video_id)
|
||||||
info.extend(page_info)
|
entries.extend(page_info)
|
||||||
if not paged or page_count != limit:
|
if not paged or page_count != limit:
|
||||||
break
|
break
|
||||||
offset += limit
|
offset += limit
|
||||||
return info
|
return {
|
||||||
|
'_type': 'playlist',
|
||||||
|
'id': video_id,
|
||||||
|
'entries': entries,
|
||||||
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
@ -11,22 +13,22 @@ from ..aes import (
|
|||||||
aes_decrypt_text
|
aes_decrypt_text
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class KeezMoviesIE(InfoExtractor):
|
class KeezMoviesIE(InfoExtractor):
|
||||||
_VALID_URL = r'^(?:https?://)?(?:www\.)?(?P<url>keezmovies\.com/video/.+?(?P<videoid>[0-9]+))(?:[/?&]|$)'
|
_VALID_URL = r'^https?://(?:www\.)?keezmovies\.com/video/.+?(?P<videoid>[0-9]+)(?:[/?&]|$)'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
u'url': u'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',
|
||||||
u'file': u'1214711.mp4',
|
'file': '1214711.mp4',
|
||||||
u'md5': u'6e297b7e789329923fcf83abb67c9289',
|
'md5': '6e297b7e789329923fcf83abb67c9289',
|
||||||
u'info_dict': {
|
'info_dict': {
|
||||||
u"title": u"Petite Asian Lady Mai Playing In Bathtub",
|
'title': 'Petite Asian Lady Mai Playing In Bathtub',
|
||||||
u"age_limit": 18,
|
'age_limit': 18,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
video_id = mobj.group('videoid')
|
video_id = mobj.group('videoid')
|
||||||
url = 'http://www.' + mobj.group('url')
|
|
||||||
|
|
||||||
req = compat_urllib_request.Request(url)
|
req = compat_urllib_request.Request(url)
|
||||||
req.add_header('Cookie', 'age_verified=1')
|
req.add_header('Cookie', 'age_verified=1')
|
||||||
@ -38,10 +40,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, u'title')
|
video_title = self._html_search_regex(r'<h1 [^>]*>([^<]+)', webpage, 'title')
|
||||||
video_url = compat_urllib_parse.unquote(self._html_search_regex(r'video_url=(.+?)&', webpage, u'video_url'))
|
video_url = compat_urllib_parse.unquote(self._html_search_regex(r'video_url=(.+?)&', webpage, 'video_url'))
|
||||||
if webpage.find('encrypted=true')!=-1:
|
if 'encrypted=true' in webpage:
|
||||||
password = self._html_search_regex(r'video_title=(.+?)&', webpage, u'password')
|
password = self._html_search_regex(r'video_title=(.+?)&', webpage, 'password')
|
||||||
video_url = aes_decrypt_text(video_url, password, 32).decode('utf-8')
|
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:]
|
||||||
|
@ -1,37 +1,39 @@
|
|||||||
|
# encoding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
class KickStarterIE(InfoExtractor):
|
class KickStarterIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://www\.kickstarter\.com/projects/(?P<id>\d*)/.*'
|
_VALID_URL = r'https?://www\.kickstarter\.com/projects/(?P<id>[^/]*)/.*'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
u"url": u"https://www.kickstarter.com/projects/1404461844/intersection-the-story-of-josh-grant?ref=home_location",
|
'url': 'https://www.kickstarter.com/projects/1404461844/intersection-the-story-of-josh-grant?ref=home_location',
|
||||||
u"file": u"1404461844.mp4",
|
'md5': 'c81addca81327ffa66c642b5d8b08cab',
|
||||||
u"md5": u"c81addca81327ffa66c642b5d8b08cab",
|
'info_dict': {
|
||||||
u"info_dict": {
|
'id': '1404461844',
|
||||||
u"title": u"Intersection: The Story of Josh Grant by Kyle Cowling",
|
'ext': 'mp4',
|
||||||
|
'title': 'Intersection: The Story of Josh Grant by Kyle Cowling',
|
||||||
|
'description': 'A unique motocross documentary that examines the '
|
||||||
|
'life and mind of one of sports most elite athletes: Josh Grant.',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
m = re.match(self._VALID_URL, url)
|
m = re.match(self._VALID_URL, url)
|
||||||
video_id = m.group('id')
|
video_id = m.group('id')
|
||||||
webpage_src = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
video_url = self._search_regex(r'data-video="(.*?)">',
|
video_url = self._search_regex(r'data-video-url="(.*?)"',
|
||||||
webpage_src, u'video URL')
|
webpage, 'video URL')
|
||||||
if 'mp4' in video_url:
|
video_title = self._html_search_regex(r'<title>(.*?)</title>',
|
||||||
ext = 'mp4'
|
webpage, 'title').rpartition('— Kickstarter')[0].strip()
|
||||||
else:
|
|
||||||
ext = 'flv'
|
|
||||||
video_title = self._html_search_regex(r"<title>(.*?)</title>",
|
|
||||||
webpage_src, u'title').rpartition(u'\u2014 Kickstarter')[0].strip()
|
|
||||||
|
|
||||||
results = [{
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'url': video_url,
|
'url': video_url,
|
||||||
'title': video_title,
|
'title': video_title,
|
||||||
'ext': ext,
|
'description': self._og_search_description(webpage),
|
||||||
}]
|
'thumbnail': self._og_search_thumbnail(webpage),
|
||||||
return results
|
}
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import datetime
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
|
||||||
@ -10,28 +9,48 @@ from .common import InfoExtractor
|
|||||||
class MailRuIE(InfoExtractor):
|
class MailRuIE(InfoExtractor):
|
||||||
IE_NAME = 'mailru'
|
IE_NAME = 'mailru'
|
||||||
IE_DESC = 'Видео@Mail.Ru'
|
IE_DESC = 'Видео@Mail.Ru'
|
||||||
_VALID_URL = r'http://(?:www\.)?my\.mail\.ru/video/.*#video=/?(?P<id>[^/]+/[^/]+/[^/]+/\d+)'
|
_VALID_URL = r'http://(?:www\.)?my\.mail\.ru/(?:video/.*#video=/?(?P<idv1>(?:[^/]+/){3}\d+)|(?:(?P<idv2prefix>(?:[^/]+/){2})video/(?P<idv2suffix>[^/]+/\d+))\.html)'
|
||||||
|
|
||||||
_TEST = {
|
_TESTS = [
|
||||||
'url': 'http://my.mail.ru/video/top#video=/mail/sonypicturesrus/75/76',
|
{
|
||||||
'md5': 'dea205f03120046894db4ebb6159879a',
|
'url': 'http://my.mail.ru/video/top#video=/mail/sonypicturesrus/75/76',
|
||||||
'info_dict': {
|
'md5': 'dea205f03120046894db4ebb6159879a',
|
||||||
'id': '46301138',
|
'info_dict': {
|
||||||
'ext': 'mp4',
|
'id': '46301138',
|
||||||
'title': 'Новый Человек-Паук. Высокое напряжение. Восстание Электро',
|
'ext': 'mp4',
|
||||||
'upload_date': '20140224',
|
'title': 'Новый Человек-Паук. Высокое напряжение. Восстание Электро',
|
||||||
'uploader': 'sonypicturesrus',
|
'timestamp': 1393232740,
|
||||||
'uploader_id': 'sonypicturesrus@mail.ru',
|
'upload_date': '20140224',
|
||||||
'duration': 184,
|
'uploader': 'sonypicturesrus',
|
||||||
}
|
'uploader_id': 'sonypicturesrus@mail.ru',
|
||||||
}
|
'duration': 184,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://my.mail.ru/corp/hitech/video/news_hi-tech_mail_ru/1263.html',
|
||||||
|
'md5': '00a91a58c3402204dcced523777b475f',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '46843144',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Samsung Galaxy S5 Hammer Smash Fail Battery Explosion',
|
||||||
|
'timestamp': 1397217632,
|
||||||
|
'upload_date': '20140411',
|
||||||
|
'uploader': 'hitech',
|
||||||
|
'uploader_id': 'hitech@corp.mail.ru',
|
||||||
|
'duration': 245,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
video_id = mobj.group('id')
|
video_id = mobj.group('idv1')
|
||||||
|
|
||||||
|
if not video_id:
|
||||||
|
video_id = mobj.group('idv2prefix') + mobj.group('idv2suffix')
|
||||||
|
|
||||||
video_data = self._download_json(
|
video_data = self._download_json(
|
||||||
'http://videoapi.my.mail.ru/videos/%s.json?new=1' % video_id, video_id, 'Downloading video JSON')
|
'http://api.video.mail.ru/videos/%s.json?new=1' % video_id, video_id, 'Downloading video JSON')
|
||||||
|
|
||||||
author = video_data['author']
|
author = video_data['author']
|
||||||
uploader = author['name']
|
uploader = author['name']
|
||||||
@ -40,10 +59,11 @@ class MailRuIE(InfoExtractor):
|
|||||||
movie = video_data['movie']
|
movie = video_data['movie']
|
||||||
content_id = str(movie['contentId'])
|
content_id = str(movie['contentId'])
|
||||||
title = movie['title']
|
title = movie['title']
|
||||||
|
if title.endswith('.mp4'):
|
||||||
|
title = title[:-4]
|
||||||
thumbnail = movie['poster']
|
thumbnail = movie['poster']
|
||||||
duration = movie['duration']
|
duration = movie['duration']
|
||||||
|
|
||||||
upload_date = datetime.datetime.fromtimestamp(video_data['timestamp']).strftime('%Y%m%d')
|
|
||||||
view_count = video_data['views_count']
|
view_count = video_data['views_count']
|
||||||
|
|
||||||
formats = [
|
formats = [
|
||||||
@ -57,7 +77,7 @@ class MailRuIE(InfoExtractor):
|
|||||||
'id': content_id,
|
'id': content_id,
|
||||||
'title': title,
|
'title': title,
|
||||||
'thumbnail': thumbnail,
|
'thumbnail': thumbnail,
|
||||||
'upload_date': upload_date,
|
'timestamp': video_data['timestamp'],
|
||||||
'uploader': uploader,
|
'uploader': uploader,
|
||||||
'uploader_id': uploader_id,
|
'uploader_id': uploader_id,
|
||||||
'duration': duration,
|
'duration': duration,
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
|
||||||
ExtractorError,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class MDRIE(InfoExtractor):
|
class MDRIE(InfoExtractor):
|
||||||
_VALID_URL = r'^(?P<domain>(?:https?://)?(?:www\.)?mdr\.de)/mediathek/(?:.*)/(?P<type>video|audio)(?P<video_id>[^/_]+)_.*'
|
_VALID_URL = r'^(?P<domain>https?://(?:www\.)?mdr\.de)/(?:.*)/(?P<type>video|audio)(?P<video_id>[^/_]+)(?:_|\.html)'
|
||||||
|
|
||||||
# No tests, MDR regularily deletes its videos
|
# No tests, MDR regularily deletes its videos
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.mdr.de/fakt/video189002.html',
|
||||||
|
'only_matching': True,
|
||||||
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
m = re.match(self._VALID_URL, url)
|
m = re.match(self._VALID_URL, url)
|
||||||
@ -19,9 +22,9 @@ class MDRIE(InfoExtractor):
|
|||||||
# determine title and media streams from webpage
|
# determine title and media streams from webpage
|
||||||
html = self._download_webpage(url, video_id)
|
html = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
title = self._html_search_regex(r'<h2>(.*?)</h2>', html, u'title')
|
title = self._html_search_regex(r'<h[12]>(.*?)</h[12]>', html, 'title')
|
||||||
xmlurl = self._search_regex(
|
xmlurl = self._search_regex(
|
||||||
r'(/mediathek/(?:.+)/(?:video|audio)[0-9]+-avCustom.xml)', html, u'XML URL')
|
r'dataURL:\'(/(?:.+)/(?:video|audio)[0-9]+-avCustom.xml)', html, 'XML URL')
|
||||||
|
|
||||||
doc = self._download_xml(domain + xmlurl, video_id)
|
doc = self._download_xml(domain + xmlurl, video_id)
|
||||||
formats = []
|
formats = []
|
||||||
@ -41,7 +44,7 @@ class MDRIE(InfoExtractor):
|
|||||||
if vbr_el is None:
|
if vbr_el is None:
|
||||||
format.update({
|
format.update({
|
||||||
'vcodec': 'none',
|
'vcodec': 'none',
|
||||||
'format_id': u'%s-%d' % (media_type, abr),
|
'format_id': '%s-%d' % (media_type, abr),
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
vbr = int(vbr_el.text) // 1000
|
vbr = int(vbr_el.text) // 1000
|
||||||
@ -49,12 +52,9 @@ class MDRIE(InfoExtractor):
|
|||||||
'vbr': vbr,
|
'vbr': vbr,
|
||||||
'width': int(a.find('frameWidth').text),
|
'width': int(a.find('frameWidth').text),
|
||||||
'height': int(a.find('frameHeight').text),
|
'height': int(a.find('frameHeight').text),
|
||||||
'format_id': u'%s-%d' % (media_type, vbr),
|
'format_id': '%s-%d' % (media_type, vbr),
|
||||||
})
|
})
|
||||||
formats.append(format)
|
formats.append(format)
|
||||||
if not formats:
|
|
||||||
raise ExtractorError(u'Could not find any valid formats')
|
|
||||||
|
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -13,8 +13,9 @@ class MetacriticIE(InfoExtractor):
|
|||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.metacritic.com/game/playstation-4/infamous-second-son/trailers/3698222',
|
'url': 'http://www.metacritic.com/game/playstation-4/infamous-second-son/trailers/3698222',
|
||||||
'file': '3698222.mp4',
|
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'id': '3698222',
|
||||||
|
'ext': 'mp4',
|
||||||
'title': 'inFamous: Second Son - inSide Sucker Punch: Smoke & Mirrors',
|
'title': 'inFamous: Second Son - inSide Sucker Punch: Smoke & Mirrors',
|
||||||
'description': 'Take a peak behind-the-scenes to see how Sucker Punch brings smoke into the universe of inFAMOUS Second Son on the PS4.',
|
'description': 'Take a peak behind-the-scenes to see how Sucker Punch brings smoke into the universe of inFAMOUS Second Son on the PS4.',
|
||||||
'duration': 221,
|
'duration': 221,
|
||||||
|
@ -4,9 +4,10 @@ import re
|
|||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
unified_strdate,
|
|
||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
|
int_or_none,
|
||||||
|
parse_iso8601,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -24,6 +25,10 @@ class MixcloudIE(InfoExtractor):
|
|||||||
'uploader': 'Daniel Holbach',
|
'uploader': 'Daniel Holbach',
|
||||||
'uploader_id': 'dholbach',
|
'uploader_id': 'dholbach',
|
||||||
'upload_date': '20111115',
|
'upload_date': '20111115',
|
||||||
|
'timestamp': 1321359578,
|
||||||
|
'thumbnail': 're:https?://.*\.jpg',
|
||||||
|
'view_count': int,
|
||||||
|
'like_count': int,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,10 +56,6 @@ class MixcloudIE(InfoExtractor):
|
|||||||
|
|
||||||
webpage = self._download_webpage(url, track_id)
|
webpage = self._download_webpage(url, track_id)
|
||||||
|
|
||||||
api_url = 'http://api.mixcloud.com/%s/%s/' % (uploader, cloudcast_name)
|
|
||||||
info = self._download_json(
|
|
||||||
api_url, track_id, 'Downloading cloudcast info')
|
|
||||||
|
|
||||||
preview_url = self._search_regex(
|
preview_url = self._search_regex(
|
||||||
r'\s(?:data-preview-url|m-preview)="(.+?)"', webpage, 'preview url')
|
r'\s(?:data-preview-url|m-preview)="(.+?)"', webpage, 'preview url')
|
||||||
song_url = preview_url.replace('/previews/', '/c/originals/')
|
song_url = preview_url.replace('/previews/', '/c/originals/')
|
||||||
@ -65,16 +66,41 @@ class MixcloudIE(InfoExtractor):
|
|||||||
template_url = template_url.replace('.mp3', '.m4a').replace('originals/', 'm4a/64/')
|
template_url = template_url.replace('.mp3', '.m4a').replace('originals/', 'm4a/64/')
|
||||||
final_song_url = self._get_url(template_url)
|
final_song_url = self._get_url(template_url)
|
||||||
if final_song_url is None:
|
if final_song_url is None:
|
||||||
raise ExtractorError(u'Unable to extract track url')
|
raise ExtractorError('Unable to extract track url')
|
||||||
|
|
||||||
|
PREFIX = (
|
||||||
|
r'<div class="cloudcast-play-button-container"'
|
||||||
|
r'(?:\s+[a-zA-Z0-9-]+(?:="[^"]+")?)*?\s+')
|
||||||
|
title = self._html_search_regex(
|
||||||
|
PREFIX + r'm-title="([^"]+)"', webpage, 'title')
|
||||||
|
thumbnail = self._proto_relative_url(self._html_search_regex(
|
||||||
|
PREFIX + r'm-thumbnail-url="([^"]+)"', webpage, 'thumbnail',
|
||||||
|
fatal=False))
|
||||||
|
uploader = self._html_search_regex(
|
||||||
|
PREFIX + r'm-owner-name="([^"]+)"',
|
||||||
|
webpage, 'uploader', fatal=False)
|
||||||
|
uploader_id = self._search_regex(
|
||||||
|
r'\s+"profile": "([^"]+)",', webpage, 'uploader id', fatal=False)
|
||||||
|
description = self._og_search_description(webpage)
|
||||||
|
like_count = int_or_none(self._search_regex(
|
||||||
|
r'<meta itemprop="interactionCount" content="UserLikes:([0-9]+)"',
|
||||||
|
webpage, 'like count', fatal=False))
|
||||||
|
view_count = int_or_none(self._search_regex(
|
||||||
|
r'<meta itemprop="interactionCount" content="UserPlays:([0-9]+)"',
|
||||||
|
webpage, 'play count', fatal=False))
|
||||||
|
timestamp = parse_iso8601(self._search_regex(
|
||||||
|
r'<time itemprop="dateCreated" datetime="([^"]+)">',
|
||||||
|
webpage, 'upload date'))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': track_id,
|
'id': track_id,
|
||||||
'title': info['name'],
|
'title': title,
|
||||||
'url': final_song_url,
|
'url': final_song_url,
|
||||||
'description': info.get('description'),
|
'description': description,
|
||||||
'thumbnail': info['pictures'].get('extra_large'),
|
'thumbnail': thumbnail,
|
||||||
'uploader': info['user']['name'],
|
'uploader': uploader,
|
||||||
'uploader_id': info['user']['username'],
|
'uploader_id': uploader_id,
|
||||||
'upload_date': unified_strdate(info['created_time']),
|
'timestamp': timestamp,
|
||||||
'view_count': info['play_count'],
|
'view_count': view_count,
|
||||||
|
'like_count': like_count,
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ from ..utils import (
|
|||||||
class MooshareIE(InfoExtractor):
|
class MooshareIE(InfoExtractor):
|
||||||
IE_NAME = 'mooshare'
|
IE_NAME = 'mooshare'
|
||||||
IE_DESC = 'Mooshare.biz'
|
IE_DESC = 'Mooshare.biz'
|
||||||
_VALID_URL = r'http://mooshare\.biz/(?P<id>[\da-z]{12})'
|
_VALID_URL = r'http://(?:www\.)?mooshare\.biz/(?P<id>[\da-z]{12})'
|
||||||
|
|
||||||
_TESTS = [
|
_TESTS = [
|
||||||
{
|
{
|
||||||
|
47
youtube_dl/extractor/morningstar.py
Normal file
47
youtube_dl/extractor/morningstar.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
|
class MorningstarIE(InfoExtractor):
|
||||||
|
IE_DESC = 'morningstar.com'
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?morningstar\.com/[cC]over/video[cC]enter\.aspx\?id=(?P<id>[0-9]+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.morningstar.com/cover/videocenter.aspx?id=615869',
|
||||||
|
'md5': '6c0acface7a787aadc8391e4bbf7b0f5',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '615869',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Get Ahead of the Curve on 2013 Taxes',
|
||||||
|
'description': "Vanguard's Joel Dickson on managing higher tax rates for high-income earners and fund capital-gain distributions in 2013.",
|
||||||
|
'thumbnail': r're:^https?://.*m(?:orning)?star\.com/.+thumb\.jpg$'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
title = self._html_search_regex(
|
||||||
|
r'<h1 id="titleLink">(.*?)</h1>', webpage, 'title')
|
||||||
|
video_url = self._html_search_regex(
|
||||||
|
r'<input type="hidden" id="hidVideoUrl" value="([^"]+)"',
|
||||||
|
webpage, 'video URL')
|
||||||
|
thumbnail = self._html_search_regex(
|
||||||
|
r'<input type="hidden" id="hidSnapshot" value="([^"]+)"',
|
||||||
|
webpage, 'thumbnail', fatal=False)
|
||||||
|
description = self._html_search_regex(
|
||||||
|
r'<div id="mstarDeck".*?>(.*?)</div>',
|
||||||
|
webpage, 'description', fatal=False)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'url': video_url,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'description': description,
|
||||||
|
}
|
63
youtube_dl/extractor/motorsport.py
Normal file
63
youtube_dl/extractor/motorsport.py
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
compat_parse_qs,
|
||||||
|
compat_str,
|
||||||
|
int_or_none,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MotorsportIE(InfoExtractor):
|
||||||
|
IE_DESC = 'motorsport.com'
|
||||||
|
_VALID_URL = r'http://www\.motorsport\.com/[^/?#]+/video/(?:[^/?#]+/)(?P<id>[^/]+)/(?:$|[?#])'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.motorsport.com/f1/video/main-gallery/red-bull-racing-2014-rules-explained/',
|
||||||
|
'md5': '5592cb7c5005d9b2c163df5ac3dc04e4',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '7063',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Red Bull Racing: 2014 Rules Explained',
|
||||||
|
'duration': 207,
|
||||||
|
'description': 'A new clip from Red Bull sees Daniel Ricciardo and Sebastian Vettel explain the 2014 Formula One regulations – which are arguably the most complex the sport has ever seen.',
|
||||||
|
'uploader': 'rainiere',
|
||||||
|
'thumbnail': r're:^http://.*motorsport\.com/.+\.jpg$'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
display_id = mobj.group('id')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
flashvars_code = self._html_search_regex(
|
||||||
|
r'<embed id="player".*?flashvars="([^"]+)"', webpage, 'flashvars')
|
||||||
|
flashvars = compat_parse_qs(flashvars_code)
|
||||||
|
params = json.loads(flashvars['parameters'][0])
|
||||||
|
|
||||||
|
e = compat_str(int(time.time()) + 24 * 60 * 60)
|
||||||
|
base_video_url = params['location'] + '?e=' + e
|
||||||
|
s = 'h3hg713fh32'
|
||||||
|
h = hashlib.md5((s + base_video_url).encode('utf-8')).hexdigest()
|
||||||
|
video_url = base_video_url + '&h=' + h
|
||||||
|
|
||||||
|
uploader = self._html_search_regex(
|
||||||
|
r'(?s)<span class="label">Video by: </span>(.*?)</a>', webpage,
|
||||||
|
'uploader', fatal=False)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': params['video_id'],
|
||||||
|
'display_id': display_id,
|
||||||
|
'title': params['title'],
|
||||||
|
'url': video_url,
|
||||||
|
'description': params.get('description'),
|
||||||
|
'thumbnail': params.get('main_thumb'),
|
||||||
|
'duration': int_or_none(params.get('duration')),
|
||||||
|
'uploader': uploader,
|
||||||
|
}
|
45
youtube_dl/extractor/moviezine.py
Normal file
45
youtube_dl/extractor/moviezine.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
|
class MoviezineIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://www\.moviezine\.se/video/(?P<id>[^?#]+)'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.moviezine.se/video/205866',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '205866',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Oculus - Trailer 1',
|
||||||
|
'description': 'md5:40cc6790fc81d931850ca9249b40e8a4',
|
||||||
|
'thumbnail': 're:http://.*\.jpg',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
jsplayer = self._download_webpage('http://www.moviezine.se/api/player.js?video=%s' % video_id, video_id, 'Downloading js api player')
|
||||||
|
|
||||||
|
formats =[{
|
||||||
|
'format_id': 'sd',
|
||||||
|
'url': self._html_search_regex(r'file: "(.+?)",', jsplayer, 'file'),
|
||||||
|
'quality': 0,
|
||||||
|
'ext': 'mp4',
|
||||||
|
}]
|
||||||
|
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': self._search_regex(r'title: "(.+?)",', jsplayer, 'title'),
|
||||||
|
'thumbnail': self._search_regex(r'image: "(.+?)",', jsplayer, 'image'),
|
||||||
|
'formats': formats,
|
||||||
|
'description': self._og_search_description(webpage),
|
||||||
|
}
|
27
youtube_dl/extractor/movshare.py
Normal file
27
youtube_dl/extractor/movshare.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .novamov import NovaMovIE
|
||||||
|
|
||||||
|
|
||||||
|
class MovShareIE(NovaMovIE):
|
||||||
|
IE_NAME = 'movshare'
|
||||||
|
IE_DESC = 'MovShare'
|
||||||
|
|
||||||
|
_VALID_URL = NovaMovIE._VALID_URL_TEMPLATE % {'host': 'movshare\.(?:net|sx|ag)'}
|
||||||
|
|
||||||
|
_HOST = 'www.movshare.net'
|
||||||
|
|
||||||
|
_FILE_DELETED_REGEX = r'>This file no longer exists on our servers.<'
|
||||||
|
_TITLE_REGEX = r'<strong>Title:</strong> ([^<]+)</p>'
|
||||||
|
_DESCRIPTION_REGEX = r'<strong>Description:</strong> ([^<]+)</p>'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.movshare.net/video/559e28be54d96',
|
||||||
|
'md5': 'abd31a2132947262c50429e1d16c1bfd',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '559e28be54d96',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'dissapeared image',
|
||||||
|
'description': 'optical illusion dissapeared image magic illusion',
|
||||||
|
}
|
||||||
|
}
|
@ -4,9 +4,7 @@ import json
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import int_or_none
|
||||||
int_or_none,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class MporaIE(InfoExtractor):
|
class MporaIE(InfoExtractor):
|
||||||
@ -20,7 +18,7 @@ class MporaIE(InfoExtractor):
|
|||||||
'info_dict': {
|
'info_dict': {
|
||||||
'title': 'Katy Curd - Winter in the Forest',
|
'title': 'Katy Curd - Winter in the Forest',
|
||||||
'duration': 416,
|
'duration': 416,
|
||||||
'uploader': 'petenewman',
|
'uploader': 'Peter Newman Media',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
75
youtube_dl/extractor/musicplayon.py
Normal file
75
youtube_dl/extractor/musicplayon.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
# encoding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import int_or_none
|
||||||
|
|
||||||
|
|
||||||
|
class MusicPlayOnIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:.+?\.)?musicplayon\.com/play(?:-touch)?\?(?:v|pl=100&play)=(?P<id>\d+)'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://en.musicplayon.com/play?v=433377',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '433377',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Rick Ross - Interview On Chelsea Lately (2014)',
|
||||||
|
'description': 'Rick Ross Interview On Chelsea Lately',
|
||||||
|
'duration': 342,
|
||||||
|
'uploader': 'ultrafish',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# m3u8 download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
page = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
title = self._og_search_title(page)
|
||||||
|
description = self._og_search_description(page)
|
||||||
|
thumbnail = self._og_search_thumbnail(page)
|
||||||
|
duration = self._html_search_meta('video:duration', page, 'duration', fatal=False)
|
||||||
|
view_count = self._og_search_property('count', page, fatal=False)
|
||||||
|
uploader = self._html_search_regex(
|
||||||
|
r'<div>by <a href="[^"]+" class="purple">([^<]+)</a></div>', page, 'uploader', fatal=False)
|
||||||
|
|
||||||
|
formats = [
|
||||||
|
{
|
||||||
|
'url': 'http://media0-eu-nl.musicplayon.com/stream-mobile?id=%s&type=.mp4' % video_id,
|
||||||
|
'ext': 'mp4',
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
manifest = self._download_webpage(
|
||||||
|
'http://en.musicplayon.com/manifest.m3u8?v=%s' % video_id, video_id, 'Downloading manifest')
|
||||||
|
|
||||||
|
for entry in manifest.split('#')[1:]:
|
||||||
|
if entry.startswith('EXT-X-STREAM-INF:'):
|
||||||
|
meta, url, _ = entry.split('\n')
|
||||||
|
params = dict(param.split('=') for param in meta.split(',')[1:])
|
||||||
|
formats.append({
|
||||||
|
'url': url,
|
||||||
|
'ext': 'mp4',
|
||||||
|
'tbr': int(params['BANDWIDTH']),
|
||||||
|
'width': int(params['RESOLUTION'].split('x')[1]),
|
||||||
|
'height': int(params['RESOLUTION'].split('x')[-1]),
|
||||||
|
'format_note': params['NAME'].replace('"', '').strip(),
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'uploader': uploader,
|
||||||
|
'duration': int_or_none(duration),
|
||||||
|
'view_count': int_or_none(view_count),
|
||||||
|
'formats': formats,
|
||||||
|
}
|
@ -6,12 +6,13 @@ from .common import InfoExtractor
|
|||||||
|
|
||||||
|
|
||||||
class NBAIE(InfoExtractor):
|
class NBAIE(InfoExtractor):
|
||||||
_VALID_URL = r'^(?:https?://)?(?:watch\.|www\.)?nba\.com/(?:nba/)?video(/[^?]*?)(?:/index\.html)?(?:\?.*)?$'
|
_VALID_URL = r'https?://(?:watch\.|www\.)?nba\.com/(?:nba/)?video(?P<id>/[^?]*?)(?:/index\.html)?(?:\?.*)?$'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.nba.com/video/games/nets/2012/12/04/0021200253-okc-bkn-recap.nba/index.html',
|
'url': 'http://www.nba.com/video/games/nets/2012/12/04/0021200253-okc-bkn-recap.nba/index.html',
|
||||||
'file': u'0021200253-okc-bkn-recap.nba.mp4',
|
|
||||||
'md5': u'c0edcfc37607344e2ff8f13c378c88a4',
|
'md5': u'c0edcfc37607344e2ff8f13c378c88a4',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'id': '0021200253-okc-bkn-recap.nba',
|
||||||
|
'ext': 'mp4',
|
||||||
'description': 'Kevin Durant scores 32 points and dishes out six assists as the Thunder beat the Nets in Brooklyn.',
|
'description': 'Kevin Durant scores 32 points and dishes out six assists as the Thunder beat the Nets in Brooklyn.',
|
||||||
'title': 'Thunder vs. Nets',
|
'title': 'Thunder vs. Nets',
|
||||||
},
|
},
|
||||||
@ -19,7 +20,7 @@ class NBAIE(InfoExtractor):
|
|||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
video_id = mobj.group(1)
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
@ -33,7 +34,6 @@ class NBAIE(InfoExtractor):
|
|||||||
return {
|
return {
|
||||||
'id': shortened_video_id,
|
'id': shortened_video_id,
|
||||||
'url': video_url,
|
'url': video_url,
|
||||||
'ext': 'mp4',
|
|
||||||
'title': title,
|
'title': title,
|
||||||
'description': description,
|
'description': description,
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import json
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import find_xpath_attr, compat_str
|
from ..utils import find_xpath_attr, compat_str
|
||||||
@ -31,30 +32,68 @@ class NBCIE(InfoExtractor):
|
|||||||
|
|
||||||
|
|
||||||
class NBCNewsIE(InfoExtractor):
|
class NBCNewsIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://www\.nbcnews\.com/video/.+?/(?P<id>\d+)'
|
_VALID_URL = r'''(?x)https?://www\.nbcnews\.com/
|
||||||
|
((video/.+?/(?P<id>\d+))|
|
||||||
|
(feature/[^/]+/(?P<title>.+)))
|
||||||
|
'''
|
||||||
|
|
||||||
_TEST = {
|
_TESTS = [
|
||||||
'url': 'http://www.nbcnews.com/video/nbc-news/52753292',
|
{
|
||||||
'md5': '47abaac93c6eaf9ad37ee6c4463a5179',
|
'url': 'http://www.nbcnews.com/video/nbc-news/52753292',
|
||||||
'info_dict': {
|
'md5': '47abaac93c6eaf9ad37ee6c4463a5179',
|
||||||
'id': '52753292',
|
'info_dict': {
|
||||||
'ext': 'flv',
|
'id': '52753292',
|
||||||
'title': 'Crew emerges after four-month Mars food study',
|
'ext': 'flv',
|
||||||
'description': 'md5:24e632ffac72b35f8b67a12d1b6ddfc1',
|
'title': 'Crew emerges after four-month Mars food study',
|
||||||
|
'description': 'md5:24e632ffac72b35f8b67a12d1b6ddfc1',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
{
|
||||||
|
'url': 'http://www.nbcnews.com/feature/edward-snowden-interview/how-twitter-reacted-snowden-interview-n117236',
|
||||||
|
'md5': 'b2421750c9f260783721d898f4c42063',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'I1wpAI_zmhsQ',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'How Twitter Reacted To The Snowden Interview',
|
||||||
|
'description': 'md5:65a0bd5d76fe114f3c2727aa3a81fe64',
|
||||||
|
},
|
||||||
|
'add_ie': ['ThePlatform'],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
video_id = mobj.group('id')
|
video_id = mobj.group('id')
|
||||||
all_info = self._download_xml('http://www.nbcnews.com/id/%s/displaymode/1219' % video_id, video_id)
|
if video_id is not None:
|
||||||
info = all_info.find('video')
|
all_info = self._download_xml('http://www.nbcnews.com/id/%s/displaymode/1219' % video_id, video_id)
|
||||||
|
info = all_info.find('video')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': info.find('headline').text,
|
'title': info.find('headline').text,
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'url': find_xpath_attr(info, 'media', 'type', 'flashVideo').text,
|
'url': find_xpath_attr(info, 'media', 'type', 'flashVideo').text,
|
||||||
'description': compat_str(info.find('caption').text),
|
'description': compat_str(info.find('caption').text),
|
||||||
'thumbnail': find_xpath_attr(info, 'media', 'type', 'thumbnail').text,
|
'thumbnail': find_xpath_attr(info, 'media', 'type', 'thumbnail').text,
|
||||||
}
|
}
|
||||||
|
else:
|
||||||
|
# "feature" pages use theplatform.com
|
||||||
|
title = mobj.group('title')
|
||||||
|
webpage = self._download_webpage(url, title)
|
||||||
|
bootstrap_json = self._search_regex(
|
||||||
|
r'var bootstrapJson = ({.+})\s*$', webpage, 'bootstrap json',
|
||||||
|
flags=re.MULTILINE)
|
||||||
|
bootstrap = json.loads(bootstrap_json)
|
||||||
|
info = bootstrap['results'][0]['video']
|
||||||
|
playlist_url = info['fallbackPlaylistUrl'] + '?form=MPXNBCNewsAPI'
|
||||||
|
mpxid = info['mpxId']
|
||||||
|
all_videos = self._download_json(playlist_url, title)['videos']
|
||||||
|
# The response contains additional videos
|
||||||
|
info = next(v for v in all_videos if v['mpxId'] == mpxid)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'_type': 'url',
|
||||||
|
# We get the best quality video
|
||||||
|
'url': info['videoAssets'][-1]['publicUrl'],
|
||||||
|
'ie_key': 'ThePlatform',
|
||||||
|
}
|
||||||
|
@ -4,7 +4,11 @@ from __future__ import unicode_literals
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import ExtractorError
|
from ..utils import (
|
||||||
|
ExtractorError,
|
||||||
|
int_or_none,
|
||||||
|
qualities,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class NDRIE(InfoExtractor):
|
class NDRIE(InfoExtractor):
|
||||||
@ -45,17 +49,16 @@ class NDRIE(InfoExtractor):
|
|||||||
|
|
||||||
page = self._download_webpage(url, video_id, 'Downloading page')
|
page = self._download_webpage(url, video_id, 'Downloading page')
|
||||||
|
|
||||||
title = self._og_search_title(page)
|
title = self._og_search_title(page).strip()
|
||||||
description = self._og_search_description(page)
|
description = self._og_search_description(page)
|
||||||
|
if description:
|
||||||
|
description = description.strip()
|
||||||
|
|
||||||
mobj = re.search(
|
duration = int_or_none(self._html_search_regex(r'duration: (\d+),\n', page, 'duration', fatal=False))
|
||||||
r'<div class="duration"><span class="min">(?P<minutes>\d+)</span>:<span class="sec">(?P<seconds>\d+)</span></div>',
|
|
||||||
page)
|
|
||||||
duration = int(mobj.group('minutes')) * 60 + int(mobj.group('seconds')) if mobj else None
|
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
|
|
||||||
mp3_url = re.search(r'''{src:'(?P<audio>[^']+)', type:"audio/mp3"},''', page)
|
mp3_url = re.search(r'''\{src:'(?P<audio>[^']+)', type:"audio/mp3"},''', page)
|
||||||
if mp3_url:
|
if mp3_url:
|
||||||
formats.append({
|
formats.append({
|
||||||
'url': mp3_url.group('audio'),
|
'url': mp3_url.group('audio'),
|
||||||
@ -64,13 +67,15 @@ class NDRIE(InfoExtractor):
|
|||||||
|
|
||||||
thumbnail = None
|
thumbnail = None
|
||||||
|
|
||||||
video_url = re.search(r'''3: {src:'(?P<video>.+?)\.hi\.mp4', type:"video/mp4"},''', page)
|
video_url = re.search(r'''3: \{src:'(?P<video>.+?)\.hi\.mp4', type:"video/mp4"},''', page)
|
||||||
if video_url:
|
if video_url:
|
||||||
thumbnail = self._html_search_regex(r'(?m)title: "NDR PLAYER",\s*poster: "([^"]+)",',
|
thumbnails = re.findall(r'''\d+: \{src: "([^"]+)"(?: \|\| '[^']+')?, quality: '([^']+)'}''', page)
|
||||||
page, 'thumbnail', fatal=False)
|
if thumbnails:
|
||||||
if thumbnail:
|
quality_key = qualities(['xs', 's', 'm', 'l', 'xl'])
|
||||||
thumbnail = 'http://www.ndr.de' + thumbnail
|
largest = max(thumbnails, key=lambda thumb: quality_key(thumb[1]))
|
||||||
for format_id in ['lo', 'hi', 'hq']:
|
thumbnail = 'http://www.ndr.de' + largest[0]
|
||||||
|
|
||||||
|
for format_id in 'lo', 'hi', 'hq':
|
||||||
formats.append({
|
formats.append({
|
||||||
'url': '%s.%s.mp4' % (video_url.group('video'), format_id),
|
'url': '%s.%s.mp4' % (video_url.group('video'), format_id),
|
||||||
'format_id': format_id,
|
'format_id': format_id,
|
||||||
|
87
youtube_dl/extractor/newstube.py
Normal file
87
youtube_dl/extractor/newstube.py
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
# encoding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
|
class NewstubeIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?newstube\.ru/media/(?P<id>.+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://newstube.ru/media/na-korable-progress-prodolzhaetsya-testirovanie-sistemy-kurs',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'd156a237-a6e9-4111-a682-039995f721f1',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'На корабле «Прогресс» продолжается тестирование системы «Курс»',
|
||||||
|
'description': 'md5:d0cbe7b4a6f600552617e48548d5dc77',
|
||||||
|
'duration': 20.04,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# rtmp download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
page = self._download_webpage(url, video_id, 'Downloading page')
|
||||||
|
|
||||||
|
video_guid = self._html_search_regex(
|
||||||
|
r'<meta property="og:video" content="https?://(?:www\.)?newstube\.ru/freshplayer\.swf\?guid=(?P<guid>[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12})',
|
||||||
|
page, 'video GUID')
|
||||||
|
|
||||||
|
player = self._download_xml(
|
||||||
|
'http://p.newstube.ru/v2/player.asmx/GetAutoPlayInfo6?state=&url=%s&sessionId=&id=%s&placement=profile&location=n2' % (url, video_guid),
|
||||||
|
video_guid, 'Downloading player XML')
|
||||||
|
|
||||||
|
def ns(s):
|
||||||
|
return s.replace('/', '/%(ns)s') % {'ns': '{http://app1.newstube.ru/N2SiteWS/player.asmx}'}
|
||||||
|
|
||||||
|
session_id = player.find(ns('./SessionId')).text
|
||||||
|
media_info = player.find(ns('./Medias/MediaInfo'))
|
||||||
|
title = media_info.find(ns('./Name')).text
|
||||||
|
description = self._og_search_description(page)
|
||||||
|
thumbnail = media_info.find(ns('./KeyFrame')).text
|
||||||
|
duration = int(media_info.find(ns('./Duration')).text) / 1000.0
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
|
||||||
|
for stream_info in media_info.findall(ns('./Streams/StreamInfo')):
|
||||||
|
media_location = stream_info.find(ns('./MediaLocation'))
|
||||||
|
if media_location is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
server = media_location.find(ns('./Server')).text
|
||||||
|
app = media_location.find(ns('./App')).text
|
||||||
|
media_id = stream_info.find(ns('./Id')).text
|
||||||
|
quality_id = stream_info.find(ns('./QualityId')).text
|
||||||
|
name = stream_info.find(ns('./Name')).text
|
||||||
|
width = int(stream_info.find(ns('./Width')).text)
|
||||||
|
height = int(stream_info.find(ns('./Height')).text)
|
||||||
|
|
||||||
|
formats.append({
|
||||||
|
'url': 'rtmp://%s/%s' % (server, app),
|
||||||
|
'app': app,
|
||||||
|
'play_path': '01/%s' % video_guid.upper(),
|
||||||
|
'rtmp_conn': ['S:%s' % session_id, 'S:%s' % media_id, 'S:n2'],
|
||||||
|
'page_url': url,
|
||||||
|
'ext': 'flv',
|
||||||
|
'format_id': quality_id,
|
||||||
|
'format_note': name,
|
||||||
|
'width': width,
|
||||||
|
'height': height,
|
||||||
|
})
|
||||||
|
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_guid,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'duration': duration,
|
||||||
|
'formats': formats,
|
||||||
|
}
|
@ -73,14 +73,16 @@ class NFBIE(InfoExtractor):
|
|||||||
title = media.find('title').text
|
title = media.find('title').text
|
||||||
description = media.find('description').text
|
description = media.find('description').text
|
||||||
# It seems assets always go from lower to better quality, so no need to sort
|
# It seems assets always go from lower to better quality, so no need to sort
|
||||||
formats = [{
|
for asset in media.findall('assets/asset'):
|
||||||
'url': x.find('default/streamerURI').text,
|
for x in asset:
|
||||||
'app': x.find('default/streamerURI').text.split('/', 3)[3],
|
formats.append({
|
||||||
'play_path': x.find('default/url').text,
|
'url': x.find('streamerURI').text,
|
||||||
'rtmp_live': False,
|
'app': x.find('streamerURI').text.split('/', 3)[3],
|
||||||
'ext': 'mp4',
|
'play_path': x.find('url').text,
|
||||||
'format_id': x.get('quality'),
|
'rtmp_live': False,
|
||||||
} for x in media.findall('assets/asset')]
|
'ext': 'mp4',
|
||||||
|
'format_id': '%s-%s' % (x.tag, asset.get('quality')),
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import socket
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
compat_http_client,
|
|
||||||
compat_urllib_error,
|
|
||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
compat_urllib_request,
|
compat_urllib_request,
|
||||||
compat_urlparse,
|
compat_urlparse,
|
||||||
@ -18,57 +16,54 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class NiconicoIE(InfoExtractor):
|
class NiconicoIE(InfoExtractor):
|
||||||
IE_NAME = u'niconico'
|
IE_NAME = 'niconico'
|
||||||
IE_DESC = u'ニコニコ動画'
|
IE_DESC = 'ニコニコ動画'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
u'url': u'http://www.nicovideo.jp/watch/sm22312215',
|
'url': 'http://www.nicovideo.jp/watch/sm22312215',
|
||||||
u'file': u'sm22312215.mp4',
|
'md5': 'd1a75c0823e2f629128c43e1212760f9',
|
||||||
u'md5': u'd1a75c0823e2f629128c43e1212760f9',
|
'info_dict': {
|
||||||
u'info_dict': {
|
'id': 'sm22312215',
|
||||||
u'title': u'Big Buck Bunny',
|
'ext': 'mp4',
|
||||||
u'uploader': u'takuya0301',
|
'title': 'Big Buck Bunny',
|
||||||
u'uploader_id': u'2698420',
|
'uploader': 'takuya0301',
|
||||||
u'upload_date': u'20131123',
|
'uploader_id': '2698420',
|
||||||
u'description': u'(c) copyright 2008, Blender Foundation / www.bigbuckbunny.org',
|
'upload_date': '20131123',
|
||||||
|
'description': '(c) copyright 2008, Blender Foundation / www.bigbuckbunny.org',
|
||||||
},
|
},
|
||||||
u'params': {
|
'params': {
|
||||||
u'username': u'ydl.niconico@gmail.com',
|
'username': 'ydl.niconico@gmail.com',
|
||||||
u'password': u'youtube-dl',
|
'password': 'youtube-dl',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
_VALID_URL = r'^https?://(?:www\.|secure\.)?nicovideo\.jp/watch/([a-z][a-z][0-9]+)(?:.*)$'
|
_VALID_URL = r'^https?://(?:www\.|secure\.)?nicovideo\.jp/watch/([a-z][a-z][0-9]+)(?:.*)$'
|
||||||
_NETRC_MACHINE = 'niconico'
|
_NETRC_MACHINE = 'niconico'
|
||||||
# If True it will raise an error if no login info is provided
|
|
||||||
_LOGIN_REQUIRED = True
|
|
||||||
|
|
||||||
def _real_initialize(self):
|
def _real_initialize(self):
|
||||||
self._login()
|
self._login()
|
||||||
|
|
||||||
def _login(self):
|
def _login(self):
|
||||||
(username, password) = self._get_login_info()
|
(username, password) = self._get_login_info()
|
||||||
# No authentication to be performed
|
|
||||||
if username is None:
|
if username is None:
|
||||||
if self._LOGIN_REQUIRED:
|
# Login is required
|
||||||
raise ExtractorError(u'No login info available, needed for using %s.' % self.IE_NAME, expected=True)
|
raise ExtractorError('No login info available, needed for using %s.' % self.IE_NAME, expected=True)
|
||||||
return False
|
|
||||||
|
|
||||||
# Log in
|
# Log in
|
||||||
login_form_strs = {
|
login_form_strs = {
|
||||||
u'mail': username,
|
'mail': username,
|
||||||
u'password': password,
|
'password': password,
|
||||||
}
|
}
|
||||||
# Convert to UTF-8 *before* urlencode because Python 2.x's urlencode
|
# Convert to UTF-8 *before* urlencode because Python 2.x's urlencode
|
||||||
# chokes on unicode
|
# chokes on unicode
|
||||||
login_form = dict((k.encode('utf-8'), v.encode('utf-8')) for k,v in login_form_strs.items())
|
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')
|
login_data = compat_urllib_parse.urlencode(login_form).encode('utf-8')
|
||||||
request = compat_urllib_request.Request(
|
request = compat_urllib_request.Request(
|
||||||
u'https://secure.nicovideo.jp/secure/login', login_data)
|
'https://secure.nicovideo.jp/secure/login', login_data)
|
||||||
login_results = self._download_webpage(
|
login_results = self._download_webpage(
|
||||||
request, u'', note=u'Logging in', errnote=u'Unable to log in')
|
request, None, note='Logging in', errnote='Unable to log in')
|
||||||
if re.search(r'(?i)<h1 class="mb8p4">Log in error</h1>', login_results) is not None:
|
if re.search(r'(?i)<h1 class="mb8p4">Log in error</h1>', login_results) is not None:
|
||||||
self._downloader.report_warning(u'unable to log in: bad username or password')
|
self._downloader.report_warning('unable to log in: bad username or password')
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -82,12 +77,12 @@ class NiconicoIE(InfoExtractor):
|
|||||||
|
|
||||||
video_info = self._download_xml(
|
video_info = self._download_xml(
|
||||||
'http://ext.nicovideo.jp/api/getthumbinfo/' + video_id, video_id,
|
'http://ext.nicovideo.jp/api/getthumbinfo/' + video_id, video_id,
|
||||||
note=u'Downloading video info page')
|
note='Downloading video info page')
|
||||||
|
|
||||||
# Get flv info
|
# Get flv info
|
||||||
flv_info_webpage = self._download_webpage(
|
flv_info_webpage = self._download_webpage(
|
||||||
u'http://flapi.nicovideo.jp/api/getflv?v=' + video_id,
|
'http://flapi.nicovideo.jp/api/getflv?v=' + video_id,
|
||||||
video_id, u'Downloading flv info')
|
video_id, 'Downloading flv info')
|
||||||
video_real_url = compat_urlparse.parse_qs(flv_info_webpage)['url'][0]
|
video_real_url = compat_urlparse.parse_qs(flv_info_webpage)['url'][0]
|
||||||
|
|
||||||
# Start extracting information
|
# Start extracting information
|
||||||
@ -106,22 +101,22 @@ class NiconicoIE(InfoExtractor):
|
|||||||
url = 'http://seiga.nicovideo.jp/api/user/info?id=' + video_uploader_id
|
url = 'http://seiga.nicovideo.jp/api/user/info?id=' + video_uploader_id
|
||||||
try:
|
try:
|
||||||
user_info = self._download_xml(
|
user_info = self._download_xml(
|
||||||
url, video_id, note=u'Downloading user information')
|
url, video_id, note='Downloading user information')
|
||||||
video_uploader = user_info.find('.//nickname').text
|
video_uploader = user_info.find('.//nickname').text
|
||||||
except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
|
except ExtractorError as err:
|
||||||
self._downloader.report_warning(u'Unable to download user info webpage: %s' % compat_str(err))
|
self._downloader.report_warning('Unable to download user info webpage: %s' % compat_str(err))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'url': video_real_url,
|
'url': video_real_url,
|
||||||
'title': video_title,
|
'title': video_title,
|
||||||
'ext': video_extension,
|
'ext': video_extension,
|
||||||
'format': video_format,
|
'format': video_format,
|
||||||
'thumbnail': video_thumbnail,
|
'thumbnail': video_thumbnail,
|
||||||
'description': video_description,
|
'description': video_description,
|
||||||
'uploader': video_uploader,
|
'uploader': video_uploader,
|
||||||
'upload_date': video_upload_date,
|
'upload_date': video_upload_date,
|
||||||
'uploader_id': video_uploader_id,
|
'uploader_id': video_uploader_id,
|
||||||
'view_count': video_view_count,
|
'view_count': video_view_count,
|
||||||
'webpage_url': video_webpage_url,
|
'webpage_url': video_webpage_url,
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,22 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import json
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from ..utils import str_to_int
|
||||||
|
|
||||||
|
|
||||||
class NineGagIE(InfoExtractor):
|
class NineGagIE(InfoExtractor):
|
||||||
IE_NAME = '9gag'
|
IE_NAME = '9gag'
|
||||||
_VALID_URL = r'^https?://(?:www\.)?9gag\.tv/v/(?P<id>[0-9]+)'
|
_VALID_URL = r'''(?x)^https?://(?:www\.)?9gag\.tv/
|
||||||
|
(?:
|
||||||
|
v/(?P<numid>[0-9]+)|
|
||||||
|
p/(?P<id>[a-zA-Z0-9]+)/(?P<display_id>[^?#/]+)
|
||||||
|
)
|
||||||
|
'''
|
||||||
|
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
"url": "http://9gag.tv/v/1912",
|
"url": "http://9gag.tv/v/1912",
|
||||||
"info_dict": {
|
"info_dict": {
|
||||||
"id": "1912",
|
"id": "1912",
|
||||||
@ -20,34 +27,42 @@ class NineGagIE(InfoExtractor):
|
|||||||
"thumbnail": "re:^https?://",
|
"thumbnail": "re:^https?://",
|
||||||
},
|
},
|
||||||
'add_ie': ['Youtube']
|
'add_ie': ['Youtube']
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://9gag.tv/p/KklwM/alternate-banned-opening-scene-of-gravity?ref=fsidebar',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'KklwM',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'display_id': 'alternate-banned-opening-scene-of-gravity',
|
||||||
|
"description": "While Gravity was a pretty awesome movie already, YouTuber Krishna Shenoi came up with a way to improve upon it, introducing a much better solution to Sandra Bullock's seemingly endless tumble in space. The ending is priceless.",
|
||||||
|
'title': "Banned Opening Scene Of \"Gravity\" That Changes The Whole Movie",
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
video_id = mobj.group('id')
|
video_id = mobj.group('numid') or mobj.group('id')
|
||||||
|
display_id = mobj.group('display_id') or video_id
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
|
||||||
youtube_id = self._html_search_regex(
|
post_view = json.loads(self._html_search_regex(
|
||||||
r'(?s)id="jsid-video-post-container".*?data-external-id="([^"]+)"',
|
r'var postView = new app\.PostView\({\s*post:\s*({.+?}),', webpage, 'post view'))
|
||||||
webpage, 'video ID')
|
|
||||||
description = self._html_search_regex(
|
youtube_id = post_view['videoExternalId']
|
||||||
r'(?s)<div class="video-caption">.*?<p>(.*?)</p>', webpage,
|
title = post_view['title']
|
||||||
'description', fatal=False)
|
description = post_view['description']
|
||||||
view_count_str = self._html_search_regex(
|
view_count = str_to_int(post_view['externalView'])
|
||||||
r'<p><b>([0-9][0-9,]*)</b> views</p>', webpage, 'view count',
|
thumbnail = post_view.get('thumbnail_700w') or post_view.get('ogImageUrl') or post_view.get('thumbnail_300w')
|
||||||
fatal=False)
|
|
||||||
view_count = (
|
|
||||||
None if view_count_str is None
|
|
||||||
else int(view_count_str.replace(',', '')))
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'_type': 'url_transparent',
|
'_type': 'url_transparent',
|
||||||
'url': youtube_id,
|
'url': youtube_id,
|
||||||
'ie_key': 'Youtube',
|
'ie_key': 'Youtube',
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': self._og_search_title(webpage),
|
'display_id': display_id,
|
||||||
|
'title': title,
|
||||||
'description': description,
|
'description': description,
|
||||||
'view_count': view_count,
|
'view_count': view_count,
|
||||||
'thumbnail': self._og_search_thumbnail(webpage),
|
'thumbnail': thumbnail,
|
||||||
}
|
}
|
||||||
|
106
youtube_dl/extractor/noco.py
Normal file
106
youtube_dl/extractor/noco.py
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
# encoding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
ExtractorError,
|
||||||
|
unified_strdate,
|
||||||
|
compat_str,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class NocoIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'http://(?:(?:www\.)?noco\.tv/emission/|player\.noco\.tv/\?idvideo=)(?P<id>\d+)'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://noco.tv/emission/11538/nolife/ami-ami-idol-hello-france/',
|
||||||
|
'md5': '0a993f0058ddbcd902630b2047ef710e',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '11538',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Ami Ami Idol - Hello! France',
|
||||||
|
'description': 'md5:4eaab46ab68fa4197a317a88a53d3b86',
|
||||||
|
'upload_date': '20140412',
|
||||||
|
'uploader': 'Nolife',
|
||||||
|
'uploader_id': 'NOL',
|
||||||
|
'duration': 2851.2,
|
||||||
|
},
|
||||||
|
'skip': 'Requires noco account',
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
medias = self._download_json(
|
||||||
|
'http://api.noco.tv/1.0/video/medias/%s' % video_id, video_id, 'Downloading video JSON')
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
|
||||||
|
for fmt in medias['fr']['video_list']['default']['quality_list']:
|
||||||
|
format_id = fmt['quality_key']
|
||||||
|
|
||||||
|
file = self._download_json(
|
||||||
|
'http://api.noco.tv/1.0/video/file/%s/fr/%s' % (format_id.lower(), video_id),
|
||||||
|
video_id, 'Downloading %s video JSON' % format_id)
|
||||||
|
|
||||||
|
file_url = file['file']
|
||||||
|
if not file_url:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if file_url == 'forbidden':
|
||||||
|
raise ExtractorError(
|
||||||
|
'%s returned error: %s - %s' % (
|
||||||
|
self.IE_NAME, file['popmessage']['title'], file['popmessage']['message']),
|
||||||
|
expected=True)
|
||||||
|
|
||||||
|
formats.append({
|
||||||
|
'url': file_url,
|
||||||
|
'format_id': format_id,
|
||||||
|
'width': fmt['res_width'],
|
||||||
|
'height': fmt['res_lines'],
|
||||||
|
'abr': fmt['audiobitrate'],
|
||||||
|
'vbr': fmt['videobitrate'],
|
||||||
|
'filesize': fmt['filesize'],
|
||||||
|
'format_note': fmt['quality_name'],
|
||||||
|
'preference': fmt['priority'],
|
||||||
|
})
|
||||||
|
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
show = self._download_json(
|
||||||
|
'http://api.noco.tv/1.0/shows/show/%s' % video_id, video_id, 'Downloading show JSON')[0]
|
||||||
|
|
||||||
|
upload_date = unified_strdate(show['indexed'])
|
||||||
|
uploader = show['partner_name']
|
||||||
|
uploader_id = show['partner_key']
|
||||||
|
duration = show['duration_ms'] / 1000.0
|
||||||
|
thumbnail = show['screenshot']
|
||||||
|
|
||||||
|
episode = show.get('show_TT') or show.get('show_OT')
|
||||||
|
family = show.get('family_TT') or show.get('family_OT')
|
||||||
|
episode_number = show.get('episode_number')
|
||||||
|
|
||||||
|
title = ''
|
||||||
|
if family:
|
||||||
|
title += family
|
||||||
|
if episode_number:
|
||||||
|
title += ' #' + compat_str(episode_number)
|
||||||
|
if episode:
|
||||||
|
title += ' - ' + episode
|
||||||
|
|
||||||
|
description = show.get('show_resume') or show.get('family_resume')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'upload_date': upload_date,
|
||||||
|
'uploader': uploader,
|
||||||
|
'uploader_id': uploader_id,
|
||||||
|
'duration': duration,
|
||||||
|
'formats': formats,
|
||||||
|
}
|
@ -13,7 +13,8 @@ class NovaMovIE(InfoExtractor):
|
|||||||
IE_NAME = 'novamov'
|
IE_NAME = 'novamov'
|
||||||
IE_DESC = 'NovaMov'
|
IE_DESC = 'NovaMov'
|
||||||
|
|
||||||
_VALID_URL = r'http://(?:(?:www\.)?%(host)s/video/|(?:(?:embed|www)\.)%(host)s/embed\.php\?(?:.*?&)?v=)(?P<videoid>[a-z\d]{13})' % {'host': 'novamov\.com'}
|
_VALID_URL_TEMPLATE = r'http://(?:(?:www\.)?%(host)s/(?:file|video)/|(?:(?:embed|www)\.)%(host)s/embed\.php\?(?:.*?&)?v=)(?P<id>[a-z\d]{13})'
|
||||||
|
_VALID_URL = _VALID_URL_TEMPLATE % {'host': 'novamov\.com'}
|
||||||
|
|
||||||
_HOST = 'www.novamov.com'
|
_HOST = 'www.novamov.com'
|
||||||
|
|
||||||
@ -36,18 +37,17 @@ class NovaMovIE(InfoExtractor):
|
|||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
video_id = mobj.group('videoid')
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
page = self._download_webpage(
|
page = self._download_webpage(
|
||||||
'http://%s/video/%s' % (self._HOST, video_id), video_id, 'Downloading video page')
|
'http://%s/video/%s' % (self._HOST, video_id), video_id, 'Downloading video page')
|
||||||
|
|
||||||
if re.search(self._FILE_DELETED_REGEX, page) is not None:
|
if re.search(self._FILE_DELETED_REGEX, page) is not None:
|
||||||
raise ExtractorError(u'Video %s does not exist' % video_id, expected=True)
|
raise ExtractorError('Video %s does not exist' % video_id, expected=True)
|
||||||
|
|
||||||
filekey = self._search_regex(self._FILEKEY_REGEX, page, 'filekey')
|
filekey = self._search_regex(self._FILEKEY_REGEX, page, 'filekey')
|
||||||
|
|
||||||
title = self._html_search_regex(self._TITLE_REGEX, page, 'title', fatal=False)
|
title = self._html_search_regex(self._TITLE_REGEX, page, 'title', fatal=False)
|
||||||
|
|
||||||
description = self._html_search_regex(self._DESCRIPTION_REGEX, page, 'description', default='', fatal=False)
|
description = self._html_search_regex(self._DESCRIPTION_REGEX, page, 'description', default='', fatal=False)
|
||||||
|
|
||||||
api_response = self._download_webpage(
|
api_response = self._download_webpage(
|
||||||
|
@ -4,9 +4,7 @@ import re
|
|||||||
|
|
||||||
from .brightcove import BrightcoveIE
|
from .brightcove import BrightcoveIE
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import ExtractorError
|
||||||
ExtractorError,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class NownessIE(InfoExtractor):
|
class NownessIE(InfoExtractor):
|
||||||
@ -14,9 +12,10 @@ class NownessIE(InfoExtractor):
|
|||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.nowness.com/day/2013/6/27/3131/candor--the-art-of-gesticulation',
|
'url': 'http://www.nowness.com/day/2013/6/27/3131/candor--the-art-of-gesticulation',
|
||||||
'file': '2520295746001.mp4',
|
'md5': '068bc0202558c2e391924cb8cc470676',
|
||||||
'md5': '0ece2f70a7bd252c7b00f3070182d418',
|
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'id': '2520295746001',
|
||||||
|
'ext': 'mp4',
|
||||||
'description': 'Candor: The Art of Gesticulation',
|
'description': 'Candor: The Art of Gesticulation',
|
||||||
'uploader': 'Nowness',
|
'uploader': 'Nowness',
|
||||||
'title': 'Candor: The Art of Gesticulation',
|
'title': 'Candor: The Art of Gesticulation',
|
||||||
|
@ -7,7 +7,7 @@ class NowVideoIE(NovaMovIE):
|
|||||||
IE_NAME = 'nowvideo'
|
IE_NAME = 'nowvideo'
|
||||||
IE_DESC = 'NowVideo'
|
IE_DESC = 'NowVideo'
|
||||||
|
|
||||||
_VALID_URL = r'http://(?:(?:www\.)?%(host)s/video/|(?:(?:embed|www)\.)%(host)s/embed\.php\?(?:.*?&)?v=)(?P<videoid>[a-z\d]{13})' % {'host': 'nowvideo\.(?:ch|sx|eu)'}
|
_VALID_URL = NovaMovIE._VALID_URL_TEMPLATE % {'host': 'nowvideo\.(?:ch|sx|eu|at|ag|co)'}
|
||||||
|
|
||||||
_HOST = 'www.nowvideo.ch'
|
_HOST = 'www.nowvideo.ch'
|
||||||
|
|
||||||
|
145
youtube_dl/extractor/nrk.py
Normal file
145
youtube_dl/extractor/nrk.py
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
# encoding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
ExtractorError,
|
||||||
|
int_or_none,
|
||||||
|
unified_strdate,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class NRKIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'http://(?:www\.)?nrk\.no/(?:video|lyd)/[^/]+/(?P<id>[\dA-F]{16})'
|
||||||
|
|
||||||
|
_TESTS = [
|
||||||
|
{
|
||||||
|
'url': 'http://www.nrk.no/video/dompap_og_andre_fugler_i_piip_show/D0FA54B5C8B6CE59/emne/piipshow/',
|
||||||
|
'md5': 'a6eac35052f3b242bb6bb7f43aed5886',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '150533',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Dompap og andre fugler i Piip-Show',
|
||||||
|
'description': 'md5:d9261ba34c43b61c812cb6b0269a5c8f'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://www.nrk.no/lyd/lyd_av_oppleser_for_blinde/AEFDDD5473BA0198/',
|
||||||
|
'md5': '3471f2a51718195164e88f46bf427668',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '154915',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Slik høres internett ut når du er blind',
|
||||||
|
'description': 'md5:a621f5cc1bd75c8d5104cb048c6b8568',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
page = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
video_id = self._html_search_regex(r'<div class="nrk-video" data-nrk-id="(\d+)">', page, 'video id')
|
||||||
|
|
||||||
|
data = self._download_json(
|
||||||
|
'http://v7.psapi.nrk.no/mediaelement/%s' % video_id, video_id, 'Downloading media JSON')
|
||||||
|
|
||||||
|
if data['usageRights']['isGeoBlocked']:
|
||||||
|
raise ExtractorError('NRK har ikke rettig-heter til å vise dette programmet utenfor Norge', expected=True)
|
||||||
|
|
||||||
|
video_url = data['mediaUrl'] + '?hdcore=3.1.1&plugin=aasp-3.1.1.69.124'
|
||||||
|
|
||||||
|
images = data.get('images')
|
||||||
|
if images:
|
||||||
|
thumbnails = images['webImages']
|
||||||
|
thumbnails.sort(key=lambda image: image['pixelWidth'])
|
||||||
|
thumbnail = thumbnails[-1]['imageUrl']
|
||||||
|
else:
|
||||||
|
thumbnail = None
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'url': video_url,
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': data['title'],
|
||||||
|
'description': data['description'],
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class NRKTVIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'http://tv\.nrk(?:super)?\.no/(?:serie/[^/]+|program)/(?P<id>[a-z]{4}\d{8})'
|
||||||
|
|
||||||
|
_TESTS = [
|
||||||
|
{
|
||||||
|
'url': 'http://tv.nrk.no/serie/20-spoersmaal-tv/muhh48000314/23-05-2014',
|
||||||
|
'md5': '7b96112fbae1faf09a6f9ae1aff6cb84',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'muhh48000314',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': '20 spørsmål',
|
||||||
|
'description': 'md5:bdea103bc35494c143c6a9acdd84887a',
|
||||||
|
'upload_date': '20140523',
|
||||||
|
'duration': 1741.52,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://tv.nrk.no/program/mdfp15000514',
|
||||||
|
'md5': '383650ece2b25ecec996ad7b5bb2a384',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'mdfp15000514',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Kunnskapskanalen: Grunnlovsjubiléet - Stor ståhei for ingenting',
|
||||||
|
'description': 'md5:654c12511f035aed1e42bdf5db3b206a',
|
||||||
|
'upload_date': '20140524',
|
||||||
|
'duration': 4605.0,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
page = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
title = self._html_search_meta('title', page, 'title')
|
||||||
|
description = self._html_search_meta('description', page, 'description')
|
||||||
|
thumbnail = self._html_search_regex(r'data-posterimage="([^"]+)"', page, 'thumbnail', fatal=False)
|
||||||
|
upload_date = unified_strdate(self._html_search_meta('rightsfrom', page, 'upload date', fatal=False))
|
||||||
|
duration = self._html_search_regex(r'data-duration="([^"]+)"', page, 'duration', fatal=False)
|
||||||
|
if duration:
|
||||||
|
duration = float(duration)
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
|
||||||
|
f4m_url = re.search(r'data-media="([^"]+)"', page)
|
||||||
|
if f4m_url:
|
||||||
|
formats.append({
|
||||||
|
'url': f4m_url.group(1) + '?hdcore=3.1.1&plugin=aasp-3.1.1.69.124',
|
||||||
|
'format_id': 'f4m',
|
||||||
|
'ext': 'flv',
|
||||||
|
})
|
||||||
|
|
||||||
|
m3u8_url = re.search(r'data-hls-media="([^"]+)"', page)
|
||||||
|
if m3u8_url:
|
||||||
|
formats.append({
|
||||||
|
'url': m3u8_url.group(1),
|
||||||
|
'format_id': 'm3u8',
|
||||||
|
})
|
||||||
|
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'upload_date': upload_date,
|
||||||
|
'duration': duration,
|
||||||
|
'formats': formats,
|
||||||
|
}
|
149
youtube_dl/extractor/ntv.py
Normal file
149
youtube_dl/extractor/ntv.py
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
# encoding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
ExtractorError,
|
||||||
|
unescapeHTML
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class NTVIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'http://(?:www\.)?ntv\.ru/(?P<id>.+)'
|
||||||
|
|
||||||
|
_TESTS = [
|
||||||
|
{
|
||||||
|
'url': 'http://www.ntv.ru/novosti/863142/',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '746000',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Командующий Черноморским флотом провел переговоры в штабе ВМС Украины',
|
||||||
|
'description': 'Командующий Черноморским флотом провел переговоры в штабе ВМС Украины',
|
||||||
|
'duration': 136,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# rtmp download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://www.ntv.ru/video/novosti/750370/',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '750370',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Родные пассажиров пропавшего Boeing не верят в трагический исход',
|
||||||
|
'description': 'Родные пассажиров пропавшего Boeing не верят в трагический исход',
|
||||||
|
'duration': 172,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# rtmp download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://www.ntv.ru/peredacha/segodnya/m23700/o232416',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '747480',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': '«Сегодня». 21 марта 2014 года. 16:00 ',
|
||||||
|
'description': '«Сегодня». 21 марта 2014 года. 16:00 ',
|
||||||
|
'duration': 1496,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# rtmp download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://www.ntv.ru/kino/Koma_film',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '758100',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Остросюжетный фильм «Кома»',
|
||||||
|
'description': 'Остросюжетный фильм «Кома»',
|
||||||
|
'duration': 5592,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# rtmp download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://www.ntv.ru/serial/Delo_vrachey/m31760/o233916/',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '751482',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': '«Дело врачей»: «Деревце жизни»',
|
||||||
|
'description': '«Дело врачей»: «Деревце жизни»',
|
||||||
|
'duration': 2590,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# rtmp download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
_VIDEO_ID_REGEXES = [
|
||||||
|
r'<meta property="og:url" content="http://www\.ntv\.ru/video/(\d+)',
|
||||||
|
r'<video embed=[^>]+><id>(\d+)</id>',
|
||||||
|
r'<video restriction[^>]+><key>(\d+)</key>',
|
||||||
|
]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
page = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
video_id = self._html_search_regex(self._VIDEO_ID_REGEXES, page, 'video id')
|
||||||
|
|
||||||
|
player = self._download_xml('http://www.ntv.ru/vi%s/' % video_id, video_id, 'Downloading video XML')
|
||||||
|
title = unescapeHTML(player.find('./data/title').text)
|
||||||
|
description = unescapeHTML(player.find('./data/description').text)
|
||||||
|
|
||||||
|
video = player.find('./data/video')
|
||||||
|
video_id = video.find('./id').text
|
||||||
|
thumbnail = video.find('./splash').text
|
||||||
|
duration = int(video.find('./totaltime').text)
|
||||||
|
view_count = int(video.find('./views').text)
|
||||||
|
puid22 = video.find('./puid22').text
|
||||||
|
|
||||||
|
apps = {
|
||||||
|
'4': 'video1',
|
||||||
|
'7': 'video2',
|
||||||
|
}
|
||||||
|
|
||||||
|
app = apps.get(puid22, apps['4'])
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
for format_id in ['', 'hi', 'webm']:
|
||||||
|
file = video.find('./%sfile' % format_id)
|
||||||
|
if file is None:
|
||||||
|
continue
|
||||||
|
size = video.find('./%ssize' % format_id)
|
||||||
|
formats.append({
|
||||||
|
'url': 'rtmp://media.ntv.ru/%s' % app,
|
||||||
|
'app': app,
|
||||||
|
'play_path': file.text,
|
||||||
|
'rtmp_conn': 'B:1',
|
||||||
|
'player_url': 'http://www.ntv.ru/swf/vps1.swf?update=20131128',
|
||||||
|
'page_url': 'http://www.ntv.ru',
|
||||||
|
'flash_ver': 'LNX 11,2,202,341',
|
||||||
|
'rtmp_live': True,
|
||||||
|
'ext': 'flv',
|
||||||
|
'filesize': int(size.text),
|
||||||
|
})
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'duration': duration,
|
||||||
|
'view_count': view_count,
|
||||||
|
'formats': formats,
|
||||||
|
}
|
48
youtube_dl/extractor/nuvid.py
Normal file
48
youtube_dl/extractor/nuvid.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
|
class NuvidIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'^https?://(?:www|m)\.nuvid\.com/video/(?P<id>[0-9]+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://m.nuvid.com/video/1310741/',
|
||||||
|
'md5': 'eab207b7ac4fccfb4e23c86201f11277',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '1310741',
|
||||||
|
'ext': 'mp4',
|
||||||
|
"title": "Horny babes show their awesome bodeis and",
|
||||||
|
"age_limit": 18,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
murl = url.replace('://www.', '://m.')
|
||||||
|
webpage = self._download_webpage(murl, video_id)
|
||||||
|
|
||||||
|
title = self._html_search_regex(
|
||||||
|
r'<div class="title">\s+<h2[^>]*>([^<]+)</h2>',
|
||||||
|
webpage, 'title').strip()
|
||||||
|
|
||||||
|
url_end = self._html_search_regex(
|
||||||
|
r'href="(/[^"]+)"[^>]*data-link_type="mp4"',
|
||||||
|
webpage, 'video_url')
|
||||||
|
video_url = 'http://m.nuvid.com' + url_end
|
||||||
|
|
||||||
|
thumbnail = self._html_search_regex(
|
||||||
|
r'href="(/thumbs/[^"]+)"[^>]*data-link_type="thumbs"',
|
||||||
|
webpage, 'thumbnail URL', fatal=False)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'url': video_url,
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': title,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'age_limit': 18,
|
||||||
|
}
|
77
youtube_dl/extractor/nytimes.py
Normal file
77
youtube_dl/extractor/nytimes.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import parse_iso8601
|
||||||
|
|
||||||
|
|
||||||
|
class NYTimesIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?nytimes\.com/video/(?:[^/]+/)+(?P<id>\d+)'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.nytimes.com/video/opinion/100000002847155/verbatim-what-is-a-photocopier.html?playlistId=100000001150263',
|
||||||
|
'md5': '18a525a510f942ada2720db5f31644c0',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '100000002847155',
|
||||||
|
'ext': 'mov',
|
||||||
|
'title': 'Verbatim: What Is a Photocopier?',
|
||||||
|
'description': 'md5:93603dada88ddbda9395632fdc5da260',
|
||||||
|
'timestamp': 1398631707,
|
||||||
|
'upload_date': '20140427',
|
||||||
|
'uploader': 'Brett Weiner',
|
||||||
|
'duration': 419,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
|
video_data = self._download_json(
|
||||||
|
'http://www.nytimes.com/svc/video/api/v2/video/%s' % video_id, video_id, 'Downloading video JSON')
|
||||||
|
|
||||||
|
title = video_data['headline']
|
||||||
|
description = video_data['summary']
|
||||||
|
duration = video_data['duration'] / 1000.0
|
||||||
|
|
||||||
|
uploader = video_data['byline']
|
||||||
|
timestamp = parse_iso8601(video_data['publication_date'][:-8])
|
||||||
|
|
||||||
|
def get_file_size(file_size):
|
||||||
|
if isinstance(file_size, int):
|
||||||
|
return file_size
|
||||||
|
elif isinstance(file_size, dict):
|
||||||
|
return int(file_size.get('value', 0))
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
formats = [
|
||||||
|
{
|
||||||
|
'url': video['url'],
|
||||||
|
'format_id': video['type'],
|
||||||
|
'vcodec': video['video_codec'],
|
||||||
|
'width': video['width'],
|
||||||
|
'height': video['height'],
|
||||||
|
'filesize': get_file_size(video['fileSize']),
|
||||||
|
} for video in video_data['renditions']
|
||||||
|
]
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
thumbnails = [
|
||||||
|
{
|
||||||
|
'url': 'http://www.nytimes.com/%s' % image['url'],
|
||||||
|
'resolution': '%dx%d' % (image['width'], image['height']),
|
||||||
|
} for image in video_data['images']
|
||||||
|
]
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'timestamp': timestamp,
|
||||||
|
'uploader': uploader,
|
||||||
|
'duration': duration,
|
||||||
|
'formats': formats,
|
||||||
|
'thumbnails': thumbnails,
|
||||||
|
}
|
40
youtube_dl/extractor/oe1.py
Normal file
40
youtube_dl/extractor/oe1.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import calendar
|
||||||
|
import datetime
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
# audios on oe1.orf.at are only available for 7 days, so we can't
|
||||||
|
# add tests.
|
||||||
|
|
||||||
|
|
||||||
|
class OE1IE(InfoExtractor):
|
||||||
|
IE_DESC = 'oe1.orf.at'
|
||||||
|
_VALID_URL = r'http://oe1\.orf\.at/programm/(?P<id>[0-9]+)'
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
show_id = mobj.group('id')
|
||||||
|
|
||||||
|
data = self._download_json(
|
||||||
|
'http://oe1.orf.at/programm/%s/konsole' % show_id,
|
||||||
|
show_id
|
||||||
|
)
|
||||||
|
|
||||||
|
timestamp = datetime.datetime.strptime('%s %s' % (
|
||||||
|
data['item']['day_label'],
|
||||||
|
data['item']['time']
|
||||||
|
), '%d.%m.%Y %H:%M')
|
||||||
|
unix_timestamp = calendar.timegm(timestamp.utctimetuple())
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': show_id,
|
||||||
|
'title': data['item']['title'],
|
||||||
|
'url': data['item']['url_stream'],
|
||||||
|
'ext': 'mp3',
|
||||||
|
'description': data['item'].get('info'),
|
||||||
|
'timestamp': unix_timestamp
|
||||||
|
}
|
@ -1,10 +1,10 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import datetime
|
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from ..utils import compat_urllib_parse
|
||||||
|
|
||||||
|
|
||||||
class PhotobucketIE(InfoExtractor):
|
class PhotobucketIE(InfoExtractor):
|
||||||
@ -14,6 +14,7 @@ class PhotobucketIE(InfoExtractor):
|
|||||||
'file': 'zpsc0c3b9fa.mp4',
|
'file': 'zpsc0c3b9fa.mp4',
|
||||||
'md5': '7dabfb92b0a31f6c16cebc0f8e60ff99',
|
'md5': '7dabfb92b0a31f6c16cebc0f8e60ff99',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
'timestamp': 1367669341,
|
||||||
'upload_date': '20130504',
|
'upload_date': '20130504',
|
||||||
'uploader': 'rachaneronas',
|
'uploader': 'rachaneronas',
|
||||||
'title': 'Tired of Link Building? Try BacklinkMyDomain.com!',
|
'title': 'Tired of Link Building? Try BacklinkMyDomain.com!',
|
||||||
@ -32,11 +33,12 @@ class PhotobucketIE(InfoExtractor):
|
|||||||
info_json = self._search_regex(r'Pb\.Data\.Shared\.put\(Pb\.Data\.Shared\.MEDIA, (.*?)\);',
|
info_json = self._search_regex(r'Pb\.Data\.Shared\.put\(Pb\.Data\.Shared\.MEDIA, (.*?)\);',
|
||||||
webpage, 'info json')
|
webpage, 'info json')
|
||||||
info = json.loads(info_json)
|
info = json.loads(info_json)
|
||||||
|
url = compat_urllib_parse.unquote(self._html_search_regex(r'file=(.+\.mp4)', info['linkcodes']['html'], 'url'))
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'url': info['downloadUrl'],
|
'url': url,
|
||||||
'uploader': info['username'],
|
'uploader': info['username'],
|
||||||
'upload_date': datetime.date.fromtimestamp(info['creationDate']).strftime('%Y%m%d'),
|
'timestamp': info['creationDate'],
|
||||||
'title': info['title'],
|
'title': info['title'],
|
||||||
'ext': video_extension,
|
'ext': video_extension,
|
||||||
'thumbnail': info['thumbUrl'],
|
'thumbnail': info['thumbUrl'],
|
||||||
|
@ -6,22 +6,36 @@ import re
|
|||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import int_or_none
|
from ..utils import int_or_none
|
||||||
|
|
||||||
|
|
||||||
class PodomaticIE(InfoExtractor):
|
class PodomaticIE(InfoExtractor):
|
||||||
IE_NAME = 'podomatic'
|
IE_NAME = 'podomatic'
|
||||||
_VALID_URL = r'^(?P<proto>https?)://(?P<channel>[^.]+)\.podomatic\.com/entry/(?P<id>[^?]+)'
|
_VALID_URL = r'^(?P<proto>https?)://(?P<channel>[^.]+)\.podomatic\.com/entry/(?P<id>[^?]+)'
|
||||||
|
|
||||||
_TEST = {
|
_TESTS = [
|
||||||
"url": "http://scienceteachingtips.podomatic.com/entry/2009-01-02T16_03_35-08_00",
|
{
|
||||||
"file": "2009-01-02T16_03_35-08_00.mp3",
|
'url': 'http://scienceteachingtips.podomatic.com/entry/2009-01-02T16_03_35-08_00',
|
||||||
"md5": "84bb855fcf3429e6bf72460e1eed782d",
|
'md5': '84bb855fcf3429e6bf72460e1eed782d',
|
||||||
"info_dict": {
|
'info_dict': {
|
||||||
"uploader": "Science Teaching Tips",
|
'id': '2009-01-02T16_03_35-08_00',
|
||||||
"uploader_id": "scienceteachingtips",
|
'ext': 'mp3',
|
||||||
"title": "64. When the Moon Hits Your Eye",
|
'uploader': 'Science Teaching Tips',
|
||||||
"duration": 446,
|
'uploader_id': 'scienceteachingtips',
|
||||||
}
|
'title': '64. When the Moon Hits Your Eye',
|
||||||
}
|
'duration': 446,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://ostbahnhof.podomatic.com/entry/2013-11-15T16_31_21-08_00',
|
||||||
|
'md5': 'd2cf443931b6148e27638650e2638297',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '2013-11-15T16_31_21-08_00',
|
||||||
|
'ext': 'mp3',
|
||||||
|
'uploader': 'Ostbahnhof / Techno Mix',
|
||||||
|
'uploader_id': 'ostbahnhof',
|
||||||
|
'title': 'Einunddreizig',
|
||||||
|
'duration': 3799,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
@ -32,10 +46,12 @@ class PodomaticIE(InfoExtractor):
|
|||||||
'?permalink=true&rtmp=0') %
|
'?permalink=true&rtmp=0') %
|
||||||
(mobj.group('proto'), channel, video_id))
|
(mobj.group('proto'), channel, video_id))
|
||||||
data_json = self._download_webpage(
|
data_json = self._download_webpage(
|
||||||
json_url, video_id, note=u'Downloading video info')
|
json_url, video_id, 'Downloading video info')
|
||||||
data = json.loads(data_json)
|
data = json.loads(data_json)
|
||||||
|
|
||||||
video_url = data['downloadLink']
|
video_url = data['downloadLink']
|
||||||
|
if not video_url:
|
||||||
|
video_url = '%s/%s' % (data['streamer'].replace('rtmp', 'http'), data['mediaLocation'])
|
||||||
uploader = data['podcast']
|
uploader = data['podcast']
|
||||||
title = data['title']
|
title = data['title']
|
||||||
thumbnail = data['imageLocation']
|
thumbnail = data['imageLocation']
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user