Compare commits
570 Commits
2017.06.23
...
2017.12.02
Author | SHA1 | Date | |
---|---|---|---|
0d56eddc59 | |||
e25ee72657 | |||
78593e294c | |||
593f2f7989 | |||
603fc4e0ea | |||
41bf647e89 | |||
fea92aa65d | |||
0981585bef | |||
f5ac68d88f | |||
1663b32946 | |||
5ea765fb72 | |||
fb61b57d0f | |||
07cf18b9c5 | |||
5f699251e9 | |||
a3474aa59e | |||
115afb77ec | |||
53f024e7c5 | |||
ffe6979ef9 | |||
dafb4c6647 | |||
82a62de192 | |||
f58a506044 | |||
5ddeb7702a | |||
6c07f0b288 | |||
e94d1adc36 | |||
d08dcd2dbd | |||
7512aa986f | |||
93f3f10cdc | |||
87dac57cf6 | |||
b485d5d6bf | |||
a238a868ba | |||
c0f647a179 | |||
6ff27b8d5a | |||
9ef909f2b2 | |||
8cfbcfab9a | |||
b7785cf156 | |||
9105523818 | |||
dbb25af657 | |||
fe4bfe36e1 | |||
6f5c598a28 | |||
cd9ff4ec5b | |||
c6c6a64aa5 | |||
e0a8686f48 | |||
6049176471 | |||
805f5bf759 | |||
32ad4f3faf | |||
6899b1d9e8 | |||
939be9adfe | |||
2688664762 | |||
8f63941104 | |||
a9efdf3d4a | |||
f610dbb05f | |||
38db52adf3 | |||
3192d4bc7a | |||
9cbd4dda10 | |||
08e45b39e7 | |||
fae0eb42ec | |||
ea2295842f | |||
a2b6aba8de | |||
ff31f2d5c3 | |||
0987f2ddb2 | |||
5871ebac47 | |||
05dee6c520 | |||
27adc9ec65 | |||
388beb86e0 | |||
d4e31b72b9 | |||
5fc12b9549 | |||
af85ce29c6 | |||
e4d9586562 | |||
79d1f8ed68 | |||
a5203935d6 | |||
59d2e6d04f | |||
a9543e37c8 | |||
61fb07e156 | |||
4222346fb2 | |||
cc6a960e13 | |||
f34b841b51 | |||
e0998333fa | |||
909191de91 | |||
477c97f86b | |||
6e71bbf4ab | |||
181e381fda | |||
187ee66c94 | |||
48107c198b | |||
cd670befc4 | |||
44cca168cc | |||
b0f4331002 | |||
044eeb1455 | |||
8fe767e072 | |||
6d0630d880 | |||
518d357b46 | |||
514e8aefd4 | |||
9211e3319e | |||
056653bbb1 | |||
c3206d02e9 | |||
eb4b5818e2 | |||
47a8587915 | |||
8e01f3ca81 | |||
f2332f18e6 | |||
7c1f419341 | |||
30e6161799 | |||
dc24a7d4a2 | |||
d673ab6562 | |||
b8c6ffc518 | |||
7913e0fca7 | |||
cdd1ce92c4 | |||
55c727a547 | |||
36e2d3ca43 | |||
f7a5038305 | |||
9ff6273cae | |||
f03ee0b372 | |||
cf6bda312b | |||
3ebbd9991e | |||
21ce434051 | |||
5c0e5bc4df | |||
9a9de2d7b2 | |||
424505df76 | |||
fa3f0fd856 | |||
c9dcd4b0c5 | |||
fc5c47d13c | |||
a26a3c6d34 | |||
382fa456ea | |||
e1d168e592 | |||
ca1c9f26fa | |||
6f3b4a98c9 | |||
fa4bc6e712 | |||
6b9cbd023f | |||
c233003afe | |||
83fcf19e2d | |||
acc4ea6237 | |||
8cc1840ccb | |||
a9ee4f6e49 | |||
aaab8c5e71 | |||
7e721e35da | |||
bd7e1406b3 | |||
74c42d9ec3 | |||
5efaf43c93 | |||
4827270526 | |||
ee093a0ea0 | |||
9bb2c7673e | |||
715534083d | |||
ee88c1cbc6 | |||
57eb45b111 | |||
b21ab85088 | |||
210a2720bc | |||
685e87b61f | |||
c9bd503e7d | |||
94a530c6cb | |||
e650659b94 | |||
2637fadc38 | |||
50d808f5c9 | |||
7a64c33aee | |||
b0def2c297 | |||
81ce479f4d | |||
414e709405 | |||
645ed3e7c9 | |||
c0bddd6d65 | |||
1baba7f4a8 | |||
344d1a6794 | |||
76581082f6 | |||
2f0eb0a68a | |||
7fee3377dc | |||
ff3f1a62f0 | |||
694b61545c | |||
af0f74288d | |||
9e38dbb19c | |||
782195a9d4 | |||
26bae2d965 | |||
5fe75f976f | |||
4fe4bda287 | |||
cdab1df912 | |||
dfc80bdd2e | |||
04af3aca04 | |||
d0f2d64114 | |||
01c742ecd0 | |||
9e71f88105 | |||
ae5af89079 | |||
197224b7a4 | |||
8992331621 | |||
b0dde6686c | |||
a22ccac1f0 | |||
8b561bfc9d | |||
8e751a185c | |||
3fc8f5b7c2 | |||
665f42d8c1 | |||
e952847541 | |||
b1a7bf44b9 | |||
2e2a8e97d5 | |||
ac93c09ab2 | |||
cd6fc19ed7 | |||
86a15ed64b | |||
7e85e8729f | |||
6be08ce602 | |||
cf5f6ed5be | |||
6b46285e85 | |||
6e736d86e7 | |||
c110944fa2 | |||
9524dca3ac | |||
3e4cedf9e8 | |||
bfd484ccff | |||
b7e14f06a4 | |||
d2ae7e24e5 | |||
544ffb7790 | |||
117589dfa2 | |||
839728f5bf | |||
fcdd37d053 | |||
1dd126180e | |||
4e599194d6 | |||
c5b7014a9c | |||
c8da40d834 | |||
b69ca0ccfc | |||
2c53bd51c6 | |||
3836b02ce8 | |||
fa3fdeb41f | |||
eb9a15be60 | |||
3600fd591d | |||
63d990d285 | |||
b14b2283a0 | |||
02d01e15f1 | |||
db96252831 | |||
8b389f7e3c | |||
9fc41bcb6b | |||
10cab6613f | |||
4d182955a2 | |||
011da618bd | |||
4c54b89e03 | |||
a87d7b4953 | |||
2f3933aa1e | |||
aab20aabfc | |||
16f54d0751 | |||
07d1344c85 | |||
47b5dfb047 | |||
e3440d824a | |||
136507b39a | |||
7f4921b38d | |||
f70ddd4aeb | |||
1c22d7a7f3 | |||
5c1452e8f1 | |||
4bb58fa118 | |||
13de91c9e9 | |||
9ce1ac4046 | |||
095774e591 | |||
2384f5a64e | |||
8c2895305d | |||
8c6919e433 | |||
f6ff52b473 | |||
12ea5c79fb | |||
3b65a6fbf3 | |||
dc76eef092 | |||
8a1a60d173 | |||
4d8c4b46d5 | |||
9c2a17f2ce | |||
4ed2d7b7d1 | |||
8251af63a1 | |||
790d379e4d | |||
3869028ffb | |||
68d43a61b5 | |||
a88d461dff | |||
a4245acef8 | |||
6be44a50ed | |||
b763e1d68c | |||
cbf85239bb | |||
159d304a9f | |||
86e55e317c | |||
c46680fb2a | |||
fad9fc537d | |||
0732a90579 | |||
319fc70676 | |||
e7c3e33456 | |||
757984af90 | |||
2f483758bc | |||
018cc61549 | |||
2709d9fa28 | |||
7dacceae75 | |||
43df248f10 | |||
f12a6e88b2 | |||
806498cf2f | |||
b98339b54b | |||
bf6ec2fea9 | |||
c3dd44e085 | |||
c7e327c4d4 | |||
48b813748d | |||
debed8d759 | |||
51aee72d16 | |||
931edb2ada | |||
5113b69124 | |||
66c9fa36c1 | |||
c5c9bf0c12 | |||
880fa66f4f | |||
6348671c4a | |||
efc57145c1 | |||
e9b865267a | |||
bc35f07537 | |||
0b4a8eb3ac | |||
c1c1585b31 | |||
0cbb841ba9 | |||
d7c7100e3d | |||
73602bcd0c | |||
23b2df82c7 | |||
503115540d | |||
64f0e30b93 | |||
a3431e1224 | |||
a2022b0c40 | |||
8681ed7fc8 | |||
8d81f3e36d | |||
7998520933 | |||
5b4bfbfc3b | |||
53647dfd0a | |||
22f65a9efc | |||
c75c384fb6 | |||
1b41da488d | |||
fea82c1780 | |||
3902cdd0e3 | |||
2cfa7cbdd0 | |||
cc0412ef91 | |||
1c9c8de29e | |||
f031b76065 | |||
62c06c593d | |||
ff17be3ac9 | |||
1ed4549942 | |||
dd121cc1ca | |||
a3c3a1e128 | |||
085d9dd9be | |||
151978f38a | |||
c7121fa7b8 | |||
745968bc72 | |||
df235dbba8 | |||
c4bdc68113 | |||
5bae33485c | |||
0830f3e048 | |||
8d7a24aff6 | |||
37d9af306a | |||
e01c3d2ef7 | |||
05915e379a | |||
7b67b60773 | |||
8d9c2a681a | |||
903d4d1625 | |||
8239c6791a | |||
b359e977b9 | |||
305d99f0bd | |||
d3d45e0a45 | |||
381ad4f309 | |||
e2481b9b6e | |||
09747ba766 | |||
f8f18f332f | |||
95f3f7c20a | |||
f5469da9e6 | |||
d14d9d8903 | |||
ea004d34f8 | |||
2738965d98 | |||
4a91910365 | |||
c0892b2b46 | |||
a5ac0c4755 | |||
5551d7714d | |||
5f5c7b92dd | |||
93d0583e34 | |||
5d28169747 | |||
7ddab7742c | |||
bfabd17b33 | |||
12f5304556 | |||
25a6e769a1 | |||
d22b67f356 | |||
a1aa659662 | |||
4850478543 | |||
134d85a7bd | |||
5c037c0d1f | |||
5d1bd3b907 | |||
19ada898dc | |||
da20951a57 | |||
16393d6535 | |||
4f049e4aa8 | |||
475bcb225f | |||
b3c6515365 | |||
eb02940cc7 | |||
4ef9152428 | |||
0c43a481b9 | |||
868f79db41 | |||
70851a95c3 | |||
e74e3b63e3 | |||
ac8491fcca | |||
82889d4ae5 | |||
92a5c41532 | |||
1663bd6e1c | |||
41918eaa5c | |||
6ed99754bb | |||
0e7dfa7d16 | |||
baba5f4d1d | |||
dee04d24a4 | |||
5b3ddadcc3 | |||
5b232f46dc | |||
4bf22f7a10 | |||
15d1e8a23d | |||
ee6a611665 | |||
463e7216c8 | |||
903a183b6a | |||
92740e4241 | |||
fac188c695 | |||
16afce174e | |||
e2b4808fd8 | |||
daaaf5f594 | |||
f172c86dcd | |||
1d5472290f | |||
c983cc3b71 | |||
1141e9104b | |||
8519b88f67 | |||
bbbe1cebfc | |||
f31fd0693b | |||
799802f368 | |||
b3b5870cba | |||
57a38a38c3 | |||
11a6793f80 | |||
1f03fef994 | |||
183062a4ab | |||
feee8d32e4 | |||
8cda78ef72 | |||
9118c9f18a | |||
5c9ea67bc0 | |||
f701827e31 | |||
8b9f50d7cb | |||
0ed4758023 | |||
a0a477b885 | |||
198d4cb40c | |||
ca127ab2c1 | |||
e445850e69 | |||
836ef26486 | |||
c04017519d | |||
2a7a823211 | |||
95908ce453 | |||
cbbe66635f | |||
c5a49ff084 | |||
24e966e8da | |||
9682666bda | |||
f9c48d895b | |||
c99d6890cb | |||
70bfab0e9a | |||
f0e31e32c9 | |||
3150976669 | |||
e3ce912c3d | |||
73095e013f | |||
905d18a7aa | |||
0db492c02a | |||
425f41319a | |||
71dde5eecf | |||
935d6c20c0 | |||
e0f1fb0a27 | |||
0017d9ad6d | |||
327c8364f1 | |||
359aa2fdd1 | |||
f76c02c87b | |||
7d9a1db111 | |||
0396806f67 | |||
dc6520aa3d | |||
c653326a14 | |||
3fcf346ac1 | |||
fa63cf6c23 | |||
85f5a74b6c | |||
d20b1c6725 | |||
bb176df3bb | |||
83d00044c1 | |||
7abed4e06c | |||
13eb526f11 | |||
00d06e3cfc | |||
749ca5eced | |||
3f59b0154a | |||
089b97cfee | |||
decf86044d | |||
94b817edeb | |||
cea931a9e5 | |||
ef78563e9c | |||
961ea474b6 | |||
ea3f20494f | |||
c7604d79e9 | |||
4e826cd9ae | |||
2583c0b54e | |||
7d02dcfaa2 | |||
00dbdfc1f7 | |||
f354d84807 | |||
15da37c7dc | |||
9a0942ad55 | |||
f2bb33a986 | |||
3615bfe1b4 | |||
e8f20ffa03 | |||
9be31e771c | |||
7f176ac477 | |||
2edfd745df | |||
708f6f511e | |||
bb13949197 | |||
c3c94ca4a4 | |||
e3cd1fcdd1 | |||
b71c18b434 | |||
7bf539edcc | |||
65c416dda8 | |||
207acd8465 | |||
71a1db8919 | |||
6e925598d6 | |||
73cf76a93f | |||
256a746d21 | |||
58179eb7d9 | |||
485cb37576 | |||
ed84454d35 | |||
a02682fd13 | |||
0d2f0b0357 | |||
c319d1c483 | |||
d2b9f362fa | |||
4328ddf82b | |||
250b042c7e | |||
665e945246 | |||
5af2fd7fa0 | |||
15237fcd51 | |||
7a57730907 | |||
8b347a389e | |||
a49804816c | |||
eadd313321 | |||
d852c6bc59 | |||
00e5c36315 | |||
8a04ade86b | |||
ab328411d5 | |||
ddeff4be3f | |||
60d4401c5e | |||
dee2ff1d81 | |||
6554708252 | |||
0a2e1b2e30 | |||
babbc04d45 | |||
609ff8ca19 | |||
b6c9fe4162 | |||
4d9ba27bba | |||
50ae3f646e | |||
99a7e76240 | |||
a3a6d01a96 | |||
02d61a65e2 | |||
9b35297be1 | |||
4917478803 | |||
54faac2235 | |||
c69701c6ab | |||
d4f8ce6e91 | |||
b311b0ead2 | |||
72d256c434 | |||
b2ed954fc6 | |||
a919ca0ad6 | |||
88d6b7c2bd | |||
fd1c5fba6b | |||
0646e34c7d | |||
bf2dc9cc6e | |||
f1c051009b | |||
33ffb645a6 | |||
35544690e4 | |||
136503e302 | |||
4a87de72df | |||
a7ce8f16c4 | |||
a5aea53fc8 | |||
0c7a631b61 | |||
fd9ee4de8c | |||
5744cf6c03 | |||
9c48b5a193 | |||
449c665776 | |||
23aec3d623 | |||
27449ad894 | |||
bd65f18153 | |||
73af5cc817 | |||
b5f523ed62 | |||
4f4dd8d797 | |||
4cb18ab1b9 | |||
ac7409eec5 | |||
c89267d31a | |||
5ff1bc0cc1 | |||
7552f96352 | |||
98f9d87381 | |||
fcace2d1ad | |||
40e41780f1 | |||
da57ebaf84 | |||
47e0cef46e |
16
.github/ISSUE_TEMPLATE.md
vendored
16
.github/ISSUE_TEMPLATE.md
vendored
@ -1,16 +1,16 @@
|
|||||||
## Please follow the guide below
|
## Please follow the guide below
|
||||||
|
|
||||||
- You will be asked some questions and requested to provide some information, please read them **carefully** and answer honestly
|
- You will be asked some questions and requested to provide some information, please read them **carefully** and answer honestly
|
||||||
- Put an `x` into all the boxes [ ] relevant to your *issue* (like that [x])
|
- Put an `x` into all the boxes [ ] relevant to your *issue* (like this: `[x]`)
|
||||||
- Use *Preview* tab to see how your issue will actually look like
|
- Use the *Preview* tab to see what your issue will actually look like
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Make sure you are using the *latest* version: run `youtube-dl --version` and ensure your version is *2017.06.23*. If it's not read [this FAQ entry](https://github.com/rg3/youtube-dl/blob/master/README.md#how-do-i-update-youtube-dl) and update. Issues with outdated version will be rejected.
|
### Make sure you are using the *latest* version: run `youtube-dl --version` and ensure your version is *2017.12.02*. If it's not, read [this FAQ entry](https://github.com/rg3/youtube-dl/blob/master/README.md#how-do-i-update-youtube-dl) and update. Issues with outdated version will be rejected.
|
||||||
- [ ] I've **verified** and **I assure** that I'm running youtube-dl **2017.06.23**
|
- [ ] I've **verified** and **I assure** that I'm running youtube-dl **2017.12.02**
|
||||||
|
|
||||||
### Before submitting an *issue* make sure you have:
|
### Before submitting an *issue* make sure you have:
|
||||||
- [ ] At least skimmed through [README](https://github.com/rg3/youtube-dl/blob/master/README.md) and **most notably** [FAQ](https://github.com/rg3/youtube-dl#faq) and [BUGS](https://github.com/rg3/youtube-dl#bugs) sections
|
- [ ] At least skimmed through the [README](https://github.com/rg3/youtube-dl/blob/master/README.md), **most notably** the [FAQ](https://github.com/rg3/youtube-dl#faq) and [BUGS](https://github.com/rg3/youtube-dl#bugs) sections
|
||||||
- [ ] [Searched](https://github.com/rg3/youtube-dl/search?type=Issues) the bugtracker for similar issues including closed ones
|
- [ ] [Searched](https://github.com/rg3/youtube-dl/search?type=Issues) the bugtracker for similar issues including closed ones
|
||||||
|
|
||||||
### What is the purpose of your *issue*?
|
### What is the purpose of your *issue*?
|
||||||
@ -28,14 +28,14 @@
|
|||||||
|
|
||||||
### If the purpose of this *issue* is a *bug report*, *site support request* or you are not completely sure provide the full verbose output as follows:
|
### If the purpose of this *issue* is a *bug report*, *site support request* or you are not completely sure provide the full verbose output as follows:
|
||||||
|
|
||||||
Add `-v` flag to **your command line** you run youtube-dl with, copy the **whole** output and insert it here. It should look similar to one below (replace it with **your** log inserted between triple ```):
|
Add the `-v` flag to **your command line** you run youtube-dl with (`youtube-dl -v <your command line>`), copy the **whole** output and insert it here. It should look similar to one below (replace it with **your** log inserted between triple ```):
|
||||||
|
|
||||||
```
|
```
|
||||||
$ youtube-dl -v <your command line>
|
|
||||||
[debug] System config: []
|
[debug] System config: []
|
||||||
[debug] User config: []
|
[debug] User config: []
|
||||||
[debug] Command-line args: [u'-v', u'http://www.youtube.com/watch?v=BaW_jenozKcj']
|
[debug] Command-line args: [u'-v', u'http://www.youtube.com/watch?v=BaW_jenozKcj']
|
||||||
[debug] Encodings: locale cp1251, fs mbcs, out cp866, pref cp1251
|
[debug] Encodings: locale cp1251, fs mbcs, out cp866, pref cp1251
|
||||||
[debug] youtube-dl version 2017.06.23
|
[debug] youtube-dl version 2017.12.02
|
||||||
[debug] Python version 2.7.11 - Windows-2003Server-5.2.3790-SP2
|
[debug] Python version 2.7.11 - Windows-2003Server-5.2.3790-SP2
|
||||||
[debug] exe versions: ffmpeg N-75573-g1d0487f, ffprobe N-75573-g1d0487f, rtmpdump 2.4
|
[debug] exe versions: ffmpeg N-75573-g1d0487f, ffprobe N-75573-g1d0487f, rtmpdump 2.4
|
||||||
[debug] Proxy map: {}
|
[debug] Proxy map: {}
|
||||||
|
12
.github/ISSUE_TEMPLATE_tmpl.md
vendored
12
.github/ISSUE_TEMPLATE_tmpl.md
vendored
@ -1,16 +1,16 @@
|
|||||||
## Please follow the guide below
|
## Please follow the guide below
|
||||||
|
|
||||||
- You will be asked some questions and requested to provide some information, please read them **carefully** and answer honestly
|
- You will be asked some questions and requested to provide some information, please read them **carefully** and answer honestly
|
||||||
- Put an `x` into all the boxes [ ] relevant to your *issue* (like that [x])
|
- Put an `x` into all the boxes [ ] relevant to your *issue* (like this: `[x]`)
|
||||||
- Use *Preview* tab to see how your issue will actually look like
|
- Use the *Preview* tab to see what your issue will actually look like
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Make sure you are using the *latest* version: run `youtube-dl --version` and ensure your version is *%(version)s*. If it's not read [this FAQ entry](https://github.com/rg3/youtube-dl/blob/master/README.md#how-do-i-update-youtube-dl) and update. Issues with outdated version will be rejected.
|
### Make sure you are using the *latest* version: run `youtube-dl --version` and ensure your version is *%(version)s*. If it's not, read [this FAQ entry](https://github.com/rg3/youtube-dl/blob/master/README.md#how-do-i-update-youtube-dl) and update. Issues with outdated version will be rejected.
|
||||||
- [ ] I've **verified** and **I assure** that I'm running youtube-dl **%(version)s**
|
- [ ] I've **verified** and **I assure** that I'm running youtube-dl **%(version)s**
|
||||||
|
|
||||||
### Before submitting an *issue* make sure you have:
|
### Before submitting an *issue* make sure you have:
|
||||||
- [ ] At least skimmed through [README](https://github.com/rg3/youtube-dl/blob/master/README.md) and **most notably** [FAQ](https://github.com/rg3/youtube-dl#faq) and [BUGS](https://github.com/rg3/youtube-dl#bugs) sections
|
- [ ] At least skimmed through the [README](https://github.com/rg3/youtube-dl/blob/master/README.md), **most notably** the [FAQ](https://github.com/rg3/youtube-dl#faq) and [BUGS](https://github.com/rg3/youtube-dl#bugs) sections
|
||||||
- [ ] [Searched](https://github.com/rg3/youtube-dl/search?type=Issues) the bugtracker for similar issues including closed ones
|
- [ ] [Searched](https://github.com/rg3/youtube-dl/search?type=Issues) the bugtracker for similar issues including closed ones
|
||||||
|
|
||||||
### What is the purpose of your *issue*?
|
### What is the purpose of your *issue*?
|
||||||
@ -28,9 +28,9 @@
|
|||||||
|
|
||||||
### If the purpose of this *issue* is a *bug report*, *site support request* or you are not completely sure provide the full verbose output as follows:
|
### If the purpose of this *issue* is a *bug report*, *site support request* or you are not completely sure provide the full verbose output as follows:
|
||||||
|
|
||||||
Add `-v` flag to **your command line** you run youtube-dl with, copy the **whole** output and insert it here. It should look similar to one below (replace it with **your** log inserted between triple ```):
|
Add the `-v` flag to **your command line** you run youtube-dl with (`youtube-dl -v <your command line>`), copy the **whole** output and insert it here. It should look similar to one below (replace it with **your** log inserted between triple ```):
|
||||||
|
|
||||||
```
|
```
|
||||||
$ youtube-dl -v <your command line>
|
|
||||||
[debug] System config: []
|
[debug] System config: []
|
||||||
[debug] User config: []
|
[debug] User config: []
|
||||||
[debug] Command-line args: [u'-v', u'http://www.youtube.com/watch?v=BaW_jenozKcj']
|
[debug] Command-line args: [u'-v', u'http://www.youtube.com/watch?v=BaW_jenozKcj']
|
||||||
|
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -9,6 +9,7 @@
|
|||||||
### Before submitting a *pull request* make sure you have:
|
### Before submitting a *pull request* make sure you have:
|
||||||
- [ ] At least skimmed through [adding new extractor tutorial](https://github.com/rg3/youtube-dl#adding-support-for-a-new-site) and [youtube-dl coding conventions](https://github.com/rg3/youtube-dl#youtube-dl-coding-conventions) sections
|
- [ ] At least skimmed through [adding new extractor tutorial](https://github.com/rg3/youtube-dl#adding-support-for-a-new-site) and [youtube-dl coding conventions](https://github.com/rg3/youtube-dl#youtube-dl-coding-conventions) sections
|
||||||
- [ ] [Searched](https://github.com/rg3/youtube-dl/search?q=is%3Apr&type=Issues) the bugtracker for similar pull requests
|
- [ ] [Searched](https://github.com/rg3/youtube-dl/search?q=is%3Apr&type=Issues) the bugtracker for similar pull requests
|
||||||
|
- [ ] Checked the code with [flake8](https://pypi.python.org/pypi/flake8)
|
||||||
|
|
||||||
### In order to be accepted and merged into youtube-dl each piece of code must be in public domain or released under [Unlicense](http://unlicense.org/). Check one of the following options:
|
### In order to be accepted and merged into youtube-dl each piece of code must be in public domain or released under [Unlicense](http://unlicense.org/). Check one of the following options:
|
||||||
- [ ] I am the original author of this code and I am willing to release it under [Unlicense](http://unlicense.org/)
|
- [ ] I am the original author of this code and I am willing to release it under [Unlicense](http://unlicense.org/)
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -22,6 +22,7 @@ cover/
|
|||||||
updates_key.pem
|
updates_key.pem
|
||||||
*.egg-info
|
*.egg-info
|
||||||
*.srt
|
*.srt
|
||||||
|
*.ttml
|
||||||
*.sbv
|
*.sbv
|
||||||
*.vtt
|
*.vtt
|
||||||
*.flv
|
*.flv
|
||||||
|
@ -11,12 +11,12 @@ sudo: false
|
|||||||
env:
|
env:
|
||||||
- YTDL_TEST_SET=core
|
- YTDL_TEST_SET=core
|
||||||
- YTDL_TEST_SET=download
|
- YTDL_TEST_SET=download
|
||||||
|
matrix:
|
||||||
|
fast_finish: true
|
||||||
|
allow_failures:
|
||||||
|
- env: YTDL_TEST_SET=download
|
||||||
script: ./devscripts/run_tests.sh
|
script: ./devscripts/run_tests.sh
|
||||||
notifications:
|
notifications:
|
||||||
email:
|
email:
|
||||||
- filippo.valsorda@gmail.com
|
- filippo.valsorda@gmail.com
|
||||||
- yasoob.khld@gmail.com
|
- yasoob.khld@gmail.com
|
||||||
# irc:
|
|
||||||
# channels:
|
|
||||||
# - "irc.freenode.org#youtube-dl"
|
|
||||||
# skip_join: true
|
|
||||||
|
11
AUTHORS
11
AUTHORS
@ -220,3 +220,14 @@ gritstub
|
|||||||
Adam Voss
|
Adam Voss
|
||||||
Mike Fährmann
|
Mike Fährmann
|
||||||
Jan Kundrát
|
Jan Kundrát
|
||||||
|
Giuseppe Fabiano
|
||||||
|
Örn Guðjónsson
|
||||||
|
Parmjit Virk
|
||||||
|
Genki Sky
|
||||||
|
Ľuboš Katrinec
|
||||||
|
Corey Nicholson
|
||||||
|
Ashutosh Chaudhary
|
||||||
|
John Dong
|
||||||
|
Tatsuyuki Ishi
|
||||||
|
Daniel Weber
|
||||||
|
Kay Bouché
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
$ youtube-dl -v <your command line>
|
$ youtube-dl -v <your command line>
|
||||||
[debug] System config: []
|
[debug] System config: []
|
||||||
[debug] User config: []
|
[debug] User config: []
|
||||||
[debug] Command-line args: [u'-v', u'http://www.youtube.com/watch?v=BaW_jenozKcj']
|
[debug] Command-line args: [u'-v', u'https://www.youtube.com/watch?v=BaW_jenozKcj']
|
||||||
[debug] Encodings: locale cp1251, fs mbcs, out cp866, pref cp1251
|
[debug] Encodings: locale cp1251, fs mbcs, out cp866, pref cp1251
|
||||||
[debug] youtube-dl version 2015.12.06
|
[debug] youtube-dl version 2015.12.06
|
||||||
[debug] Git HEAD: 135392e
|
[debug] Git HEAD: 135392e
|
||||||
@ -34,7 +34,7 @@ For bug reports, this means that your report should contain the *complete* outpu
|
|||||||
|
|
||||||
If your server has multiple IPs or you suspect censorship, adding `--call-home` may be a good idea to get more diagnostics. If the error is `ERROR: Unable to extract ...` and you cannot reproduce it from multiple countries, add `--dump-pages` (warning: this will yield a rather large output, redirect it to the file `log.txt` by adding `>log.txt 2>&1` to your command-line) or upload the `.dump` files you get when you add `--write-pages` [somewhere](https://gist.github.com/).
|
If your server has multiple IPs or you suspect censorship, adding `--call-home` may be a good idea to get more diagnostics. If the error is `ERROR: Unable to extract ...` and you cannot reproduce it from multiple countries, add `--dump-pages` (warning: this will yield a rather large output, redirect it to the file `log.txt` by adding `>log.txt 2>&1` to your command-line) or upload the `.dump` files you get when you add `--write-pages` [somewhere](https://gist.github.com/).
|
||||||
|
|
||||||
**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 `https://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. `https://www.youtube.com/`) is *not* an example URL.
|
||||||
|
|
||||||
### Are you using the latest version?
|
### Are you using the latest version?
|
||||||
|
|
||||||
@ -70,7 +70,7 @@ It may sound strange, but some bug reports we receive are completely unrelated t
|
|||||||
|
|
||||||
# DEVELOPER INSTRUCTIONS
|
# DEVELOPER INSTRUCTIONS
|
||||||
|
|
||||||
Most users do not need to build youtube-dl and can [download the builds](http://rg3.github.io/youtube-dl/download.html) or get them from their distribution.
|
Most users do not need to build youtube-dl and can [download the builds](https://rg3.github.io/youtube-dl/download.html) or get them from their distribution.
|
||||||
|
|
||||||
To run youtube-dl as a developer, you don't need to build anything either. Simply execute
|
To run youtube-dl as a developer, you don't need to build anything either. Simply execute
|
||||||
|
|
||||||
@ -82,6 +82,8 @@ To run the test, simply invoke your favorite test runner, or execute a test file
|
|||||||
python test/test_download.py
|
python test/test_download.py
|
||||||
nosetests
|
nosetests
|
||||||
|
|
||||||
|
See item 6 of [new extractor tutorial](#adding-support-for-a-new-site) for how to run extractor specific test cases.
|
||||||
|
|
||||||
If you want to create a build of youtube-dl yourself, you'll need
|
If you want to create a build of youtube-dl yourself, you'll need
|
||||||
|
|
||||||
* python
|
* python
|
||||||
@ -118,7 +120,7 @@ After you have ensured this site is distributing its content legally, you can fo
|
|||||||
class YourExtractorIE(InfoExtractor):
|
class YourExtractorIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?yourextractor\.com/watch/(?P<id>[0-9]+)'
|
_VALID_URL = r'https?://(?:www\.)?yourextractor\.com/watch/(?P<id>[0-9]+)'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://yourextractor.com/watch/42',
|
'url': 'https://yourextractor.com/watch/42',
|
||||||
'md5': 'TODO: md5 sum of the first 10241 bytes of the video file (use --test)',
|
'md5': 'TODO: md5 sum of the first 10241 bytes of the video file (use --test)',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '42',
|
'id': '42',
|
||||||
@ -149,10 +151,10 @@ After you have ensured this site is distributing its content legally, you can fo
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
5. Add an import in [`youtube_dl/extractor/extractors.py`](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/extractors.py).
|
5. Add an import in [`youtube_dl/extractor/extractors.py`](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/extractors.py).
|
||||||
6. Run `python test/test_download.py TestDownload.test_YourExtractor`. This *should fail* at first, but you can continually re-run it until you're done. If you decide to add more than one test, then rename ``_TEST`` to ``_TESTS`` and make it into a list of dictionaries. The tests will then be named `TestDownload.test_YourExtractor`, `TestDownload.test_YourExtractor_1`, `TestDownload.test_YourExtractor_2`, etc.
|
6. Run `python test/test_download.py TestDownload.test_YourExtractor`. This *should fail* at first, but you can continually re-run it until you're done. If you decide to add more than one test, then rename ``_TEST`` to ``_TESTS`` and make it into a list of dictionaries. The tests will then be named `TestDownload.test_YourExtractor`, `TestDownload.test_YourExtractor_1`, `TestDownload.test_YourExtractor_2`, etc. Note that tests with `only_matching` key in test's dict are not counted in.
|
||||||
7. Have a look at [`youtube_dl/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 and may return](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/common.py#L74-L252). Add tests and code for as many as you want.
|
7. Have a look at [`youtube_dl/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 and may return](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/common.py#L74-L252). Add tests and code for as many as you want.
|
||||||
8. Make sure your code follows [youtube-dl coding conventions](#youtube-dl-coding-conventions) and check the code with [flake8](https://pypi.python.org/pypi/flake8). Also make sure your code works under all [Python](http://www.python.org/) versions claimed supported by youtube-dl, namely 2.6, 2.7, and 3.2+.
|
8. Make sure your code follows [youtube-dl coding conventions](#youtube-dl-coding-conventions) and check the code with [flake8](https://pypi.python.org/pypi/flake8). Also make sure your code works under all [Python](https://www.python.org/) versions claimed supported by youtube-dl, namely 2.6, 2.7, and 3.2+.
|
||||||
9. When the tests pass, [add](http://git-scm.com/docs/git-add) the new files and [commit](http://git-scm.com/docs/git-commit) them and [push](http://git-scm.com/docs/git-push) the result, like this:
|
9. When the tests pass, [add](https://git-scm.com/docs/git-add) the new files and [commit](https://git-scm.com/docs/git-commit) them and [push](https://git-scm.com/docs/git-push) the result, like this:
|
||||||
|
|
||||||
$ git add youtube_dl/extractor/extractors.py
|
$ git add youtube_dl/extractor/extractors.py
|
||||||
$ git add youtube_dl/extractor/yourextractor.py
|
$ git add youtube_dl/extractor/yourextractor.py
|
||||||
|
598
ChangeLog
598
ChangeLog
@ -1,3 +1,599 @@
|
|||||||
|
version 2017.12.02
|
||||||
|
|
||||||
|
Core
|
||||||
|
+ [downloader/fragment] Commit part file after each fragment
|
||||||
|
+ [extractor/common] Add durations for DASH fragments with bare SegmentURLs
|
||||||
|
+ [extractor/common] Add support for DASH manifests with SegmentLists with
|
||||||
|
bare SegmentURLs (#14844)
|
||||||
|
+ [utils] Add hvc1 codec code to parse_codecs
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
* [xhamster] Fix extraction (#14884)
|
||||||
|
* [youku] Update ccode (#14872)
|
||||||
|
* [mnet] Fix format extraction (#14883)
|
||||||
|
+ [xiami] Add Referer header to API request
|
||||||
|
* [mtv] Correct scc extention in extracted subtitles (#13730)
|
||||||
|
* [vvvvid] Fix extraction for kenc videos (#13406)
|
||||||
|
+ [br] Add support for BR Mediathek videos (#14560, #14788)
|
||||||
|
+ [daisuki] Add support for motto.daisuki.com (#14681)
|
||||||
|
* [odnoklassniki] Fix API metadata request (#14862)
|
||||||
|
* [itv] Fix HLS formats extraction
|
||||||
|
+ [pbs] Add another media id regular expression
|
||||||
|
|
||||||
|
|
||||||
|
version 2017.11.26
|
||||||
|
|
||||||
|
Core
|
||||||
|
* [extractor/common] Use final URL when dumping request (#14769)
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
* [fczenit] Fix extraction
|
||||||
|
- [firstpost] Remove extractor
|
||||||
|
* [freespeech] Fix extraction
|
||||||
|
* [nexx] Extract more formats
|
||||||
|
+ [openload] Add support for openload.link (#14763)
|
||||||
|
* [empflix] Relax URL regular expression
|
||||||
|
* [empflix] Fix extractrion
|
||||||
|
* [tnaflix] Don't modify download URLs (#14811)
|
||||||
|
- [gamersyde] Remove extractor
|
||||||
|
* [francetv:generationwhat] Fix extraction
|
||||||
|
+ [massengeschmacktv] Add support for Massengeschmack TV
|
||||||
|
* [fox9] Fix extraction
|
||||||
|
* [faz] Fix extraction and add support for Perform Group embeds (#14714)
|
||||||
|
+ [performgroup] Add support for performgroup.com
|
||||||
|
+ [jwplatform] Add support for iframes (#14828)
|
||||||
|
* [culturebox] Fix extraction (#14827)
|
||||||
|
* [youku] Fix extraction; update ccode (#14815)
|
||||||
|
* [livestream] Make SMIL extraction non fatal (#14792)
|
||||||
|
+ [drtuber] Add support for mobile URLs (#14772)
|
||||||
|
+ [spankbang] Add support for mobile URLs (#14771)
|
||||||
|
* [instagram] Fix description, timestamp and counters extraction (#14755)
|
||||||
|
|
||||||
|
|
||||||
|
version 2017.11.15
|
||||||
|
|
||||||
|
Core
|
||||||
|
* [common] Skip Apple FairPlay m3u8 manifests (#14741)
|
||||||
|
* [YoutubeDL] Fix playlist range optimization for --playlist-items (#14740)
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
* [vshare] Capture and output error message
|
||||||
|
* [vshare] Fix extraction (#14473)
|
||||||
|
* [crunchyroll] Extract old RTMP formats
|
||||||
|
* [tva] Fix extraction (#14736)
|
||||||
|
* [gamespot] Lower preference of HTTP formats (#14652)
|
||||||
|
* [instagram:user] Fix extraction (#14699)
|
||||||
|
* [ccma] Fix typo (#14730)
|
||||||
|
- Remove sensitive data from logging in messages
|
||||||
|
* [instagram:user] Fix extraction (#14699)
|
||||||
|
+ [gamespot] Add support for article URLs (#14652)
|
||||||
|
* [gamespot] Skip Brightcove Once HTTP formats (#14652)
|
||||||
|
* [cartoonnetwork] Update tokenizer_src (#14666)
|
||||||
|
+ [wsj] Recognize another URL pattern (#14704)
|
||||||
|
* [pandatv] Update API URL and sign format URLs (#14693)
|
||||||
|
* [crunchyroll] Use old login method (#11572)
|
||||||
|
|
||||||
|
|
||||||
|
version 2017.11.06
|
||||||
|
|
||||||
|
Core
|
||||||
|
+ [extractor/common] Add protocol for f4m formats
|
||||||
|
* [f4m] Prefer baseURL for relative URLs (#14660)
|
||||||
|
* [extractor/common] Respect URL query in _extract_wowza_formats (14645)
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
+ [hotstar:playlist] Add support for playlists (#12465)
|
||||||
|
* [hotstar] Bypass geo restriction (#14672)
|
||||||
|
- [22tracks] Remove extractor (#11024, #14628)
|
||||||
|
+ [skysport] Sdd support ooyala videos protected with embed_token (#14641)
|
||||||
|
* [gamespot] Extract formats referenced with new data fields (#14652)
|
||||||
|
* [spankbang] Detect unavailable videos (#14644)
|
||||||
|
|
||||||
|
|
||||||
|
version 2017.10.29
|
||||||
|
|
||||||
|
Core
|
||||||
|
* [extractor/common] Prefix format id for audio only HLS formats
|
||||||
|
+ [utils] Add support for zero years and months in parse_duration
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
* [egghead] Fix extraction (#14388)
|
||||||
|
+ [fxnetworks] Extract series metadata (#14603)
|
||||||
|
+ [younow] Add support for younow.com (#9255, #9432, #12436)
|
||||||
|
* [dctptv] Fix extraction (#14599)
|
||||||
|
* [youtube] Restrict embed regex (#14600)
|
||||||
|
* [vimeo] Restrict iframe embed regex (#14600)
|
||||||
|
* [soundgasm] Improve extraction (#14588)
|
||||||
|
- [myvideo] Remove extractor (#8557)
|
||||||
|
+ [nbc] Add support for classic-tv videos (#14575)
|
||||||
|
+ [vrtnu] Add support for cookies authentication and simplify (#11873)
|
||||||
|
+ [canvas] Add support for vrt.be/vrtnu (#11873)
|
||||||
|
* [twitch:clips] Fix title extraction (#14566)
|
||||||
|
+ [ndtv] Add support for sub-sites (#14534)
|
||||||
|
* [dramafever] Fix login error message extraction
|
||||||
|
+ [nick] Add support for more nickelodeon sites (no, dk, se, ch, fr, es, pt,
|
||||||
|
ro, hu) (#14553)
|
||||||
|
|
||||||
|
|
||||||
|
version 2017.10.20
|
||||||
|
|
||||||
|
Core
|
||||||
|
* [downloader/fragment] Report warning instead of error on inconsistent
|
||||||
|
download state
|
||||||
|
* [downloader/hls] Fix total fragments count when ad fragments exist
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
* [parliamentliveuk] Fix extraction (#14524)
|
||||||
|
* [soundcloud] Update client id (#14546)
|
||||||
|
+ [servus] Add support for servus.com (#14362)
|
||||||
|
+ [unity] Add support for unity3d.com (#14528)
|
||||||
|
* [youtube] Replace youtube redirect URLs in description (#14517)
|
||||||
|
* [pbs] Restrict direct video URL regular expression (#14519)
|
||||||
|
* [drtv] Respect preference for direct HTTP formats (#14509)
|
||||||
|
+ [eporner] Add support for embed URLs (#14507)
|
||||||
|
* [arte] Capture and output error message
|
||||||
|
* [niconico] Improve uploader metadata extraction robustness (#14135)
|
||||||
|
|
||||||
|
|
||||||
|
version 2017.10.15.1
|
||||||
|
|
||||||
|
Core
|
||||||
|
* [downloader/hls] Ignore anvato ad fragments (#14496)
|
||||||
|
* [downloader/fragment] Output ad fragment count
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
* [scrippsnetworks:watch] Bypass geo restriction
|
||||||
|
+ [anvato] Add ability to bypass geo restriction
|
||||||
|
* [redditr] Fix extraction for URLs with query (#14495)
|
||||||
|
|
||||||
|
|
||||||
|
version 2017.10.15
|
||||||
|
|
||||||
|
Core
|
||||||
|
+ [common] Add support for jwplayer youtube embeds
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
* [scrippsnetworks:watch] Fix extraction (#14389)
|
||||||
|
* [anvato] Process master m3u8 manifests
|
||||||
|
* [youtube] Fix relative URLs in description
|
||||||
|
* [spike] Bypass geo restriction
|
||||||
|
+ [howstuffworks] Add support for more domains
|
||||||
|
* [infoq] Fix http format downloading
|
||||||
|
+ [rtlnl] Add support for another type of embeds
|
||||||
|
+ [onionstudios] Add support for bulbs-video embeds
|
||||||
|
* [udn] Fix extraction
|
||||||
|
* [shahid] Fix extraction (#14448)
|
||||||
|
* [kaltura] Ignore Widevine encrypted video (.wvm) (#14471)
|
||||||
|
* [vh1] Fix extraction (#9613)
|
||||||
|
|
||||||
|
|
||||||
|
version 2017.10.12
|
||||||
|
|
||||||
|
Core
|
||||||
|
* [YoutubeDL] Improve _default_format_spec (#14461)
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
* [steam] Fix extraction (#14067)
|
||||||
|
+ [funk] Add support for funk.net (#14464)
|
||||||
|
+ [nexx] Add support for shortcuts and relax domain id extraction
|
||||||
|
+ [voxmedia] Add support for recode.net (#14173)
|
||||||
|
+ [once] Add support for vmap URLs
|
||||||
|
+ [generic] Add support for channel9 embeds (#14469)
|
||||||
|
* [tva] Fix extraction (#14328)
|
||||||
|
+ [tubitv] Add support for new URL format (#14460)
|
||||||
|
- [afreecatv:global] Remove extractor
|
||||||
|
- [youtube:shared] Removed extractor (#14420)
|
||||||
|
+ [slideslive] Add support for slideslive.com (#2680)
|
||||||
|
+ [facebook] Support thumbnails (#14416)
|
||||||
|
* [vvvvid] Fix episode number extraction (#14456)
|
||||||
|
* [hrti:playlist] Relax URL regular expression
|
||||||
|
* [wdr] Relax media link regular expression (#14447)
|
||||||
|
* [hrti] Relax URL regular expression (#14443)
|
||||||
|
* [fox] Delegate extraction to uplynk:preplay (#14147)
|
||||||
|
+ [youtube] Add support for hooktube.com (#14437)
|
||||||
|
|
||||||
|
|
||||||
|
version 2017.10.07
|
||||||
|
|
||||||
|
Core
|
||||||
|
* [YoutubeDL] Ignore duplicates in --playlist-items
|
||||||
|
* [YoutubeDL] Fix out of range --playlist-items for iterable playlists and
|
||||||
|
reduce code duplication (#14425)
|
||||||
|
+ [utils] Use cache in OnDemandPagedList by default
|
||||||
|
* [postprocessor/ffmpeg] Convert to opus using libopus (#14381)
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
* [reddit] Sort formats (#14430)
|
||||||
|
* [lnkgo] Relax URL regular expression (#14423)
|
||||||
|
* [pornflip] Extend URL regular expression (#14405, #14406)
|
||||||
|
+ [xtube] Add support for embed URLs (#14417)
|
||||||
|
+ [xvideos] Add support for embed URLs and improve extraction (#14409)
|
||||||
|
* [beeg] Fix extraction (#14403)
|
||||||
|
* [tvn24] Relax URL regular expression (#14395)
|
||||||
|
* [nbc] Fix extraction (#13651, #13715, #14137, #14198, #14312, #14314, #14378,
|
||||||
|
#14392, #14414, #14419, #14431)
|
||||||
|
+ [ketnet] Add support for videos without direct sources (#14377)
|
||||||
|
* [canvas] Generalize mediazone.vrt.be extractor and rework canvas and een
|
||||||
|
+ [afreecatv] Add support for adult videos (#14376)
|
||||||
|
|
||||||
|
|
||||||
|
version 2017.10.01
|
||||||
|
|
||||||
|
Core
|
||||||
|
* [YoutubeDL] Document youtube_include_dash_manifest
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
+ [tvp] Add support for new URL schema (#14368)
|
||||||
|
+ [generic] Add support for single format Video.js embeds (#14371)
|
||||||
|
* [yahoo] Bypass geo restriction for brightcove (#14210)
|
||||||
|
* [yahoo] Use extracted brightcove account id (#14210)
|
||||||
|
* [rtve:alacarta] Fix extraction (#14290)
|
||||||
|
+ [yahoo] Add support for custom brigthcove embeds (#14210)
|
||||||
|
+ [generic] Add support for Video.js embeds
|
||||||
|
+ [gfycat] Add support for /gifs/detail URLs (#14322)
|
||||||
|
* [generic] Fix infinite recursion for twitter:player URLs (#14339)
|
||||||
|
* [xhamsterembed] Fix extraction (#14308)
|
||||||
|
|
||||||
|
|
||||||
|
version 2017.09.24
|
||||||
|
|
||||||
|
Core
|
||||||
|
+ [options] Accept lrc as a subtitle conversion target format (#14292)
|
||||||
|
* [utils] Fix handling raw TTML subtitles (#14191)
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
* [24video] Fix timestamp extraction and make non fatal (#14295)
|
||||||
|
+ [24video] Add support for 24video.adult (#14295)
|
||||||
|
+ [kakao] Add support for tv.kakao.com (#12298, #14007)
|
||||||
|
+ [twitter] Add support for URLs without user id (#14270)
|
||||||
|
+ [americastestkitchen] Add support for americastestkitchen.com (#10764,
|
||||||
|
#13996)
|
||||||
|
* [generic] Fix support for multiple HTML5 videos on one page (#14080)
|
||||||
|
* [mixcloud] Fix extraction (#14088, #14132)
|
||||||
|
+ [lynda] Add support for educourse.ga (#14286)
|
||||||
|
* [beeg] Fix extraction (#14275)
|
||||||
|
* [nbcsports:vplayer] Correct theplatform URL (#13873)
|
||||||
|
* [twitter] Fix duration extraction (#14141)
|
||||||
|
* [tvplay] Bypass geo restriction
|
||||||
|
+ [heise] Add support for YouTube embeds (#14109)
|
||||||
|
+ [popcorntv] Add support for popcorntv.it (#5914, #14211)
|
||||||
|
* [viki] Update app data (#14181)
|
||||||
|
* [morningstar] Relax URL regular expression (#14222)
|
||||||
|
* [openload] Fix extraction (#14225, #14257)
|
||||||
|
* [noovo] Fix extraction (#14214)
|
||||||
|
* [dailymotion:playlist] Relax URL regular expression (#14219)
|
||||||
|
+ [twitch] Add support for go.twitch.tv URLs (#14215)
|
||||||
|
* [vgtv] Relax URL regular expression (#14223)
|
||||||
|
|
||||||
|
|
||||||
|
version 2017.09.15
|
||||||
|
|
||||||
|
Core
|
||||||
|
* [downloader/fragment] Restart inconsistent incomplete fragment downloads
|
||||||
|
(#13731)
|
||||||
|
* [YoutubeDL] Download raw subtitles files (#12909, #14191)
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
* [condenast] Fix extraction (#14196, #14207)
|
||||||
|
+ [orf] Add support for f4m stories
|
||||||
|
* [tv4] Relax URL regular expression (#14206)
|
||||||
|
* [animeondemand] Bypass geo restriction
|
||||||
|
+ [animeondemand] Add support for flash videos (#9944)
|
||||||
|
|
||||||
|
|
||||||
|
version 2017.09.11
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
* [rutube:playlist] Fix suitable (#14166)
|
||||||
|
|
||||||
|
|
||||||
|
version 2017.09.10
|
||||||
|
|
||||||
|
Core
|
||||||
|
+ [utils] Introduce bool_or_none
|
||||||
|
* [YoutubeDL] Ensure dir existence for each requested format (#14116)
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
* [fox] Fix extraction (#14147)
|
||||||
|
* [rutube] Use bool_or_none
|
||||||
|
* [rutube] Rework and generalize playlist extractors (#13565)
|
||||||
|
+ [rutube:playlist] Add support for playlists (#13534, #13565)
|
||||||
|
+ [radiocanada] Add fallback for title extraction (#14145)
|
||||||
|
* [vk] Use dedicated YouTube embeds extraction routine
|
||||||
|
* [vice] Use dedicated YouTube embeds extraction routine
|
||||||
|
* [cracked] Use dedicated YouTube embeds extraction routine
|
||||||
|
* [chilloutzone] Use dedicated YouTube embeds extraction routine
|
||||||
|
* [abcnews] Use dedicated YouTube embeds extraction routine
|
||||||
|
* [youtube] Separate methods for embeds extraction
|
||||||
|
* [redtube] Fix formats extraction (#14122)
|
||||||
|
* [arte] Relax unavailability check (#14112)
|
||||||
|
+ [manyvids] Add support for preview videos from manyvids.com (#14053, #14059)
|
||||||
|
* [vidme:user] Relax URL regular expression (#14054)
|
||||||
|
* [bpb] Fix extraction (#14043, #14086)
|
||||||
|
* [soundcloud] Fix download URL with private tracks (#14093)
|
||||||
|
* [aliexpress:live] Add support for live.aliexpress.com (#13698, #13707)
|
||||||
|
* [viidea] Capture and output lecture error message (#14099)
|
||||||
|
* [radiocanada] Skip unsupported platforms (#14100)
|
||||||
|
|
||||||
|
|
||||||
|
version 2017.09.02
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
* [youtube] Force old layout for each webpage (#14068, #14072, #14074, #14076,
|
||||||
|
#14077, #14079, #14082, #14083, #14094, #14095, #14096)
|
||||||
|
* [youtube] Fix upload date extraction (#14065)
|
||||||
|
+ [charlierose] Add support for episodes (#14062)
|
||||||
|
+ [bbccouk] Add support for w-prefixed ids (#14056)
|
||||||
|
* [googledrive] Extend URL regular expression (#9785)
|
||||||
|
+ [googledrive] Add support for source format (#14046)
|
||||||
|
* [pornhd] Fix extraction (#14005)
|
||||||
|
|
||||||
|
|
||||||
|
version 2017.08.27.1
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
|
||||||
|
* [youtube] Fix extraction with --youtube-skip-dash-manifest enabled (#14037)
|
||||||
|
|
||||||
|
|
||||||
|
version 2017.08.27
|
||||||
|
|
||||||
|
Core
|
||||||
|
+ [extractor/common] Extract height and format id for HTML5 videos (#14034)
|
||||||
|
* [downloader/http] Rework HTTP downloader (#506, #809, #2849, #4240, #6023,
|
||||||
|
#8625, #9483)
|
||||||
|
* Simplify code and split into separate routines to facilitate maintaining
|
||||||
|
* Make retry mechanism work on errors during actual download not only
|
||||||
|
during connection establishment phase
|
||||||
|
* Retry on ECONNRESET and ETIMEDOUT during reading data from network
|
||||||
|
* Retry on content too short
|
||||||
|
* Show error description on retry
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
* [generic] Lower preference for extraction from LD-JSON
|
||||||
|
* [rai] Fix audio formats extraction (#14024)
|
||||||
|
* [youtube] Fix controversy videos extraction (#14027, #14029)
|
||||||
|
* [mixcloud] Fix extraction (#14015, #14020)
|
||||||
|
|
||||||
|
|
||||||
|
version 2017.08.23
|
||||||
|
|
||||||
|
Core
|
||||||
|
+ [extractor/common] Introduce _parse_xml
|
||||||
|
* [extractor/common] Make HLS and DASH extraction in_parse_html5_media_entries
|
||||||
|
non fatal (#13970)
|
||||||
|
* [utils] Fix unescapeHTML for misformed string like "&a"" (#13935)
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
* [cbc:watch] Bypass geo restriction (#13993)
|
||||||
|
* [toutv] Relax DRM check (#13994)
|
||||||
|
+ [googledrive] Add support for subtitles (#13619, #13638)
|
||||||
|
* [pornhub] Relax uploader regular expression (#13906, #13975)
|
||||||
|
* [bandcamp:album] Extract track titles (#13962)
|
||||||
|
+ [bbccouk] Add support for events URLs (#13893)
|
||||||
|
+ [liveleak] Support multi-video pages (#6542)
|
||||||
|
+ [liveleak] Support another liveleak embedding pattern (#13336)
|
||||||
|
* [cda] Fix extraction (#13935)
|
||||||
|
+ [laola1tv] Add support for tv.ittf.com (#13965)
|
||||||
|
* [mixcloud] Fix extraction (#13958, #13974, #13980, #14003)
|
||||||
|
|
||||||
|
|
||||||
|
version 2017.08.18
|
||||||
|
|
||||||
|
Core
|
||||||
|
* [YoutubeDL] Sanitize byte string format URLs (#13951)
|
||||||
|
+ [extractor/common] Add support for float durations in _parse_mpd_formats
|
||||||
|
(#13919)
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
* [arte] Detect unavailable videos (#13945)
|
||||||
|
* [generic] Convert redirect URLs to unicode strings (#13951)
|
||||||
|
* [udemy] Fix paid course detection (#13943)
|
||||||
|
* [pluralsight] Use RPC API for course extraction (#13937)
|
||||||
|
+ [clippit] Add support for clippituser.tv
|
||||||
|
+ [qqmusic] Support new URL schemes (#13805)
|
||||||
|
* [periscope] Renew HLS extraction (#13917)
|
||||||
|
* [mixcloud] Extract decrypt key
|
||||||
|
|
||||||
|
|
||||||
|
version 2017.08.13
|
||||||
|
|
||||||
|
Core
|
||||||
|
* [YoutubeDL] Make sure format id is not empty
|
||||||
|
* [extractor/common] Make _family_friendly_search optional
|
||||||
|
* [extractor/common] Respect source's type attribute for HTML5 media (#13892)
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
* [pornhub:playlistbase] Skip videos from drop-down menu (#12819, #13902)
|
||||||
|
+ [fourtube] Add support pornerbros.com (#6022)
|
||||||
|
+ [fourtube] Add support porntube.com (#7859, #13901)
|
||||||
|
+ [fourtube] Add support fux.com
|
||||||
|
* [limelight] Improve embeds detection (#13895)
|
||||||
|
+ [reddit] Add support for v.redd.it and reddit.com (#13847)
|
||||||
|
* [aparat] Extract all formats (#13887)
|
||||||
|
* [mixcloud] Fix play info decryption (#13885)
|
||||||
|
+ [generic] Add support for vzaar embeds (#13876)
|
||||||
|
|
||||||
|
|
||||||
|
version 2017.08.09
|
||||||
|
|
||||||
|
Core
|
||||||
|
* [utils] Skip missing params in cli_bool_option (#13865)
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
* [xxxymovies] Fix title extraction (#13868)
|
||||||
|
+ [nick] Add support for nick.com.pl (#13860)
|
||||||
|
* [mixcloud] Fix play info decryption (#13867)
|
||||||
|
* [20min] Fix embeds extraction (#13852)
|
||||||
|
* [dplayit] Fix extraction (#13851)
|
||||||
|
+ [niconico] Support videos with multiple formats (#13522)
|
||||||
|
+ [niconico] Support HTML5-only videos (#13806)
|
||||||
|
|
||||||
|
|
||||||
|
version 2017.08.06
|
||||||
|
|
||||||
|
Core
|
||||||
|
* Use relative paths for DASH fragments (#12990)
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
* [pluralsight] Fix format selection
|
||||||
|
- [mpora] Remove extractor (#13826)
|
||||||
|
+ [voot] Add support for voot.com (#10255, #11644, #11814, #12350, #13218)
|
||||||
|
* [vlive:channel] Limit number of videos per page to 100 (#13830)
|
||||||
|
* [podomatic] Extend URL regular expression (#13827)
|
||||||
|
* [cinchcast] Extend URL regular expression
|
||||||
|
* [yandexdisk] Relax URL regular expression (#13824)
|
||||||
|
* [vidme] Extract DASH and HLS formats
|
||||||
|
- [teamfour] Remove extractor (#13782)
|
||||||
|
* [pornhd] Fix extraction (#13783)
|
||||||
|
* [udemy] Fix subtitles extraction (#13812)
|
||||||
|
* [mlb] Extend URL regular expression (#13740, #13773)
|
||||||
|
+ [pbs] Add support for new URL schema (#13801)
|
||||||
|
* [nrktv] Update API host (#13796)
|
||||||
|
|
||||||
|
|
||||||
|
version 2017.07.30.1
|
||||||
|
|
||||||
|
Core
|
||||||
|
* [downloader/hls] Use redirect URL as manifest base (#13755)
|
||||||
|
* [options] Correctly hide login info from debug outputs (#13696)
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
+ [watchbox] Add support for watchbox.de (#13739)
|
||||||
|
- [clipfish] Remove extractor
|
||||||
|
+ [youjizz] Fix extraction (#13744)
|
||||||
|
+ [generic] Add support for another ooyala embed pattern (#13727)
|
||||||
|
+ [ard] Add support for lives (#13771)
|
||||||
|
* [soundcloud] Update client id
|
||||||
|
+ [soundcloud:trackstation] Add support for track stations (#13733)
|
||||||
|
* [svtplay] Use geo verification proxy for API request
|
||||||
|
* [svtplay] Update API URL (#13767)
|
||||||
|
+ [yandexdisk] Add support for yadi.sk (#13755)
|
||||||
|
+ [megaphone] Add support for megaphone.fm
|
||||||
|
* [amcnetworks] Make rating optional (#12453)
|
||||||
|
* [cloudy] Fix extraction (#13737)
|
||||||
|
+ [nickru] Add support for nickelodeon.ru
|
||||||
|
* [mtv] Improve thumbnal extraction
|
||||||
|
* [nick] Automate geo-restriction bypass (#13711)
|
||||||
|
* [niconico] Improve error reporting (#13696)
|
||||||
|
|
||||||
|
|
||||||
|
version 2017.07.23
|
||||||
|
|
||||||
|
Core
|
||||||
|
* [YoutubeDL] Improve default format specification (#13704)
|
||||||
|
* [YoutubeDL] Do not override id, extractor and extractor_key for
|
||||||
|
url_transparent entities
|
||||||
|
* [extractor/common] Fix playlist_from_matches
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
* [itv] Fix production id extraction (#13671, #13703)
|
||||||
|
* [vidio] Make duration non fatal and fix typo
|
||||||
|
* [mtv] Skip missing video parts (#13690)
|
||||||
|
* [sportbox:embed] Fix extraction
|
||||||
|
+ [npo] Add support for npo3.nl URLs (#13695)
|
||||||
|
* [dramafever] Remove video id from title (#13699)
|
||||||
|
+ [egghead:lesson] Add support for lessons (#6635)
|
||||||
|
* [funnyordie] Extract more metadata (#13677)
|
||||||
|
* [youku:show] Fix playlist extraction (#13248)
|
||||||
|
+ [dispeak] Recognize sevt subdomain (#13276)
|
||||||
|
* [adn] Improve error reporting (#13663)
|
||||||
|
* [crunchyroll] Relax series and season regular expression (#13659)
|
||||||
|
+ [spiegel:article] Add support for nexx iframe embeds (#13029)
|
||||||
|
+ [nexx:embed] Add support for iframe embeds
|
||||||
|
* [nexx] Improve JS embed extraction
|
||||||
|
+ [pearvideo] Add support for pearvideo.com (#13031)
|
||||||
|
|
||||||
|
|
||||||
|
version 2017.07.15
|
||||||
|
|
||||||
|
Core
|
||||||
|
* [YoutubeDL] Don't expand environment variables in meta fields (#13637)
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
* [spiegeltv] Delegate extraction to nexx extractor (#13159)
|
||||||
|
+ [nexx] Add support for nexx.cloud (#10807, #13465)
|
||||||
|
* [generic] Fix rutube embeds extraction (#13641)
|
||||||
|
* [karrierevideos] Fix title extraction (#13641)
|
||||||
|
* [youtube] Don't capture YouTube Red ad for creator meta field (#13621)
|
||||||
|
* [slideshare] Fix extraction (#13617)
|
||||||
|
+ [5tv] Add another video URL pattern (#13354, #13606)
|
||||||
|
* [drtv] Make HLS and HDS extraction non fatal
|
||||||
|
* [ted] Fix subtitles extraction (#13628, #13629)
|
||||||
|
* [vine] Make sure the title won't be empty
|
||||||
|
+ [twitter] Support HLS streams in vmap URLs
|
||||||
|
+ [periscope] Support pscp.tv URLs in embedded frames
|
||||||
|
* [twitter] Extract mp4 urls via mobile API (#12726)
|
||||||
|
* [niconico] Fix authentication error handling (#12486)
|
||||||
|
* [giantbomb] Extract m3u8 formats (#13626)
|
||||||
|
+ [vlive:playlist] Add support for playlists (#13613)
|
||||||
|
|
||||||
|
|
||||||
|
version 2017.07.09
|
||||||
|
|
||||||
|
Core
|
||||||
|
+ [extractor/common] Add support for AMP tags in _parse_html5_media_entries
|
||||||
|
+ [utils] Support attributes with no values in get_elements_by_attribute
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
+ [dailymail] Add support for embeds
|
||||||
|
+ [joj] Add support for joj.sk (#13268)
|
||||||
|
* [abc.net.au:iview] Extract more formats (#13492, #13489)
|
||||||
|
* [egghead:course] Fix extraction (#6635, #13370)
|
||||||
|
+ [cjsw] Add support for cjsw.com (#13525)
|
||||||
|
+ [eagleplatform] Add support for referrer protected videos (#13557)
|
||||||
|
+ [eagleplatform] Add support for another embed pattern (#13557)
|
||||||
|
* [veoh] Extend URL regular expression (#13601)
|
||||||
|
* [npo:live] Fix live stream id extraction (#13568, #13605)
|
||||||
|
* [googledrive] Fix height extraction (#13603)
|
||||||
|
+ [dailymotion] Add support for new layout (#13580)
|
||||||
|
- [yam] Remove extractor
|
||||||
|
* [xhamster] Extract all formats and fix duration extraction (#13593)
|
||||||
|
+ [xhamster] Add support for new URL schema (#13593)
|
||||||
|
* [espn] Extend URL regular expression (#13244, #13549)
|
||||||
|
* [kaltura] Fix typo in subtitles extraction (#13569)
|
||||||
|
* [vier] Adapt extraction to redesign (#13575)
|
||||||
|
|
||||||
|
|
||||||
|
version 2017.07.02
|
||||||
|
|
||||||
|
Core
|
||||||
|
* [extractor/common] Improve _json_ld
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
+ [thisoldhouse] Add more fallbacks for video id
|
||||||
|
* [thisoldhouse] Fix video id extraction (#13540, #13541)
|
||||||
|
* [xfileshare] Extend format regular expression (#13536)
|
||||||
|
* [ted] Fix extraction (#13535)
|
||||||
|
+ [tastytrade] Add support for tastytrade.com (#13521)
|
||||||
|
* [dplayit] Relax video id regular expression (#13524)
|
||||||
|
+ [generic] Extract more generic metadata (#13527)
|
||||||
|
+ [bbccouk] Capture and output error message (#13501, #13518)
|
||||||
|
* [cbsnews] Relax video info regular expression (#13284, #13503)
|
||||||
|
+ [facebook] Add support for plugin video embeds and multiple embeds (#13493)
|
||||||
|
* [soundcloud] Switch to https for API requests (#13502)
|
||||||
|
* [pandatv] Switch to https for API and download URLs
|
||||||
|
+ [pandatv] Add support for https URLs (#13491)
|
||||||
|
+ [niconico] Support sp subdomain (#13494)
|
||||||
|
|
||||||
|
|
||||||
|
version 2017.06.25
|
||||||
|
|
||||||
|
Core
|
||||||
|
+ [adobepass] Add support for DIRECTV NOW (mso ATTOTT) (#13472)
|
||||||
|
* [YoutubeDL] Skip malformed formats for better extraction robustness
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
+ [wsj] Add support for barrons.com (#13470)
|
||||||
|
+ [ign] Add another video id pattern (#13328)
|
||||||
|
+ [raiplay:live] Add support for live streams (#13414)
|
||||||
|
+ [redbulltv] Add support for live videos and segments (#13486)
|
||||||
|
+ [onetpl] Add support for videos embedded via pulsembed (#13482)
|
||||||
|
* [ooyala] Make more robust
|
||||||
|
* [ooyala] Skip empty format URLs (#13471, #13476)
|
||||||
|
* [hgtv.com:show] Fix typo
|
||||||
|
|
||||||
|
|
||||||
version 2017.06.23
|
version 2017.06.23
|
||||||
|
|
||||||
Core
|
Core
|
||||||
@ -375,7 +971,7 @@ version 2017.04.14
|
|||||||
|
|
||||||
Core
|
Core
|
||||||
+ [downloader/hls] Add basic support for EXT-X-BYTERANGE tag (#10955)
|
+ [downloader/hls] Add basic support for EXT-X-BYTERANGE tag (#10955)
|
||||||
+ [adobepass] Improve Comcast and Verison login code (#10803)
|
+ [adobepass] Improve Comcast and Verizon login code (#10803)
|
||||||
+ [adobepass] Add support for Verizon (#10803)
|
+ [adobepass] Add support for Verizon (#10803)
|
||||||
|
|
||||||
Extractors
|
Extractors
|
||||||
|
25
Makefile
25
Makefile
@ -36,8 +36,17 @@ test:
|
|||||||
|
|
||||||
ot: offlinetest
|
ot: offlinetest
|
||||||
|
|
||||||
|
# Keep this list in sync with devscripts/run_tests.sh
|
||||||
offlinetest: codetest
|
offlinetest: codetest
|
||||||
$(PYTHON) -m nose --verbose test --exclude test_download.py --exclude test_age_restriction.py --exclude test_subtitles.py --exclude test_write_annotations.py --exclude test_youtube_lists.py --exclude test_iqiyi_sdk_interpreter.py --exclude test_socks.py
|
$(PYTHON) -m nose --verbose test \
|
||||||
|
--exclude test_age_restriction.py \
|
||||||
|
--exclude test_download.py \
|
||||||
|
--exclude test_iqiyi_sdk_interpreter.py \
|
||||||
|
--exclude test_socks.py \
|
||||||
|
--exclude test_subtitles.py \
|
||||||
|
--exclude test_write_annotations.py \
|
||||||
|
--exclude test_youtube_lists.py \
|
||||||
|
--exclude test_youtube_signature.py
|
||||||
|
|
||||||
tar: youtube-dl.tar.gz
|
tar: youtube-dl.tar.gz
|
||||||
|
|
||||||
@ -46,8 +55,15 @@ tar: youtube-dl.tar.gz
|
|||||||
pypi-files: youtube-dl.bash-completion README.txt youtube-dl.1 youtube-dl.fish
|
pypi-files: youtube-dl.bash-completion README.txt youtube-dl.1 youtube-dl.fish
|
||||||
|
|
||||||
youtube-dl: youtube_dl/*.py youtube_dl/*/*.py
|
youtube-dl: youtube_dl/*.py youtube_dl/*/*.py
|
||||||
zip --quiet youtube-dl youtube_dl/*.py youtube_dl/*/*.py
|
mkdir -p zip
|
||||||
zip --quiet --junk-paths youtube-dl youtube_dl/__main__.py
|
for d in youtube_dl youtube_dl/downloader youtube_dl/extractor youtube_dl/postprocessor ; do \
|
||||||
|
mkdir -p zip/$$d ;\
|
||||||
|
cp -pPR $$d/*.py zip/$$d/ ;\
|
||||||
|
done
|
||||||
|
touch -t 200001010101 zip/youtube_dl/*.py zip/youtube_dl/*/*.py
|
||||||
|
mv zip/youtube_dl/__main__.py zip/
|
||||||
|
cd zip ; zip -q ../youtube-dl youtube_dl/*.py youtube_dl/*/*.py __main__.py
|
||||||
|
rm -rf zip
|
||||||
echo '#!$(PYTHON)' > youtube-dl
|
echo '#!$(PYTHON)' > youtube-dl
|
||||||
cat youtube-dl.zip >> youtube-dl
|
cat youtube-dl.zip >> youtube-dl
|
||||||
rm youtube-dl.zip
|
rm youtube-dl.zip
|
||||||
@ -103,11 +119,10 @@ youtube-dl.tar.gz: youtube-dl README.md README.txt youtube-dl.1 youtube-dl.bash-
|
|||||||
--exclude '*~' \
|
--exclude '*~' \
|
||||||
--exclude '__pycache__' \
|
--exclude '__pycache__' \
|
||||||
--exclude '.git' \
|
--exclude '.git' \
|
||||||
--exclude 'testdata' \
|
|
||||||
--exclude 'docs/_build' \
|
--exclude 'docs/_build' \
|
||||||
-- \
|
-- \
|
||||||
bin devscripts test youtube_dl docs \
|
bin devscripts test youtube_dl docs \
|
||||||
ChangeLog LICENSE README.md README.txt \
|
ChangeLog LICENSE README.md README.txt \
|
||||||
Makefile MANIFEST.in youtube-dl.1 youtube-dl.bash-completion \
|
Makefile MANIFEST.in youtube-dl.1 youtube-dl.bash-completion \
|
||||||
youtube-dl.zsh youtube-dl.fish setup.py \
|
youtube-dl.zsh youtube-dl.fish setup.py setup.cfg \
|
||||||
youtube-dl
|
youtube-dl
|
||||||
|
64
README.md
64
README.md
@ -1,3 +1,5 @@
|
|||||||
|
[](https://travis-ci.org/rg3/youtube-dl)
|
||||||
|
|
||||||
youtube-dl - download videos from youtube.com or other video platforms
|
youtube-dl - download videos from youtube.com or other video platforms
|
||||||
|
|
||||||
- [INSTALLATION](#installation)
|
- [INSTALLATION](#installation)
|
||||||
@ -25,7 +27,7 @@ If you do not have curl, you can alternatively use a recent wget:
|
|||||||
sudo wget https://yt-dl.org/downloads/latest/youtube-dl -O /usr/local/bin/youtube-dl
|
sudo wget https://yt-dl.org/downloads/latest/youtube-dl -O /usr/local/bin/youtube-dl
|
||||||
sudo chmod a+rx /usr/local/bin/youtube-dl
|
sudo chmod a+rx /usr/local/bin/youtube-dl
|
||||||
|
|
||||||
Windows users can [download an .exe file](https://yt-dl.org/latest/youtube-dl.exe) and place it in any location on their [PATH](http://en.wikipedia.org/wiki/PATH_%28variable%29) except for `%SYSTEMROOT%\System32` (e.g. **do not** put in `C:\Windows\System32`).
|
Windows users can [download an .exe file](https://yt-dl.org/latest/youtube-dl.exe) and place it in any location on their [PATH](https://en.wikipedia.org/wiki/PATH_%28variable%29) except for `%SYSTEMROOT%\System32` (e.g. **do not** put in `C:\Windows\System32`).
|
||||||
|
|
||||||
You can also use pip:
|
You can also use pip:
|
||||||
|
|
||||||
@ -33,7 +35,7 @@ You can also use pip:
|
|||||||
|
|
||||||
This command will update youtube-dl if you have already installed it. See the [pypi page](https://pypi.python.org/pypi/youtube_dl) for more information.
|
This command will update youtube-dl if you have already installed it. See the [pypi page](https://pypi.python.org/pypi/youtube_dl) for more information.
|
||||||
|
|
||||||
OS X users can install youtube-dl with [Homebrew](http://brew.sh/):
|
OS X users can install youtube-dl with [Homebrew](https://brew.sh/):
|
||||||
|
|
||||||
brew install youtube-dl
|
brew install youtube-dl
|
||||||
|
|
||||||
@ -427,7 +429,7 @@ Alternatively, refer to the [developer instructions](#developer-instructions) fo
|
|||||||
syntax. Example: --exec 'adb push {}
|
syntax. Example: --exec 'adb push {}
|
||||||
/sdcard/Music/ && rm {}'
|
/sdcard/Music/ && rm {}'
|
||||||
--convert-subs FORMAT Convert the subtitles to other format
|
--convert-subs FORMAT Convert the subtitles to other format
|
||||||
(currently supported: srt|ass|vtt)
|
(currently supported: srt|ass|vtt|lrc)
|
||||||
|
|
||||||
# CONFIGURATION
|
# CONFIGURATION
|
||||||
|
|
||||||
@ -458,7 +460,7 @@ You can also use `--config-location` if you want to use custom configuration fil
|
|||||||
|
|
||||||
### Authentication with `.netrc` file
|
### Authentication with `.netrc` file
|
||||||
|
|
||||||
You may also want to configure automatic credentials storage for extractors that support authentication (by providing login and password with `--username` and `--password`) in order not to pass credentials as command line arguments on every youtube-dl execution and prevent tracking plain text passwords in the shell command history. You can achieve this using a [`.netrc` file](http://stackoverflow.com/tags/.netrc/info) on a per extractor basis. For that you will need to create a `.netrc` file in your `$HOME` and restrict permissions to read/write by only you:
|
You may also want to configure automatic credentials storage for extractors that support authentication (by providing login and password with `--username` and `--password`) in order not to pass credentials as command line arguments on every youtube-dl execution and prevent tracking plain text passwords in the shell command history. You can achieve this using a [`.netrc` file](https://stackoverflow.com/tags/.netrc/info) on a per extractor basis. For that you will need to create a `.netrc` file in your `$HOME` and restrict permissions to read/write by only you:
|
||||||
```
|
```
|
||||||
touch $HOME/.netrc
|
touch $HOME/.netrc
|
||||||
chmod a-rwx,u+rw $HOME/.netrc
|
chmod a-rwx,u+rw $HOME/.netrc
|
||||||
@ -485,7 +487,7 @@ The `-o` option allows users to indicate a template for the output file names.
|
|||||||
|
|
||||||
**tl;dr:** [navigate me to examples](#output-template-examples).
|
**tl;dr:** [navigate me to examples](#output-template-examples).
|
||||||
|
|
||||||
The basic usage is not to set any template arguments when downloading a single file, like in `youtube-dl -o funny_video.flv "http://some/video"`. However, it may contain special sequences that will be replaced when downloading each video. The special sequences may be formatted according to [python string formatting operations](https://docs.python.org/2/library/stdtypes.html#string-formatting). For example, `%(NAME)s` or `%(NAME)05d`. To clarify, that is a percent symbol followed by a name in parentheses, followed by a formatting operations. Allowed names along with sequence type are:
|
The basic usage is not to set any template arguments when downloading a single file, like in `youtube-dl -o funny_video.flv "https://some/video"`. However, it may contain special sequences that will be replaced when downloading each video. The special sequences may be formatted according to [python string formatting operations](https://docs.python.org/2/library/stdtypes.html#string-formatting). For example, `%(NAME)s` or `%(NAME)05d`. To clarify, that is a percent symbol followed by a name in parentheses, followed by a formatting operations. Allowed names along with sequence type are:
|
||||||
|
|
||||||
- `id` (string): Video identifier
|
- `id` (string): Video identifier
|
||||||
- `title` (string): Video title
|
- `title` (string): Video title
|
||||||
@ -584,7 +586,7 @@ If you are using an output template inside a Windows batch file then you must es
|
|||||||
|
|
||||||
#### Output template examples
|
#### Output template examples
|
||||||
|
|
||||||
Note on Windows you may need to use double quotes instead of single.
|
Note that on Windows you may need to use double quotes instead of single.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ youtube-dl --get-filename -o '%(title)s.%(ext)s' BaW_jenozKc
|
$ youtube-dl --get-filename -o '%(title)s.%(ext)s' BaW_jenozKc
|
||||||
@ -603,7 +605,7 @@ $ youtube-dl -o '%(uploader)s/%(playlist)s/%(playlist_index)s - %(title)s.%(ext)
|
|||||||
$ youtube-dl -u user -p password -o '~/MyVideos/%(playlist)s/%(chapter_number)s - %(chapter)s/%(title)s.%(ext)s' https://www.udemy.com/java-tutorial/
|
$ youtube-dl -u user -p password -o '~/MyVideos/%(playlist)s/%(chapter_number)s - %(chapter)s/%(title)s.%(ext)s' https://www.udemy.com/java-tutorial/
|
||||||
|
|
||||||
# Download entire series season keeping each series and each season in separate directory under C:/MyVideos
|
# Download entire series season keeping each series and each season in separate directory under C:/MyVideos
|
||||||
$ youtube-dl -o "C:/MyVideos/%(series)s/%(season_number)s - %(season)s/%(episode_number)s - %(episode)s.%(ext)s" http://videomore.ru/kino_v_detalayah/5_sezon/367617
|
$ youtube-dl -o "C:/MyVideos/%(series)s/%(season_number)s - %(season)s/%(episode_number)s - %(episode)s.%(ext)s" https://videomore.ru/kino_v_detalayah/5_sezon/367617
|
||||||
|
|
||||||
# Stream the video being downloaded to stdout
|
# Stream the video being downloaded to stdout
|
||||||
$ youtube-dl -o - BaW_jenozKc
|
$ youtube-dl -o - BaW_jenozKc
|
||||||
@ -671,7 +673,7 @@ If you want to preserve the old format selection behavior (prior to youtube-dl 2
|
|||||||
|
|
||||||
#### Format selection examples
|
#### Format selection examples
|
||||||
|
|
||||||
Note on Windows you may need to use double quotes instead of single.
|
Note that on Windows you may need to use double quotes instead of single.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Download best mp4 format available or any other best if no mp4 available
|
# Download best mp4 format available or any other best if no mp4 available
|
||||||
@ -716,17 +718,17 @@ $ youtube-dl --dateafter 20000101 --datebefore 20091231
|
|||||||
|
|
||||||
### How do I update youtube-dl?
|
### How do I update youtube-dl?
|
||||||
|
|
||||||
If you've followed [our manual installation instructions](http://rg3.github.io/youtube-dl/download.html), you can simply run `youtube-dl -U` (or, on Linux, `sudo youtube-dl -U`).
|
If you've followed [our manual installation instructions](https://rg3.github.io/youtube-dl/download.html), you can simply run `youtube-dl -U` (or, on Linux, `sudo youtube-dl -U`).
|
||||||
|
|
||||||
If you have used pip, a simple `sudo pip install -U youtube-dl` is sufficient to update.
|
If you have used pip, a simple `sudo pip install -U youtube-dl` is sufficient to update.
|
||||||
|
|
||||||
If you have installed youtube-dl using a package manager like *apt-get* or *yum*, use the standard system update mechanism to update. Note that distribution packages are often outdated. As a rule of thumb, youtube-dl releases at least once a month, and often weekly or even daily. Simply go to http://yt-dl.org/ to find out the current version. Unfortunately, there is nothing we youtube-dl developers can do if your distribution serves a really outdated version. You can (and should) complain to your distribution in their bugtracker or support forum.
|
If you have installed youtube-dl using a package manager like *apt-get* or *yum*, use the standard system update mechanism to update. Note that distribution packages are often outdated. As a rule of thumb, youtube-dl releases at least once a month, and often weekly or even daily. Simply go to https://yt-dl.org to find out the current version. Unfortunately, there is nothing we youtube-dl developers can do if your distribution serves a really outdated version. You can (and should) complain to your distribution in their bugtracker or support forum.
|
||||||
|
|
||||||
As a last resort, you can also uninstall the version installed by your package manager and follow our manual installation instructions. For that, remove the distribution's package, with a line like
|
As a last resort, you can also uninstall the version installed by your package manager and follow our manual installation instructions. For that, remove the distribution's package, with a line like
|
||||||
|
|
||||||
sudo apt-get remove -y youtube-dl
|
sudo apt-get remove -y youtube-dl
|
||||||
|
|
||||||
Afterwards, simply follow [our manual installation instructions](http://rg3.github.io/youtube-dl/download.html):
|
Afterwards, simply follow [our manual installation instructions](https://rg3.github.io/youtube-dl/download.html):
|
||||||
|
|
||||||
```
|
```
|
||||||
sudo wget https://yt-dl.org/latest/youtube-dl -O /usr/local/bin/youtube-dl
|
sudo wget https://yt-dl.org/latest/youtube-dl -O /usr/local/bin/youtube-dl
|
||||||
@ -766,11 +768,11 @@ Apparently YouTube requires you to pass a CAPTCHA test if you download too much.
|
|||||||
|
|
||||||
youtube-dl works fine on its own on most sites. However, if you want to convert video/audio, you'll need [avconv](https://libav.org/) or [ffmpeg](https://www.ffmpeg.org/). On some sites - most notably YouTube - videos can be retrieved in a higher quality format without sound. youtube-dl will detect whether avconv/ffmpeg is present and automatically pick the best option.
|
youtube-dl works fine on its own on most sites. However, if you want to convert video/audio, you'll need [avconv](https://libav.org/) or [ffmpeg](https://www.ffmpeg.org/). On some sites - most notably YouTube - videos can be retrieved in a higher quality format without sound. youtube-dl will detect whether avconv/ffmpeg is present and automatically pick the best option.
|
||||||
|
|
||||||
Videos or video formats streamed via RTMP protocol can only be downloaded when [rtmpdump](https://rtmpdump.mplayerhq.hu/) is installed. Downloading MMS and RTSP videos requires either [mplayer](http://mplayerhq.hu/) or [mpv](https://mpv.io/) to be installed.
|
Videos or video formats streamed via RTMP protocol can only be downloaded when [rtmpdump](https://rtmpdump.mplayerhq.hu/) is installed. Downloading MMS and RTSP videos requires either [mplayer](https://mplayerhq.hu/) or [mpv](https://mpv.io/) to be installed.
|
||||||
|
|
||||||
### I have downloaded a video but how can I play it?
|
### I have downloaded a video but how can I play it?
|
||||||
|
|
||||||
Once the video is fully downloaded, use any video player, such as [mpv](https://mpv.io/), [vlc](http://www.videolan.org/) or [mplayer](http://www.mplayerhq.hu/).
|
Once the video is fully downloaded, use any video player, such as [mpv](https://mpv.io/), [vlc](https://www.videolan.org/) or [mplayer](https://www.mplayerhq.hu/).
|
||||||
|
|
||||||
### I extracted a video URL with `-g`, but it does not play on another machine / in my web browser.
|
### I extracted a video URL with `-g`, but it does not play on another machine / in my web browser.
|
||||||
|
|
||||||
@ -845,10 +847,10 @@ Use the `-o` to specify an [output template](#output-template), for example `-o
|
|||||||
|
|
||||||
### How do I download a video starting with a `-`?
|
### How do I download a video starting with a `-`?
|
||||||
|
|
||||||
Either prepend `http://www.youtube.com/watch?v=` or separate the ID from the options with `--`:
|
Either prepend `https://www.youtube.com/watch?v=` or separate the ID from the options with `--`:
|
||||||
|
|
||||||
youtube-dl -- -wNyEUrxzFU
|
youtube-dl -- -wNyEUrxzFU
|
||||||
youtube-dl "http://www.youtube.com/watch?v=-wNyEUrxzFU"
|
youtube-dl "https://www.youtube.com/watch?v=-wNyEUrxzFU"
|
||||||
|
|
||||||
### How do I pass cookies to youtube-dl?
|
### How do I pass cookies to youtube-dl?
|
||||||
|
|
||||||
@ -862,9 +864,9 @@ Passing cookies to youtube-dl is a good way to workaround login when a particula
|
|||||||
|
|
||||||
### How do I stream directly to media player?
|
### How do I stream directly to media player?
|
||||||
|
|
||||||
You will first need to tell youtube-dl to stream media to stdout with `-o -`, and also tell your media player to read from stdin (it must be capable of this for streaming) and then pipe former to latter. For example, streaming to [vlc](http://www.videolan.org/) can be achieved with:
|
You will first need to tell youtube-dl to stream media to stdout with `-o -`, and also tell your media player to read from stdin (it must be capable of this for streaming) and then pipe former to latter. For example, streaming to [vlc](https://www.videolan.org/) can be achieved with:
|
||||||
|
|
||||||
youtube-dl -o - "http://www.youtube.com/watch?v=BaW_jenozKcj" | vlc -
|
youtube-dl -o - "https://www.youtube.com/watch?v=BaW_jenozKcj" | vlc -
|
||||||
|
|
||||||
### How do I download only new videos from a playlist?
|
### How do I download only new videos from a playlist?
|
||||||
|
|
||||||
@ -884,7 +886,7 @@ When youtube-dl detects an HLS video, it can download it either with the built-i
|
|||||||
|
|
||||||
When youtube-dl knows that one particular downloader works better for a given website, that downloader will be picked. Otherwise, youtube-dl will pick the best downloader for general compatibility, which at the moment happens to be ffmpeg. This choice may change in future versions of youtube-dl, with improvements of the built-in downloader and/or ffmpeg.
|
When youtube-dl knows that one particular downloader works better for a given website, that downloader will be picked. Otherwise, youtube-dl will pick the best downloader for general compatibility, which at the moment happens to be ffmpeg. This choice may change in future versions of youtube-dl, with improvements of the built-in downloader and/or ffmpeg.
|
||||||
|
|
||||||
In particular, the generic extractor (used when your website is not in the [list of supported sites by youtube-dl](http://rg3.github.io/youtube-dl/supportedsites.html) cannot mandate one specific downloader.
|
In particular, the generic extractor (used when your website is not in the [list of supported sites by youtube-dl](https://rg3.github.io/youtube-dl/supportedsites.html) cannot mandate one specific downloader.
|
||||||
|
|
||||||
If you put either `--hls-prefer-native` or `--hls-prefer-ffmpeg` into your configuration, a different subset of videos will fail to download correctly. Instead, it is much better to [file an issue](https://yt-dl.org/bug) or a pull request which details why the native or the ffmpeg HLS downloader is a better choice for your use case.
|
If you put either `--hls-prefer-native` or `--hls-prefer-ffmpeg` into your configuration, a different subset of videos will fail to download correctly. Instead, it is much better to [file an issue](https://yt-dl.org/bug) or a pull request which details why the native or the ffmpeg HLS downloader is a better choice for your use case.
|
||||||
|
|
||||||
@ -910,7 +912,7 @@ Feel free to bump the issue from time to time by writing a small comment ("Issue
|
|||||||
|
|
||||||
### How can I detect whether a given URL is supported by youtube-dl?
|
### How can I detect whether a given URL is supported by youtube-dl?
|
||||||
|
|
||||||
For one, have a look at the [list of supported sites](docs/supportedsites.md). Note that it can sometimes happen that the site changes its URL scheme (say, from http://example.com/video/1234567 to http://example.com/v/1234567 ) and youtube-dl reports an URL of a service in that list as unsupported. In that case, simply report a bug.
|
For one, have a look at the [list of supported sites](docs/supportedsites.md). Note that it can sometimes happen that the site changes its URL scheme (say, from https://example.com/video/1234567 to https://example.com/v/1234567 ) and youtube-dl reports an URL of a service in that list as unsupported. In that case, simply report a bug.
|
||||||
|
|
||||||
It is *not* possible to detect whether a URL is supported or not. That's because youtube-dl contains a generic extractor which matches **all** URLs. You may be tempted to disable, exclude, or remove the generic extractor, but the generic extractor not only allows users to extract videos from lots of websites that embed a video from another service, but may also be used to extract video from a service that it's hosting itself. Therefore, we neither recommend nor support disabling, excluding, or removing the generic extractor.
|
It is *not* possible to detect whether a URL is supported or not. That's because youtube-dl contains a generic extractor which matches **all** URLs. You may be tempted to disable, exclude, or remove the generic extractor, but the generic extractor not only allows users to extract videos from lots of websites that embed a video from another service, but may also be used to extract video from a service that it's hosting itself. Therefore, we neither recommend nor support disabling, excluding, or removing the generic extractor.
|
||||||
|
|
||||||
@ -924,7 +926,7 @@ youtube-dl is an open-source project manned by too few volunteers, so we'd rathe
|
|||||||
|
|
||||||
# DEVELOPER INSTRUCTIONS
|
# DEVELOPER INSTRUCTIONS
|
||||||
|
|
||||||
Most users do not need to build youtube-dl and can [download the builds](http://rg3.github.io/youtube-dl/download.html) or get them from their distribution.
|
Most users do not need to build youtube-dl and can [download the builds](https://rg3.github.io/youtube-dl/download.html) or get them from their distribution.
|
||||||
|
|
||||||
To run youtube-dl as a developer, you don't need to build anything either. Simply execute
|
To run youtube-dl as a developer, you don't need to build anything either. Simply execute
|
||||||
|
|
||||||
@ -936,6 +938,8 @@ To run the test, simply invoke your favorite test runner, or execute a test file
|
|||||||
python test/test_download.py
|
python test/test_download.py
|
||||||
nosetests
|
nosetests
|
||||||
|
|
||||||
|
See item 6 of [new extractor tutorial](#adding-support-for-a-new-site) for how to run extractor specific test cases.
|
||||||
|
|
||||||
If you want to create a build of youtube-dl yourself, you'll need
|
If you want to create a build of youtube-dl yourself, you'll need
|
||||||
|
|
||||||
* python
|
* python
|
||||||
@ -972,7 +976,7 @@ After you have ensured this site is distributing its content legally, you can fo
|
|||||||
class YourExtractorIE(InfoExtractor):
|
class YourExtractorIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?yourextractor\.com/watch/(?P<id>[0-9]+)'
|
_VALID_URL = r'https?://(?:www\.)?yourextractor\.com/watch/(?P<id>[0-9]+)'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://yourextractor.com/watch/42',
|
'url': 'https://yourextractor.com/watch/42',
|
||||||
'md5': 'TODO: md5 sum of the first 10241 bytes of the video file (use --test)',
|
'md5': 'TODO: md5 sum of the first 10241 bytes of the video file (use --test)',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '42',
|
'id': '42',
|
||||||
@ -1003,10 +1007,10 @@ After you have ensured this site is distributing its content legally, you can fo
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
5. Add an import in [`youtube_dl/extractor/extractors.py`](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/extractors.py).
|
5. Add an import in [`youtube_dl/extractor/extractors.py`](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/extractors.py).
|
||||||
6. Run `python test/test_download.py TestDownload.test_YourExtractor`. This *should fail* at first, but you can continually re-run it until you're done. If you decide to add more than one test, then rename ``_TEST`` to ``_TESTS`` and make it into a list of dictionaries. The tests will then be named `TestDownload.test_YourExtractor`, `TestDownload.test_YourExtractor_1`, `TestDownload.test_YourExtractor_2`, etc.
|
6. Run `python test/test_download.py TestDownload.test_YourExtractor`. This *should fail* at first, but you can continually re-run it until you're done. If you decide to add more than one test, then rename ``_TEST`` to ``_TESTS`` and make it into a list of dictionaries. The tests will then be named `TestDownload.test_YourExtractor`, `TestDownload.test_YourExtractor_1`, `TestDownload.test_YourExtractor_2`, etc. Note that tests with `only_matching` key in test's dict are not counted in.
|
||||||
7. Have a look at [`youtube_dl/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 and may return](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/common.py#L74-L252). Add tests and code for as many as you want.
|
7. Have a look at [`youtube_dl/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 and may return](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/common.py#L74-L252). Add tests and code for as many as you want.
|
||||||
8. Make sure your code follows [youtube-dl coding conventions](#youtube-dl-coding-conventions) and check the code with [flake8](https://pypi.python.org/pypi/flake8). Also make sure your code works under all [Python](http://www.python.org/) versions claimed supported by youtube-dl, namely 2.6, 2.7, and 3.2+.
|
8. Make sure your code follows [youtube-dl coding conventions](#youtube-dl-coding-conventions) and check the code with [flake8](https://pypi.python.org/pypi/flake8). Also make sure your code works under all [Python](https://www.python.org/) versions claimed supported by youtube-dl, namely 2.6, 2.7, and 3.2+.
|
||||||
9. When the tests pass, [add](http://git-scm.com/docs/git-add) the new files and [commit](http://git-scm.com/docs/git-commit) them and [push](http://git-scm.com/docs/git-push) the result, like this:
|
9. When the tests pass, [add](https://git-scm.com/docs/git-add) the new files and [commit](https://git-scm.com/docs/git-commit) them and [push](https://git-scm.com/docs/git-push) the result, like this:
|
||||||
|
|
||||||
$ git add youtube_dl/extractor/extractors.py
|
$ git add youtube_dl/extractor/extractors.py
|
||||||
$ git add youtube_dl/extractor/yourextractor.py
|
$ git add youtube_dl/extractor/yourextractor.py
|
||||||
@ -1162,10 +1166,10 @@ import youtube_dl
|
|||||||
|
|
||||||
ydl_opts = {}
|
ydl_opts = {}
|
||||||
with youtube_dl.YoutubeDL(ydl_opts) as ydl:
|
with youtube_dl.YoutubeDL(ydl_opts) as ydl:
|
||||||
ydl.download(['http://www.youtube.com/watch?v=BaW_jenozKc'])
|
ydl.download(['https://www.youtube.com/watch?v=BaW_jenozKc'])
|
||||||
```
|
```
|
||||||
|
|
||||||
Most likely, you'll want to use various options. For a list of options available, have a look at [`youtube_dl/YoutubeDL.py`](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/YoutubeDL.py#L129-L279). For a start, if you want to intercept youtube-dl's output, set a `logger` object.
|
Most likely, you'll want to use various options. For a list of options available, have a look at [`youtube_dl/YoutubeDL.py`](https://github.com/rg3/youtube-dl/blob/3e4cedf9e8cd3157df2457df7274d0c842421945/youtube_dl/YoutubeDL.py#L137-L312). For a start, if you want to intercept youtube-dl's output, set a `logger` object.
|
||||||
|
|
||||||
Here's a more complete example of a program that outputs only errors (and a short message after the download is finished), and downloads/converts the video to an mp3 file:
|
Here's a more complete example of a program that outputs only errors (and a short message after the download is finished), and downloads/converts the video to an mp3 file:
|
||||||
|
|
||||||
@ -1201,19 +1205,19 @@ ydl_opts = {
|
|||||||
'progress_hooks': [my_hook],
|
'progress_hooks': [my_hook],
|
||||||
}
|
}
|
||||||
with youtube_dl.YoutubeDL(ydl_opts) as ydl:
|
with youtube_dl.YoutubeDL(ydl_opts) as ydl:
|
||||||
ydl.download(['http://www.youtube.com/watch?v=BaW_jenozKc'])
|
ydl.download(['https://www.youtube.com/watch?v=BaW_jenozKc'])
|
||||||
```
|
```
|
||||||
|
|
||||||
# BUGS
|
# BUGS
|
||||||
|
|
||||||
Bugs and suggestions should be reported at: <https://github.com/rg3/youtube-dl/issues>. Unless you were prompted to or there is another pertinent reason (e.g. GitHub fails to accept the bug report), please do not send bug reports via personal email. For discussions, join us in the IRC channel [#youtube-dl](irc://chat.freenode.net/#youtube-dl) on freenode ([webchat](http://webchat.freenode.net/?randomnick=1&channels=youtube-dl)).
|
Bugs and suggestions should be reported at: <https://github.com/rg3/youtube-dl/issues>. Unless you were prompted to or there is another pertinent reason (e.g. GitHub fails to accept the bug report), please do not send bug reports via personal email. For discussions, join us in the IRC channel [#youtube-dl](irc://chat.freenode.net/#youtube-dl) on freenode ([webchat](https://webchat.freenode.net/?randomnick=1&channels=youtube-dl)).
|
||||||
|
|
||||||
**Please include the full output of youtube-dl when run with `-v`**, i.e. **add** `-v` flag to **your command line**, copy the **whole** output and post it in the issue body wrapped in \`\`\` for better formatting. It should look similar to this:
|
**Please include the full output of youtube-dl when run with `-v`**, i.e. **add** `-v` flag to **your command line**, copy the **whole** output and post it in the issue body wrapped in \`\`\` for better formatting. It should look similar to this:
|
||||||
```
|
```
|
||||||
$ youtube-dl -v <your command line>
|
$ youtube-dl -v <your command line>
|
||||||
[debug] System config: []
|
[debug] System config: []
|
||||||
[debug] User config: []
|
[debug] User config: []
|
||||||
[debug] Command-line args: [u'-v', u'http://www.youtube.com/watch?v=BaW_jenozKcj']
|
[debug] Command-line args: [u'-v', u'https://www.youtube.com/watch?v=BaW_jenozKcj']
|
||||||
[debug] Encodings: locale cp1251, fs mbcs, out cp866, pref cp1251
|
[debug] Encodings: locale cp1251, fs mbcs, out cp866, pref cp1251
|
||||||
[debug] youtube-dl version 2015.12.06
|
[debug] youtube-dl version 2015.12.06
|
||||||
[debug] Git HEAD: 135392e
|
[debug] Git HEAD: 135392e
|
||||||
@ -1244,7 +1248,7 @@ For bug reports, this means that your report should contain the *complete* outpu
|
|||||||
|
|
||||||
If your server has multiple IPs or you suspect censorship, adding `--call-home` may be a good idea to get more diagnostics. If the error is `ERROR: Unable to extract ...` and you cannot reproduce it from multiple countries, add `--dump-pages` (warning: this will yield a rather large output, redirect it to the file `log.txt` by adding `>log.txt 2>&1` to your command-line) or upload the `.dump` files you get when you add `--write-pages` [somewhere](https://gist.github.com/).
|
If your server has multiple IPs or you suspect censorship, adding `--call-home` may be a good idea to get more diagnostics. If the error is `ERROR: Unable to extract ...` and you cannot reproduce it from multiple countries, add `--dump-pages` (warning: this will yield a rather large output, redirect it to the file `log.txt` by adding `>log.txt 2>&1` to your command-line) or upload the `.dump` files you get when you add `--write-pages` [somewhere](https://gist.github.com/).
|
||||||
|
|
||||||
**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 `https://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. `https://www.youtube.com/`) is *not* an example URL.
|
||||||
|
|
||||||
### Are you using the latest version?
|
### Are you using the latest version?
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
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 get_testcases
|
from test.helper import gettestcases
|
||||||
from youtube_dl.utils import compat_urllib_parse_urlparse
|
from youtube_dl.utils import compat_urllib_parse_urlparse
|
||||||
from youtube_dl.utils import compat_urllib_request
|
from youtube_dl.utils import compat_urllib_request
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ if len(sys.argv) > 1:
|
|||||||
else:
|
else:
|
||||||
METHOD = 'EURISTIC'
|
METHOD = 'EURISTIC'
|
||||||
|
|
||||||
for test in get_testcases():
|
for test in gettestcases():
|
||||||
if METHOD == 'EURISTIC':
|
if METHOD == 'EURISTIC':
|
||||||
try:
|
try:
|
||||||
webpage = compat_urllib_request.urlopen(test['url'], timeout=10).read()
|
webpage = compat_urllib_request.urlopen(test['url'], timeout=10).read()
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
DOWNLOAD_TESTS="age_restriction|download|subtitles|write_annotations|iqiyi_sdk_interpreter|youtube_lists"
|
# Keep this list in sync with the `offlinetest` target in Makefile
|
||||||
|
DOWNLOAD_TESTS="age_restriction|download|iqiyi_sdk_interpreter|socks|subtitles|write_annotations|youtube_lists|youtube_signature"
|
||||||
|
|
||||||
test_set=""
|
test_set=""
|
||||||
multiprocess_args=""
|
multiprocess_args=""
|
||||||
|
@ -3,8 +3,6 @@
|
|||||||
- **1up.com**
|
- **1up.com**
|
||||||
- **20min**
|
- **20min**
|
||||||
- **220.ro**
|
- **220.ro**
|
||||||
- **22tracks:genre**
|
|
||||||
- **22tracks:track**
|
|
||||||
- **24video**
|
- **24video**
|
||||||
- **3qsdn**: 3Q SDN
|
- **3qsdn**: 3Q SDN
|
||||||
- **3sat**
|
- **3sat**
|
||||||
@ -36,13 +34,14 @@
|
|||||||
- **AdultSwim**
|
- **AdultSwim**
|
||||||
- **aenetworks**: A+E Networks: A&E, Lifetime, History.com, FYI Network
|
- **aenetworks**: A+E Networks: A&E, Lifetime, History.com, FYI Network
|
||||||
- **afreecatv**: afreecatv.com
|
- **afreecatv**: afreecatv.com
|
||||||
- **afreecatv:global**: afreecatv.com
|
|
||||||
- **AirMozilla**
|
- **AirMozilla**
|
||||||
|
- **AliExpressLive**
|
||||||
- **AlJazeera**
|
- **AlJazeera**
|
||||||
- **Allocine**
|
- **Allocine**
|
||||||
- **AlphaPorno**
|
- **AlphaPorno**
|
||||||
- **AMCNetworks**
|
- **AMCNetworks**
|
||||||
- **anderetijden**: npo.nl and ntr.nl
|
- **AmericasTestKitchen**
|
||||||
|
- **anderetijden**: npo.nl, ntr.nl, omroepwnl.nl, zapp.nl and npo3.nl
|
||||||
- **AnimeOnDemand**
|
- **AnimeOnDemand**
|
||||||
- **anitube.se**
|
- **anitube.se**
|
||||||
- **Anvato**
|
- **Anvato**
|
||||||
@ -113,11 +112,12 @@
|
|||||||
- **BokeCC**
|
- **BokeCC**
|
||||||
- **BostonGlobe**
|
- **BostonGlobe**
|
||||||
- **Bpb**: Bundeszentrale für politische Bildung
|
- **Bpb**: Bundeszentrale für politische Bildung
|
||||||
- **BR**: Bayerischer Rundfunk Mediathek
|
- **BR**: Bayerischer Rundfunk
|
||||||
- **BravoTV**
|
- **BravoTV**
|
||||||
- **Break**
|
- **Break**
|
||||||
- **brightcove:legacy**
|
- **brightcove:legacy**
|
||||||
- **brightcove:new**
|
- **brightcove:new**
|
||||||
|
- **BRMediathek**: Bayerischer Rundfunk Mediathek
|
||||||
- **bt:article**: Bergens Tidende Articles
|
- **bt:article**: Bergens Tidende Articles
|
||||||
- **bt:vestlendingen**: Bergens Tidende - Vestlendingen
|
- **bt:vestlendingen**: Bergens Tidende - Vestlendingen
|
||||||
- **BuzzFeed**
|
- **BuzzFeed**
|
||||||
@ -128,7 +128,8 @@
|
|||||||
- **CamWithHer**
|
- **CamWithHer**
|
||||||
- **canalc2.tv**
|
- **canalc2.tv**
|
||||||
- **Canalplus**: canalplus.fr, piwiplus.fr and d8.tv
|
- **Canalplus**: canalplus.fr, piwiplus.fr and d8.tv
|
||||||
- **Canvas**: canvas.be and een.be
|
- **Canvas**
|
||||||
|
- **CanvasEen**: canvas.be and een.be
|
||||||
- **CarambaTV**
|
- **CarambaTV**
|
||||||
- **CarambaTVPage**
|
- **CarambaTVPage**
|
||||||
- **CartoonNetwork**
|
- **CartoonNetwork**
|
||||||
@ -154,8 +155,9 @@
|
|||||||
- **chirbit**
|
- **chirbit**
|
||||||
- **chirbit:profile**
|
- **chirbit:profile**
|
||||||
- **Cinchcast**
|
- **Cinchcast**
|
||||||
- **Clipfish**
|
- **CJSW**
|
||||||
- **cliphunter**
|
- **cliphunter**
|
||||||
|
- **Clippit**
|
||||||
- **ClipRs**
|
- **ClipRs**
|
||||||
- **Clipsyndicate**
|
- **Clipsyndicate**
|
||||||
- **CloserToTruth**
|
- **CloserToTruth**
|
||||||
@ -197,8 +199,8 @@
|
|||||||
- **dailymotion:playlist**
|
- **dailymotion:playlist**
|
||||||
- **dailymotion:user**
|
- **dailymotion:user**
|
||||||
- **DailymotionCloud**
|
- **DailymotionCloud**
|
||||||
- **Daisuki**
|
- **DaisukiMotto**
|
||||||
- **DaisukiPlaylist**
|
- **DaisukiMottoPlaylist**
|
||||||
- **daum.net**
|
- **daum.net**
|
||||||
- **daum.net:clip**
|
- **daum.net:clip**
|
||||||
- **daum.net:playlist**
|
- **daum.net:playlist**
|
||||||
@ -237,6 +239,7 @@
|
|||||||
- **EbaumsWorld**
|
- **EbaumsWorld**
|
||||||
- **EchoMsk**
|
- **EchoMsk**
|
||||||
- **egghead:course**: egghead.io course
|
- **egghead:course**: egghead.io course
|
||||||
|
- **egghead:lesson**: egghead.io lesson
|
||||||
- **eHow**
|
- **eHow**
|
||||||
- **Einthusan**
|
- **Einthusan**
|
||||||
- **eitb.tv**
|
- **eitb.tv**
|
||||||
@ -264,10 +267,8 @@
|
|||||||
- **fc2**
|
- **fc2**
|
||||||
- **fc2:embed**
|
- **fc2:embed**
|
||||||
- **Fczenit**
|
- **Fczenit**
|
||||||
- **fernsehkritik.tv**
|
|
||||||
- **filmon**
|
- **filmon**
|
||||||
- **filmon:channel**
|
- **filmon:channel**
|
||||||
- **Firstpost**
|
|
||||||
- **FiveTV**
|
- **FiveTV**
|
||||||
- **Flickr**
|
- **Flickr**
|
||||||
- **Flipagram**
|
- **Flipagram**
|
||||||
@ -281,7 +282,7 @@
|
|||||||
- **foxnews:article**
|
- **foxnews:article**
|
||||||
- **foxnews:insider**
|
- **foxnews:insider**
|
||||||
- **FoxSports**
|
- **FoxSports**
|
||||||
- **france2.fr:generation-quoi**
|
- **france2.fr:generation-what**
|
||||||
- **FranceCulture**
|
- **FranceCulture**
|
||||||
- **FranceInter**
|
- **FranceInter**
|
||||||
- **FranceTV**
|
- **FranceTV**
|
||||||
@ -291,13 +292,14 @@
|
|||||||
- **freespeech.org**
|
- **freespeech.org**
|
||||||
- **FreshLive**
|
- **FreshLive**
|
||||||
- **Funimation**
|
- **Funimation**
|
||||||
|
- **Funk**
|
||||||
- **FunnyOrDie**
|
- **FunnyOrDie**
|
||||||
- **Fusion**
|
- **Fusion**
|
||||||
|
- **Fux**
|
||||||
- **FXNetworks**
|
- **FXNetworks**
|
||||||
- **GameInformer**
|
- **GameInformer**
|
||||||
- **GameOne**
|
- **GameOne**
|
||||||
- **gameone:playlist**
|
- **gameone:playlist**
|
||||||
- **Gamersyde**
|
|
||||||
- **GameSpot**
|
- **GameSpot**
|
||||||
- **GameStar**
|
- **GameStar**
|
||||||
- **Gaskrank**
|
- **Gaskrank**
|
||||||
@ -336,6 +338,7 @@
|
|||||||
- **HornBunny**
|
- **HornBunny**
|
||||||
- **HotNewHipHop**
|
- **HotNewHipHop**
|
||||||
- **HotStar**
|
- **HotStar**
|
||||||
|
- **hotstar:playlist**
|
||||||
- **Howcast**
|
- **Howcast**
|
||||||
- **HowStuffWorks**
|
- **HowStuffWorks**
|
||||||
- **HRTi**
|
- **HRTi**
|
||||||
@ -360,6 +363,7 @@
|
|||||||
- **IPrima**
|
- **IPrima**
|
||||||
- **iqiyi**: 爱奇艺
|
- **iqiyi**: 爱奇艺
|
||||||
- **Ir90Tv**
|
- **Ir90Tv**
|
||||||
|
- **ITTF**
|
||||||
- **ITV**
|
- **ITV**
|
||||||
- **ivi**: ivi.ru
|
- **ivi**: ivi.ru
|
||||||
- **ivi:compilation**: ivi.ru compilations
|
- **ivi:compilation**: ivi.ru compilations
|
||||||
@ -369,9 +373,11 @@
|
|||||||
- **Jamendo**
|
- **Jamendo**
|
||||||
- **JamendoAlbum**
|
- **JamendoAlbum**
|
||||||
- **JeuxVideo**
|
- **JeuxVideo**
|
||||||
|
- **Joj**
|
||||||
- **Jove**
|
- **Jove**
|
||||||
- **jpopsuki.tv**
|
- **jpopsuki.tv**
|
||||||
- **JWPlatform**
|
- **JWPlatform**
|
||||||
|
- **Kakao**
|
||||||
- **Kaltura**
|
- **Kaltura**
|
||||||
- **Kamcord**
|
- **Kamcord**
|
||||||
- **KanalPlay**: Kanal 5/9/11 Play
|
- **KanalPlay**: Kanal 5/9/11 Play
|
||||||
@ -415,6 +421,7 @@
|
|||||||
- **limelight:channel_list**
|
- **limelight:channel_list**
|
||||||
- **LiTV**
|
- **LiTV**
|
||||||
- **LiveLeak**
|
- **LiveLeak**
|
||||||
|
- **LiveLeakEmbed**
|
||||||
- **livestream**
|
- **livestream**
|
||||||
- **livestream:original**
|
- **livestream:original**
|
||||||
- **LnkGo**
|
- **LnkGo**
|
||||||
@ -431,12 +438,15 @@
|
|||||||
- **MakerTV**
|
- **MakerTV**
|
||||||
- **mangomolo:live**
|
- **mangomolo:live**
|
||||||
- **mangomolo:video**
|
- **mangomolo:video**
|
||||||
|
- **ManyVids**
|
||||||
|
- **massengeschmack.tv**
|
||||||
- **MatchTV**
|
- **MatchTV**
|
||||||
- **MDR**: MDR.DE and KiKA
|
- **MDR**: MDR.DE and KiKA
|
||||||
- **media.ccc.de**
|
- **media.ccc.de**
|
||||||
- **Medialaan**
|
- **Medialaan**
|
||||||
- **Mediaset**
|
- **Mediaset**
|
||||||
- **Medici**
|
- **Medici**
|
||||||
|
- **megaphone.fm**: megaphone.fm embedded players
|
||||||
- **Meipai**: 美拍
|
- **Meipai**: 美拍
|
||||||
- **MelonVOD**
|
- **MelonVOD**
|
||||||
- **META**
|
- **META**
|
||||||
@ -469,7 +479,6 @@
|
|||||||
- **MovieFap**
|
- **MovieFap**
|
||||||
- **Moviezine**
|
- **Moviezine**
|
||||||
- **MovingImage**
|
- **MovingImage**
|
||||||
- **MPORA**
|
|
||||||
- **MSN**
|
- **MSN**
|
||||||
- **mtg**: MTG services
|
- **mtg**: MTG services
|
||||||
- **mtv**
|
- **mtv**
|
||||||
@ -487,7 +496,6 @@
|
|||||||
- **MySpace:album**
|
- **MySpace:album**
|
||||||
- **MySpass**
|
- **MySpass**
|
||||||
- **Myvi**
|
- **Myvi**
|
||||||
- **myvideo** (Currently broken)
|
|
||||||
- **MyVidster**
|
- **MyVidster**
|
||||||
- **n-tv.de**
|
- **n-tv.de**
|
||||||
- **natgeo**
|
- **natgeo**
|
||||||
@ -519,6 +527,8 @@
|
|||||||
- **NextMedia**: 蘋果日報
|
- **NextMedia**: 蘋果日報
|
||||||
- **NextMediaActionNews**: 蘋果日報 - 動新聞
|
- **NextMediaActionNews**: 蘋果日報 - 動新聞
|
||||||
- **NextTV**: 壹電視
|
- **NextTV**: 壹電視
|
||||||
|
- **Nexx**
|
||||||
|
- **NexxEmbed**
|
||||||
- **nfb**: National Film Board of Canada
|
- **nfb**: National Film Board of Canada
|
||||||
- **nfl.com**
|
- **nfl.com**
|
||||||
- **NhkVod**
|
- **NhkVod**
|
||||||
@ -528,6 +538,7 @@
|
|||||||
- **nhl.com:videocenter:category**: NHL videocenter category
|
- **nhl.com:videocenter:category**: NHL videocenter category
|
||||||
- **nick.com**
|
- **nick.com**
|
||||||
- **nick.de**
|
- **nick.de**
|
||||||
|
- **nickelodeonru**
|
||||||
- **nicknight**
|
- **nicknight**
|
||||||
- **niconico**: ニコニコ動画
|
- **niconico**: ニコニコ動画
|
||||||
- **NiconicoPlaylist**
|
- **NiconicoPlaylist**
|
||||||
@ -549,7 +560,7 @@
|
|||||||
- **NowTVList**
|
- **NowTVList**
|
||||||
- **nowvideo**: NowVideo
|
- **nowvideo**: NowVideo
|
||||||
- **Noz**
|
- **Noz**
|
||||||
- **npo**: npo.nl and ntr.nl
|
- **npo**: npo.nl, ntr.nl, omroepwnl.nl, zapp.nl and npo3.nl
|
||||||
- **npo.nl:live**
|
- **npo.nl:live**
|
||||||
- **npo.nl:radio**
|
- **npo.nl:radio**
|
||||||
- **npo.nl:radio:fragment**
|
- **npo.nl:radio:fragment**
|
||||||
@ -582,6 +593,7 @@
|
|||||||
- **Openload**
|
- **Openload**
|
||||||
- **OraTV**
|
- **OraTV**
|
||||||
- **orf:fm4**: radio FM4
|
- **orf:fm4**: radio FM4
|
||||||
|
- **orf:fm4:story**: fm4.orf.at stories
|
||||||
- **orf:iptv**: iptv.ORF.at
|
- **orf:iptv**: iptv.ORF.at
|
||||||
- **orf:oe1**: Radio Österreich 1
|
- **orf:oe1**: Radio Österreich 1
|
||||||
- **orf:tvthek**: ORF TVthek
|
- **orf:tvthek**: ORF TVthek
|
||||||
@ -593,7 +605,9 @@
|
|||||||
- **Patreon**
|
- **Patreon**
|
||||||
- **pbs**: Public Broadcasting Service (PBS) and member stations: PBS: Public Broadcasting Service, APT - Alabama Public Television (WBIQ), GPB/Georgia Public Broadcasting (WGTV), Mississippi Public Broadcasting (WMPN), Nashville Public Television (WNPT), WFSU-TV (WFSU), WSRE (WSRE), WTCI (WTCI), WPBA/Channel 30 (WPBA), Alaska Public Media (KAKM), Arizona PBS (KAET), KNME-TV/Channel 5 (KNME), Vegas PBS (KLVX), AETN/ARKANSAS ETV NETWORK (KETS), KET (WKLE), WKNO/Channel 10 (WKNO), LPB/LOUISIANA PUBLIC BROADCASTING (WLPB), OETA (KETA), Ozarks Public Television (KOZK), WSIU Public Broadcasting (WSIU), KEET TV (KEET), KIXE/Channel 9 (KIXE), KPBS San Diego (KPBS), KQED (KQED), KVIE Public Television (KVIE), PBS SoCal/KOCE (KOCE), ValleyPBS (KVPT), CONNECTICUT PUBLIC TELEVISION (WEDH), KNPB Channel 5 (KNPB), SOPTV (KSYS), Rocky Mountain PBS (KRMA), KENW-TV3 (KENW), KUED Channel 7 (KUED), Wyoming PBS (KCWC), Colorado Public Television / KBDI 12 (KBDI), KBYU-TV (KBYU), Thirteen/WNET New York (WNET), WGBH/Channel 2 (WGBH), WGBY (WGBY), NJTV Public Media NJ (WNJT), WLIW21 (WLIW), mpt/Maryland Public Television (WMPB), WETA Television and Radio (WETA), WHYY (WHYY), PBS 39 (WLVT), WVPT - Your Source for PBS and More! (WVPT), Howard University Television (WHUT), WEDU PBS (WEDU), WGCU Public Media (WGCU), WPBT2 (WPBT), WUCF TV (WUCF), WUFT/Channel 5 (WUFT), WXEL/Channel 42 (WXEL), WLRN/Channel 17 (WLRN), WUSF Public Broadcasting (WUSF), ETV (WRLK), UNC-TV (WUNC), PBS Hawaii - Oceanic Cable Channel 10 (KHET), Idaho Public Television (KAID), KSPS (KSPS), OPB (KOPB), KWSU/Channel 10 & KTNW/Channel 31 (KWSU), WILL-TV (WILL), Network Knowledge - WSEC/Springfield (WSEC), WTTW11 (WTTW), Iowa Public Television/IPTV (KDIN), Nine Network (KETC), PBS39 Fort Wayne (WFWA), WFYI Indianapolis (WFYI), Milwaukee Public Television (WMVS), WNIN (WNIN), WNIT Public Television (WNIT), WPT (WPNE), WVUT/Channel 22 (WVUT), WEIU/Channel 51 (WEIU), WQPT-TV (WQPT), WYCC PBS Chicago (WYCC), WIPB-TV (WIPB), WTIU (WTIU), CET (WCET), ThinkTVNetwork (WPTD), WBGU-TV (WBGU), WGVU TV (WGVU), NET1 (KUON), Pioneer Public Television (KWCM), SDPB Television (KUSD), TPT (KTCA), KSMQ (KSMQ), KPTS/Channel 8 (KPTS), KTWU/Channel 11 (KTWU), East Tennessee PBS (WSJK), WCTE-TV (WCTE), WLJT, Channel 11 (WLJT), WOSU TV (WOSU), WOUB/WOUC (WOUB), WVPB (WVPB), WKYU-PBS (WKYU), KERA 13 (KERA), MPBN (WCBB), Mountain Lake PBS (WCFE), NHPTV (WENH), Vermont PBS (WETK), witf (WITF), WQED Multimedia (WQED), WMHT Educational Telecommunications (WMHT), Q-TV (WDCQ), WTVS Detroit Public TV (WTVS), CMU Public Television (WCMU), WKAR-TV (WKAR), WNMU-TV Public TV 13 (WNMU), WDSE - WRPT (WDSE), WGTE TV (WGTE), Lakeland Public Television (KAWE), KMOS-TV - Channels 6.1, 6.2 and 6.3 (KMOS), MontanaPBS (KUSM), KRWG/Channel 22 (KRWG), KACV (KACV), KCOS/Channel 13 (KCOS), WCNY/Channel 24 (WCNY), WNED (WNED), WPBS (WPBS), WSKG Public TV (WSKG), WXXI (WXXI), WPSU (WPSU), WVIA Public Media Studios (WVIA), WTVI (WTVI), Western Reserve PBS (WNEO), WVIZ/PBS ideastream (WVIZ), KCTS 9 (KCTS), Basin PBS (KPBT), KUHT / Channel 8 (KUHT), KLRN (KLRN), KLRU (KLRU), WTJX Channel 12 (WTJX), WCVE PBS (WCVE), KBTC Public Television (KBTC)
|
- **pbs**: Public Broadcasting Service (PBS) and member stations: PBS: Public Broadcasting Service, APT - Alabama Public Television (WBIQ), GPB/Georgia Public Broadcasting (WGTV), Mississippi Public Broadcasting (WMPN), Nashville Public Television (WNPT), WFSU-TV (WFSU), WSRE (WSRE), WTCI (WTCI), WPBA/Channel 30 (WPBA), Alaska Public Media (KAKM), Arizona PBS (KAET), KNME-TV/Channel 5 (KNME), Vegas PBS (KLVX), AETN/ARKANSAS ETV NETWORK (KETS), KET (WKLE), WKNO/Channel 10 (WKNO), LPB/LOUISIANA PUBLIC BROADCASTING (WLPB), OETA (KETA), Ozarks Public Television (KOZK), WSIU Public Broadcasting (WSIU), KEET TV (KEET), KIXE/Channel 9 (KIXE), KPBS San Diego (KPBS), KQED (KQED), KVIE Public Television (KVIE), PBS SoCal/KOCE (KOCE), ValleyPBS (KVPT), CONNECTICUT PUBLIC TELEVISION (WEDH), KNPB Channel 5 (KNPB), SOPTV (KSYS), Rocky Mountain PBS (KRMA), KENW-TV3 (KENW), KUED Channel 7 (KUED), Wyoming PBS (KCWC), Colorado Public Television / KBDI 12 (KBDI), KBYU-TV (KBYU), Thirteen/WNET New York (WNET), WGBH/Channel 2 (WGBH), WGBY (WGBY), NJTV Public Media NJ (WNJT), WLIW21 (WLIW), mpt/Maryland Public Television (WMPB), WETA Television and Radio (WETA), WHYY (WHYY), PBS 39 (WLVT), WVPT - Your Source for PBS and More! (WVPT), Howard University Television (WHUT), WEDU PBS (WEDU), WGCU Public Media (WGCU), WPBT2 (WPBT), WUCF TV (WUCF), WUFT/Channel 5 (WUFT), WXEL/Channel 42 (WXEL), WLRN/Channel 17 (WLRN), WUSF Public Broadcasting (WUSF), ETV (WRLK), UNC-TV (WUNC), PBS Hawaii - Oceanic Cable Channel 10 (KHET), Idaho Public Television (KAID), KSPS (KSPS), OPB (KOPB), KWSU/Channel 10 & KTNW/Channel 31 (KWSU), WILL-TV (WILL), Network Knowledge - WSEC/Springfield (WSEC), WTTW11 (WTTW), Iowa Public Television/IPTV (KDIN), Nine Network (KETC), PBS39 Fort Wayne (WFWA), WFYI Indianapolis (WFYI), Milwaukee Public Television (WMVS), WNIN (WNIN), WNIT Public Television (WNIT), WPT (WPNE), WVUT/Channel 22 (WVUT), WEIU/Channel 51 (WEIU), WQPT-TV (WQPT), WYCC PBS Chicago (WYCC), WIPB-TV (WIPB), WTIU (WTIU), CET (WCET), ThinkTVNetwork (WPTD), WBGU-TV (WBGU), WGVU TV (WGVU), NET1 (KUON), Pioneer Public Television (KWCM), SDPB Television (KUSD), TPT (KTCA), KSMQ (KSMQ), KPTS/Channel 8 (KPTS), KTWU/Channel 11 (KTWU), East Tennessee PBS (WSJK), WCTE-TV (WCTE), WLJT, Channel 11 (WLJT), WOSU TV (WOSU), WOUB/WOUC (WOUB), WVPB (WVPB), WKYU-PBS (WKYU), KERA 13 (KERA), MPBN (WCBB), Mountain Lake PBS (WCFE), NHPTV (WENH), Vermont PBS (WETK), witf (WITF), WQED Multimedia (WQED), WMHT Educational Telecommunications (WMHT), Q-TV (WDCQ), WTVS Detroit Public TV (WTVS), CMU Public Television (WCMU), WKAR-TV (WKAR), WNMU-TV Public TV 13 (WNMU), WDSE - WRPT (WDSE), WGTE TV (WGTE), Lakeland Public Television (KAWE), KMOS-TV - Channels 6.1, 6.2 and 6.3 (KMOS), MontanaPBS (KUSM), KRWG/Channel 22 (KRWG), KACV (KACV), KCOS/Channel 13 (KCOS), WCNY/Channel 24 (WCNY), WNED (WNED), WPBS (WPBS), WSKG Public TV (WSKG), WXXI (WXXI), WPSU (WPSU), WVIA Public Media Studios (WVIA), WTVI (WTVI), Western Reserve PBS (WNEO), WVIZ/PBS ideastream (WVIZ), KCTS 9 (KCTS), Basin PBS (KPBT), KUHT / Channel 8 (KUHT), KLRN (KLRN), KLRU (KLRU), WTJX Channel 12 (WTJX), WCVE PBS (WCVE), KBTC Public Television (KBTC)
|
||||||
- **pcmag**
|
- **pcmag**
|
||||||
|
- **PearVideo**
|
||||||
- **People**
|
- **People**
|
||||||
|
- **PerformGroup**
|
||||||
- **periscope**: Periscope
|
- **periscope**: Periscope
|
||||||
- **periscope:user**: Periscope user videos
|
- **periscope:user**: Periscope user videos
|
||||||
- **PhilharmonieDeParis**: Philharmonie de Paris
|
- **PhilharmonieDeParis**: Philharmonie de Paris
|
||||||
@ -614,7 +628,9 @@
|
|||||||
- **Pokemon**
|
- **Pokemon**
|
||||||
- **PolskieRadio**
|
- **PolskieRadio**
|
||||||
- **PolskieRadioCategory**
|
- **PolskieRadioCategory**
|
||||||
|
- **PopcornTV**
|
||||||
- **PornCom**
|
- **PornCom**
|
||||||
|
- **PornerBros**
|
||||||
- **PornFlip**
|
- **PornFlip**
|
||||||
- **PornHd**
|
- **PornHd**
|
||||||
- **PornHub**: PornHub and Thumbzilla
|
- **PornHub**: PornHub and Thumbzilla
|
||||||
@ -623,6 +639,7 @@
|
|||||||
- **Pornotube**
|
- **Pornotube**
|
||||||
- **PornoVoisines**
|
- **PornoVoisines**
|
||||||
- **PornoXO**
|
- **PornoXO**
|
||||||
|
- **PornTube**
|
||||||
- **PressTV**
|
- **PressTV**
|
||||||
- **PrimeShareTV**
|
- **PrimeShareTV**
|
||||||
- **PromptFile**
|
- **PromptFile**
|
||||||
@ -644,9 +661,12 @@
|
|||||||
- **RadioJavan**
|
- **RadioJavan**
|
||||||
- **Rai**
|
- **Rai**
|
||||||
- **RaiPlay**
|
- **RaiPlay**
|
||||||
|
- **RaiPlayLive**
|
||||||
- **RBMARadio**
|
- **RBMARadio**
|
||||||
- **RDS**: RDS.ca
|
- **RDS**: RDS.ca
|
||||||
- **RedBullTV**
|
- **RedBullTV**
|
||||||
|
- **Reddit**
|
||||||
|
- **RedditR**
|
||||||
- **RedTube**
|
- **RedTube**
|
||||||
- **RegioTV**
|
- **RegioTV**
|
||||||
- **RENTV**
|
- **RENTV**
|
||||||
@ -686,6 +706,7 @@
|
|||||||
- **rutube:embed**: Rutube embedded videos
|
- **rutube:embed**: Rutube embedded videos
|
||||||
- **rutube:movie**: Rutube movies
|
- **rutube:movie**: Rutube movies
|
||||||
- **rutube:person**: Rutube person videos
|
- **rutube:person**: Rutube person videos
|
||||||
|
- **rutube:playlist**: Rutube playlists
|
||||||
- **RUTV**: RUTV.RU
|
- **RUTV**: RUTV.RU
|
||||||
- **Ruutu**
|
- **Ruutu**
|
||||||
- **Ruv**
|
- **Ruv**
|
||||||
@ -705,6 +726,7 @@
|
|||||||
- **SenateISVP**
|
- **SenateISVP**
|
||||||
- **SendtoNews**
|
- **SendtoNews**
|
||||||
- **ServingSys**
|
- **ServingSys**
|
||||||
|
- **Servus**
|
||||||
- **Sexu**
|
- **Sexu**
|
||||||
- **Shahid**
|
- **Shahid**
|
||||||
- **Shared**: shared.sx
|
- **Shared**: shared.sx
|
||||||
@ -715,6 +737,7 @@
|
|||||||
- **skynewsarabia:video**
|
- **skynewsarabia:video**
|
||||||
- **SkySports**
|
- **SkySports**
|
||||||
- **Slideshare**
|
- **Slideshare**
|
||||||
|
- **SlidesLive**
|
||||||
- **Slutload**
|
- **Slutload**
|
||||||
- **smotri**: Smotri.com
|
- **smotri**: Smotri.com
|
||||||
- **smotri:broadcast**: Smotri.com broadcasts
|
- **smotri:broadcast**: Smotri.com broadcasts
|
||||||
@ -727,6 +750,7 @@
|
|||||||
- **soundcloud:playlist**
|
- **soundcloud:playlist**
|
||||||
- **soundcloud:search**: Soundcloud search
|
- **soundcloud:search**: Soundcloud search
|
||||||
- **soundcloud:set**
|
- **soundcloud:set**
|
||||||
|
- **soundcloud:trackstation**
|
||||||
- **soundcloud:user**
|
- **soundcloud:user**
|
||||||
- **soundgasm**
|
- **soundgasm**
|
||||||
- **soundgasm:profile**
|
- **soundgasm:profile**
|
||||||
@ -767,13 +791,13 @@
|
|||||||
- **Tagesschau**
|
- **Tagesschau**
|
||||||
- **tagesschau:player**
|
- **tagesschau:player**
|
||||||
- **Tass**
|
- **Tass**
|
||||||
- **TBS**
|
- **TastyTrade**
|
||||||
|
- **TBS** (Currently broken)
|
||||||
- **TDSLifeway**
|
- **TDSLifeway**
|
||||||
- **teachertube**: teachertube.com videos
|
- **teachertube**: teachertube.com videos
|
||||||
- **teachertube:user:collection**: teachertube.com user and collection videos
|
- **teachertube:user:collection**: teachertube.com user and collection videos
|
||||||
- **TeachingChannel**
|
- **TeachingChannel**
|
||||||
- **Teamcoco**
|
- **Teamcoco**
|
||||||
- **TeamFourStar**
|
|
||||||
- **TechTalks**
|
- **TechTalks**
|
||||||
- **techtv.mit.edu**
|
- **techtv.mit.edu**
|
||||||
- **ted**
|
- **ted**
|
||||||
@ -862,6 +886,7 @@
|
|||||||
- **UDNEmbed**: 聯合影音
|
- **UDNEmbed**: 聯合影音
|
||||||
- **UKTVPlay**
|
- **UKTVPlay**
|
||||||
- **Unistra**
|
- **Unistra**
|
||||||
|
- **Unity**
|
||||||
- **uol.com.br**
|
- **uol.com.br**
|
||||||
- **uplynk**
|
- **uplynk**
|
||||||
- **uplynk:preplay**
|
- **uplynk:preplay**
|
||||||
@ -938,15 +963,19 @@
|
|||||||
- **vk:wallpost**
|
- **vk:wallpost**
|
||||||
- **vlive**
|
- **vlive**
|
||||||
- **vlive:channel**
|
- **vlive:channel**
|
||||||
|
- **vlive:playlist**
|
||||||
- **Vodlocker**
|
- **Vodlocker**
|
||||||
- **VODPl**
|
- **VODPl**
|
||||||
- **VODPlatform**
|
- **VODPlatform**
|
||||||
- **VoiceRepublic**
|
- **VoiceRepublic**
|
||||||
|
- **Voot**
|
||||||
- **VoxMedia**
|
- **VoxMedia**
|
||||||
|
- **VoxMediaVolume**
|
||||||
- **Vporn**
|
- **Vporn**
|
||||||
- **vpro**: npo.nl and ntr.nl
|
- **vpro**: npo.nl, ntr.nl, omroepwnl.nl, zapp.nl and npo3.nl
|
||||||
- **Vrak**
|
- **Vrak**
|
||||||
- **VRT**: deredactie.be, sporza.be, cobra.be and cobra.canvas.be
|
- **VRT**: deredactie.be, sporza.be, cobra.be and cobra.canvas.be
|
||||||
|
- **VrtNU**: VrtNU.be
|
||||||
- **vrv**
|
- **vrv**
|
||||||
- **vrv:series**
|
- **vrv:series**
|
||||||
- **VShare**
|
- **VShare**
|
||||||
@ -959,6 +988,7 @@
|
|||||||
- **washingtonpost**
|
- **washingtonpost**
|
||||||
- **washingtonpost:article**
|
- **washingtonpost:article**
|
||||||
- **wat.tv**
|
- **wat.tv**
|
||||||
|
- **WatchBox**
|
||||||
- **WatchIndianPorn**: Watch Indian Porn
|
- **WatchIndianPorn**: Watch Indian Porn
|
||||||
- **WDR**
|
- **WDR**
|
||||||
- **wdr:mobile**
|
- **wdr:mobile**
|
||||||
@ -970,7 +1000,7 @@
|
|||||||
- **wholecloud**: WholeCloud
|
- **wholecloud**: WholeCloud
|
||||||
- **Wimp**
|
- **Wimp**
|
||||||
- **Wistia**
|
- **Wistia**
|
||||||
- **wnl**: npo.nl and ntr.nl
|
- **wnl**: npo.nl, ntr.nl, omroepwnl.nl, zapp.nl and npo3.nl
|
||||||
- **WorldStarHipHop**
|
- **WorldStarHipHop**
|
||||||
- **wrzuta.pl**
|
- **wrzuta.pl**
|
||||||
- **wrzuta.pl:playlist**
|
- **wrzuta.pl:playlist**
|
||||||
@ -994,7 +1024,7 @@
|
|||||||
- **XVideos**
|
- **XVideos**
|
||||||
- **XXXYMovies**
|
- **XXXYMovies**
|
||||||
- **Yahoo**: Yahoo screen and movies
|
- **Yahoo**: Yahoo screen and movies
|
||||||
- **Yam**: 蕃薯藤yam天空部落
|
- **YandexDisk**
|
||||||
- **yandexmusic:album**: Яндекс.Музыка - Альбом
|
- **yandexmusic:album**: Яндекс.Музыка - Альбом
|
||||||
- **yandexmusic:playlist**: Яндекс.Музыка - Плейлист
|
- **yandexmusic:playlist**: Яндекс.Музыка - Плейлист
|
||||||
- **yandexmusic:track**: Яндекс.Музыка - Трек
|
- **yandexmusic:track**: Яндекс.Музыка - Трек
|
||||||
@ -1004,6 +1034,9 @@
|
|||||||
- **YouJizz**
|
- **YouJizz**
|
||||||
- **youku**: 优酷
|
- **youku**: 优酷
|
||||||
- **youku:show**
|
- **youku:show**
|
||||||
|
- **YouNowChannel**
|
||||||
|
- **YouNowLive**
|
||||||
|
- **YouNowMoment**
|
||||||
- **YouPorn**
|
- **YouPorn**
|
||||||
- **YourUpload**
|
- **YourUpload**
|
||||||
- **youtube**: YouTube.com
|
- **youtube**: YouTube.com
|
||||||
@ -1017,7 +1050,6 @@
|
|||||||
- **youtube:search**: YouTube.com searches
|
- **youtube:search**: YouTube.com searches
|
||||||
- **youtube:search:date**: YouTube.com searches, newest videos first
|
- **youtube:search:date**: YouTube.com searches, newest videos first
|
||||||
- **youtube:search_url**: YouTube.com search URLs
|
- **youtube:search_url**: YouTube.com search URLs
|
||||||
- **youtube:shared**
|
|
||||||
- **youtube:show**: YouTube.com (multi-season) shows
|
- **youtube:show**: YouTube.com (multi-season) shows
|
||||||
- **youtube:subscriptions**: YouTube.com subscriptions feed, "ytsubs" keyword (requires authentication)
|
- **youtube:subscriptions**: YouTube.com subscriptions feed, "ytsubs" keyword (requires authentication)
|
||||||
- **youtube:user**: YouTube.com user videos (URL or "ytuser" keyword)
|
- **youtube:user**: YouTube.com user videos (URL or "ytuser" keyword)
|
||||||
|
@ -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 FakeYDL, expect_dict, expect_value
|
from test.helper import FakeYDL, expect_dict, expect_value
|
||||||
|
from youtube_dl.compat import compat_etree_fromstring
|
||||||
from youtube_dl.extractor.common import InfoExtractor
|
from youtube_dl.extractor.common import InfoExtractor
|
||||||
from youtube_dl.extractor import YoutubeIE, get_info_extractor
|
from youtube_dl.extractor import YoutubeIE, get_info_extractor
|
||||||
from youtube_dl.utils import encode_data_uri, strip_jsonp, ExtractorError, RegexNotFoundError
|
from youtube_dl.utils import encode_data_uri, strip_jsonp, ExtractorError, RegexNotFoundError
|
||||||
@ -488,6 +489,200 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
|
|||||||
self.ie._sort_formats(formats)
|
self.ie._sort_formats(formats)
|
||||||
expect_value(self, formats, expected_formats, None)
|
expect_value(self, formats, expected_formats, None)
|
||||||
|
|
||||||
|
def test_parse_mpd_formats(self):
|
||||||
|
_TEST_CASES = [
|
||||||
|
(
|
||||||
|
# https://github.com/rg3/youtube-dl/issues/13919
|
||||||
|
'float_duration',
|
||||||
|
'http://unknown/manifest.mpd',
|
||||||
|
[{
|
||||||
|
'manifest_url': 'http://unknown/manifest.mpd',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'format_id': '318597',
|
||||||
|
'format_note': 'DASH video',
|
||||||
|
'protocol': 'http_dash_segments',
|
||||||
|
'acodec': 'none',
|
||||||
|
'vcodec': 'avc1.42001f',
|
||||||
|
'tbr': 318.597,
|
||||||
|
'width': 340,
|
||||||
|
'height': 192,
|
||||||
|
}, {
|
||||||
|
'manifest_url': 'http://unknown/manifest.mpd',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'format_id': '638590',
|
||||||
|
'format_note': 'DASH video',
|
||||||
|
'protocol': 'http_dash_segments',
|
||||||
|
'acodec': 'none',
|
||||||
|
'vcodec': 'avc1.42001f',
|
||||||
|
'tbr': 638.59,
|
||||||
|
'width': 512,
|
||||||
|
'height': 288,
|
||||||
|
}, {
|
||||||
|
'manifest_url': 'http://unknown/manifest.mpd',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'format_id': '1022565',
|
||||||
|
'format_note': 'DASH video',
|
||||||
|
'protocol': 'http_dash_segments',
|
||||||
|
'acodec': 'none',
|
||||||
|
'vcodec': 'avc1.4d001f',
|
||||||
|
'tbr': 1022.565,
|
||||||
|
'width': 688,
|
||||||
|
'height': 384,
|
||||||
|
}, {
|
||||||
|
'manifest_url': 'http://unknown/manifest.mpd',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'format_id': '2046506',
|
||||||
|
'format_note': 'DASH video',
|
||||||
|
'protocol': 'http_dash_segments',
|
||||||
|
'acodec': 'none',
|
||||||
|
'vcodec': 'avc1.4d001f',
|
||||||
|
'tbr': 2046.506,
|
||||||
|
'width': 1024,
|
||||||
|
'height': 576,
|
||||||
|
}, {
|
||||||
|
'manifest_url': 'http://unknown/manifest.mpd',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'format_id': '3998017',
|
||||||
|
'format_note': 'DASH video',
|
||||||
|
'protocol': 'http_dash_segments',
|
||||||
|
'acodec': 'none',
|
||||||
|
'vcodec': 'avc1.640029',
|
||||||
|
'tbr': 3998.017,
|
||||||
|
'width': 1280,
|
||||||
|
'height': 720,
|
||||||
|
}, {
|
||||||
|
'manifest_url': 'http://unknown/manifest.mpd',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'format_id': '5997485',
|
||||||
|
'format_note': 'DASH video',
|
||||||
|
'protocol': 'http_dash_segments',
|
||||||
|
'acodec': 'none',
|
||||||
|
'vcodec': 'avc1.640032',
|
||||||
|
'tbr': 5997.485,
|
||||||
|
'width': 1920,
|
||||||
|
'height': 1080,
|
||||||
|
}]
|
||||||
|
), (
|
||||||
|
# https://github.com/rg3/youtube-dl/pull/14844
|
||||||
|
'urls_only',
|
||||||
|
'http://unknown/manifest.mpd',
|
||||||
|
[{
|
||||||
|
'manifest_url': 'http://unknown/manifest.mpd',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'format_id': 'h264_aac_144p_m4s',
|
||||||
|
'format_note': 'DASH video',
|
||||||
|
'protocol': 'http_dash_segments',
|
||||||
|
'acodec': 'mp4a.40.2',
|
||||||
|
'vcodec': 'avc3.42c01e',
|
||||||
|
'tbr': 200,
|
||||||
|
'width': 256,
|
||||||
|
'height': 144,
|
||||||
|
}, {
|
||||||
|
'manifest_url': 'http://unknown/manifest.mpd',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'format_id': 'h264_aac_240p_m4s',
|
||||||
|
'format_note': 'DASH video',
|
||||||
|
'protocol': 'http_dash_segments',
|
||||||
|
'acodec': 'mp4a.40.2',
|
||||||
|
'vcodec': 'avc3.42c01e',
|
||||||
|
'tbr': 400,
|
||||||
|
'width': 424,
|
||||||
|
'height': 240,
|
||||||
|
}, {
|
||||||
|
'manifest_url': 'http://unknown/manifest.mpd',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'format_id': 'h264_aac_360p_m4s',
|
||||||
|
'format_note': 'DASH video',
|
||||||
|
'protocol': 'http_dash_segments',
|
||||||
|
'acodec': 'mp4a.40.2',
|
||||||
|
'vcodec': 'avc3.42c01e',
|
||||||
|
'tbr': 800,
|
||||||
|
'width': 640,
|
||||||
|
'height': 360,
|
||||||
|
}, {
|
||||||
|
'manifest_url': 'http://unknown/manifest.mpd',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'format_id': 'h264_aac_480p_m4s',
|
||||||
|
'format_note': 'DASH video',
|
||||||
|
'protocol': 'http_dash_segments',
|
||||||
|
'acodec': 'mp4a.40.2',
|
||||||
|
'vcodec': 'avc3.42c01e',
|
||||||
|
'tbr': 1200,
|
||||||
|
'width': 856,
|
||||||
|
'height': 480,
|
||||||
|
}, {
|
||||||
|
'manifest_url': 'http://unknown/manifest.mpd',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'format_id': 'h264_aac_576p_m4s',
|
||||||
|
'format_note': 'DASH video',
|
||||||
|
'protocol': 'http_dash_segments',
|
||||||
|
'acodec': 'mp4a.40.2',
|
||||||
|
'vcodec': 'avc3.42c01e',
|
||||||
|
'tbr': 1600,
|
||||||
|
'width': 1024,
|
||||||
|
'height': 576,
|
||||||
|
}, {
|
||||||
|
'manifest_url': 'http://unknown/manifest.mpd',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'format_id': 'h264_aac_720p_m4s',
|
||||||
|
'format_note': 'DASH video',
|
||||||
|
'protocol': 'http_dash_segments',
|
||||||
|
'acodec': 'mp4a.40.2',
|
||||||
|
'vcodec': 'avc3.42c01e',
|
||||||
|
'tbr': 2400,
|
||||||
|
'width': 1280,
|
||||||
|
'height': 720,
|
||||||
|
}, {
|
||||||
|
'manifest_url': 'http://unknown/manifest.mpd',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'format_id': 'h264_aac_1080p_m4s',
|
||||||
|
'format_note': 'DASH video',
|
||||||
|
'protocol': 'http_dash_segments',
|
||||||
|
'acodec': 'mp4a.40.2',
|
||||||
|
'vcodec': 'avc3.42c01e',
|
||||||
|
'tbr': 4400,
|
||||||
|
'width': 1920,
|
||||||
|
'height': 1080,
|
||||||
|
}]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
for mpd_file, mpd_url, expected_formats in _TEST_CASES:
|
||||||
|
with io.open('./test/testdata/mpd/%s.mpd' % mpd_file,
|
||||||
|
mode='r', encoding='utf-8') as f:
|
||||||
|
formats = self.ie._parse_mpd_formats(
|
||||||
|
compat_etree_fromstring(f.read().encode('utf-8')),
|
||||||
|
mpd_url=mpd_url)
|
||||||
|
self.ie._sort_formats(formats)
|
||||||
|
expect_value(self, formats, expected_formats, None)
|
||||||
|
|
||||||
|
def test_parse_f4m_formats(self):
|
||||||
|
_TEST_CASES = [
|
||||||
|
(
|
||||||
|
# https://github.com/rg3/youtube-dl/issues/14660
|
||||||
|
'custom_base_url',
|
||||||
|
'http://api.new.livestream.com/accounts/6115179/events/6764928/videos/144884262.f4m',
|
||||||
|
[{
|
||||||
|
'manifest_url': 'http://api.new.livestream.com/accounts/6115179/events/6764928/videos/144884262.f4m',
|
||||||
|
'ext': 'flv',
|
||||||
|
'format_id': '2148',
|
||||||
|
'protocol': 'f4m',
|
||||||
|
'tbr': 2148,
|
||||||
|
'width': 1280,
|
||||||
|
'height': 720,
|
||||||
|
}]
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
for f4m_file, f4m_url, expected_formats in _TEST_CASES:
|
||||||
|
with io.open('./test/testdata/f4m/%s.f4m' % f4m_file,
|
||||||
|
mode='r', encoding='utf-8') as f:
|
||||||
|
formats = self.ie._parse_f4m_formats(
|
||||||
|
compat_etree_fromstring(f.read().encode('utf-8')),
|
||||||
|
f4m_url, None)
|
||||||
|
self.ie._sort_formats(formats)
|
||||||
|
expect_value(self, formats, expected_formats, None)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -41,6 +41,7 @@ def _make_result(formats, **kwargs):
|
|||||||
'id': 'testid',
|
'id': 'testid',
|
||||||
'title': 'testttitle',
|
'title': 'testttitle',
|
||||||
'extractor': 'testex',
|
'extractor': 'testex',
|
||||||
|
'extractor_key': 'TestEx',
|
||||||
}
|
}
|
||||||
res.update(**kwargs)
|
res.update(**kwargs)
|
||||||
return res
|
return res
|
||||||
@ -370,6 +371,19 @@ class TestFormatSelection(unittest.TestCase):
|
|||||||
ydl = YDL({'format': 'best[height>360]'})
|
ydl = YDL({'format': 'best[height>360]'})
|
||||||
self.assertRaises(ExtractorError, ydl.process_ie_result, info_dict.copy())
|
self.assertRaises(ExtractorError, ydl.process_ie_result, info_dict.copy())
|
||||||
|
|
||||||
|
def test_format_selection_issue_10083(self):
|
||||||
|
# See https://github.com/rg3/youtube-dl/issues/10083
|
||||||
|
formats = [
|
||||||
|
{'format_id': 'regular', 'height': 360, 'url': TEST_URL},
|
||||||
|
{'format_id': 'video', 'height': 720, 'acodec': 'none', 'url': TEST_URL},
|
||||||
|
{'format_id': 'audio', 'vcodec': 'none', 'url': TEST_URL},
|
||||||
|
]
|
||||||
|
info_dict = _make_result(formats)
|
||||||
|
|
||||||
|
ydl = YDL({'format': 'best[height>360]/bestvideo[height>360]+bestaudio'})
|
||||||
|
ydl.process_ie_result(info_dict.copy())
|
||||||
|
self.assertEqual(ydl.downloaded_info_dicts[0]['format_id'], 'video+audio')
|
||||||
|
|
||||||
def test_invalid_format_specs(self):
|
def test_invalid_format_specs(self):
|
||||||
def assert_syntax_error(format_spec):
|
def assert_syntax_error(format_spec):
|
||||||
ydl = YDL({'format': format_spec})
|
ydl = YDL({'format': format_spec})
|
||||||
@ -448,6 +462,23 @@ class TestFormatSelection(unittest.TestCase):
|
|||||||
pass
|
pass
|
||||||
self.assertEqual(ydl.downloaded_info_dicts, [])
|
self.assertEqual(ydl.downloaded_info_dicts, [])
|
||||||
|
|
||||||
|
def test_default_format_spec(self):
|
||||||
|
ydl = YDL({'simulate': True})
|
||||||
|
self.assertEqual(ydl._default_format_spec({}), 'bestvideo+bestaudio/best')
|
||||||
|
|
||||||
|
ydl = YDL({})
|
||||||
|
self.assertEqual(ydl._default_format_spec({'is_live': True}), 'best/bestvideo+bestaudio')
|
||||||
|
|
||||||
|
ydl = YDL({'simulate': True})
|
||||||
|
self.assertEqual(ydl._default_format_spec({'is_live': True}), 'bestvideo+bestaudio/best')
|
||||||
|
|
||||||
|
ydl = YDL({'outtmpl': '-'})
|
||||||
|
self.assertEqual(ydl._default_format_spec({}), 'best/bestvideo+bestaudio')
|
||||||
|
|
||||||
|
ydl = YDL({})
|
||||||
|
self.assertEqual(ydl._default_format_spec({}, download=False), 'bestvideo+bestaudio/best')
|
||||||
|
self.assertEqual(ydl._default_format_spec({'is_live': True}), 'best/bestvideo+bestaudio')
|
||||||
|
|
||||||
|
|
||||||
class TestYoutubeDL(unittest.TestCase):
|
class TestYoutubeDL(unittest.TestCase):
|
||||||
def test_subtitles(self):
|
def test_subtitles(self):
|
||||||
@ -527,6 +558,8 @@ class TestYoutubeDL(unittest.TestCase):
|
|||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'width': None,
|
'width': None,
|
||||||
'height': 1080,
|
'height': 1080,
|
||||||
|
'title1': '$PATH',
|
||||||
|
'title2': '%PATH%',
|
||||||
}
|
}
|
||||||
|
|
||||||
def fname(templ):
|
def fname(templ):
|
||||||
@ -545,10 +578,14 @@ class TestYoutubeDL(unittest.TestCase):
|
|||||||
self.assertEqual(fname('%(height)0 6d.%(ext)s'), ' 01080.mp4')
|
self.assertEqual(fname('%(height)0 6d.%(ext)s'), ' 01080.mp4')
|
||||||
self.assertEqual(fname('%(height)0 6d.%(ext)s'), ' 01080.mp4')
|
self.assertEqual(fname('%(height)0 6d.%(ext)s'), ' 01080.mp4')
|
||||||
self.assertEqual(fname('%(height) 0 6d.%(ext)s'), ' 01080.mp4')
|
self.assertEqual(fname('%(height) 0 6d.%(ext)s'), ' 01080.mp4')
|
||||||
|
self.assertEqual(fname('%%'), '%')
|
||||||
|
self.assertEqual(fname('%%%%'), '%%')
|
||||||
self.assertEqual(fname('%%(height)06d.%(ext)s'), '%(height)06d.mp4')
|
self.assertEqual(fname('%%(height)06d.%(ext)s'), '%(height)06d.mp4')
|
||||||
self.assertEqual(fname('%(width)06d.%(ext)s'), 'NA.mp4')
|
self.assertEqual(fname('%(width)06d.%(ext)s'), 'NA.mp4')
|
||||||
self.assertEqual(fname('%(width)06d.%%(ext)s'), 'NA.%(ext)s')
|
self.assertEqual(fname('%(width)06d.%%(ext)s'), 'NA.%(ext)s')
|
||||||
self.assertEqual(fname('%%(width)06d.%(ext)s'), '%(width)06d.mp4')
|
self.assertEqual(fname('%%(width)06d.%(ext)s'), '%(width)06d.mp4')
|
||||||
|
self.assertEqual(fname('Hello %(title1)s'), 'Hello $PATH')
|
||||||
|
self.assertEqual(fname('Hello %(title2)s'), 'Hello %PATH%')
|
||||||
|
|
||||||
def test_format_note(self):
|
def test_format_note(self):
|
||||||
ydl = YoutubeDL()
|
ydl = YoutubeDL()
|
||||||
@ -739,6 +776,12 @@ class TestYoutubeDL(unittest.TestCase):
|
|||||||
result = get_ids({'playlist_items': '10'})
|
result = get_ids({'playlist_items': '10'})
|
||||||
self.assertEqual(result, [])
|
self.assertEqual(result, [])
|
||||||
|
|
||||||
|
result = get_ids({'playlist_items': '3-10'})
|
||||||
|
self.assertEqual(result, [3, 4])
|
||||||
|
|
||||||
|
result = get_ids({'playlist_items': '2-4,3-4,3'})
|
||||||
|
self.assertEqual(result, [2, 3, 4])
|
||||||
|
|
||||||
def test_urlopen_no_file_protocol(self):
|
def test_urlopen_no_file_protocol(self):
|
||||||
# see https://github.com/rg3/youtube-dl/issues/8227
|
# see https://github.com/rg3/youtube-dl/issues/8227
|
||||||
ydl = YDL()
|
ydl = YDL()
|
||||||
@ -755,7 +798,8 @@ class TestYoutubeDL(unittest.TestCase):
|
|||||||
'_type': 'url_transparent',
|
'_type': 'url_transparent',
|
||||||
'url': 'foo2:',
|
'url': 'foo2:',
|
||||||
'ie_key': 'Foo2',
|
'ie_key': 'Foo2',
|
||||||
'title': 'foo1 title'
|
'title': 'foo1 title',
|
||||||
|
'id': 'foo1_id',
|
||||||
}
|
}
|
||||||
|
|
||||||
class Foo2IE(InfoExtractor):
|
class Foo2IE(InfoExtractor):
|
||||||
@ -781,6 +825,9 @@ class TestYoutubeDL(unittest.TestCase):
|
|||||||
downloaded = ydl.downloaded_info_dicts[0]
|
downloaded = ydl.downloaded_info_dicts[0]
|
||||||
self.assertEqual(downloaded['url'], TEST_URL)
|
self.assertEqual(downloaded['url'], TEST_URL)
|
||||||
self.assertEqual(downloaded['title'], 'foo1 title')
|
self.assertEqual(downloaded['title'], 'foo1 title')
|
||||||
|
self.assertEqual(downloaded['id'], 'testid')
|
||||||
|
self.assertEqual(downloaded['extractor'], 'testex')
|
||||||
|
self.assertEqual(downloaded['extractor_key'], 'TestEx')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
26
test/test_options.py
Normal file
26
test/test_options.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
# Allow direct execution
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
from youtube_dl.options import _hide_login_info
|
||||||
|
|
||||||
|
|
||||||
|
class TestOptions(unittest.TestCase):
|
||||||
|
def test_hide_login_info(self):
|
||||||
|
self.assertEqual(_hide_login_info(['-u', 'foo', '-p', 'bar']),
|
||||||
|
['-u', 'PRIVATE', '-p', 'PRIVATE'])
|
||||||
|
self.assertEqual(_hide_login_info(['-u']), ['-u'])
|
||||||
|
self.assertEqual(_hide_login_info(['-u', 'foo', '-u', 'bar']),
|
||||||
|
['-u', 'PRIVATE', '-u', 'PRIVATE'])
|
||||||
|
self.assertEqual(_hide_login_info(['--username=foo']),
|
||||||
|
['--username=PRIVATE'])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
@ -98,6 +98,7 @@ from youtube_dl.compat import (
|
|||||||
compat_chr,
|
compat_chr,
|
||||||
compat_etree_fromstring,
|
compat_etree_fromstring,
|
||||||
compat_getenv,
|
compat_getenv,
|
||||||
|
compat_os_name,
|
||||||
compat_setenv,
|
compat_setenv,
|
||||||
compat_urlparse,
|
compat_urlparse,
|
||||||
compat_parse_qs,
|
compat_parse_qs,
|
||||||
@ -278,6 +279,7 @@ class TestUtil(unittest.TestCase):
|
|||||||
self.assertEqual(unescapeHTML('/'), '/')
|
self.assertEqual(unescapeHTML('/'), '/')
|
||||||
self.assertEqual(unescapeHTML('é'), 'é')
|
self.assertEqual(unescapeHTML('é'), 'é')
|
||||||
self.assertEqual(unescapeHTML('�'), '�')
|
self.assertEqual(unescapeHTML('�'), '�')
|
||||||
|
self.assertEqual(unescapeHTML('&a"'), '&a"')
|
||||||
# HTML5 entities
|
# HTML5 entities
|
||||||
self.assertEqual(unescapeHTML('.''), '.\'')
|
self.assertEqual(unescapeHTML('.''), '.\'')
|
||||||
|
|
||||||
@ -448,7 +450,9 @@ class TestUtil(unittest.TestCase):
|
|||||||
|
|
||||||
def test_shell_quote(self):
|
def test_shell_quote(self):
|
||||||
args = ['ffmpeg', '-i', encodeFilename('ñ€ß\'.mp4')]
|
args = ['ffmpeg', '-i', encodeFilename('ñ€ß\'.mp4')]
|
||||||
self.assertEqual(shell_quote(args), """ffmpeg -i 'ñ€ß'"'"'.mp4'""")
|
self.assertEqual(
|
||||||
|
shell_quote(args),
|
||||||
|
"""ffmpeg -i 'ñ€ß'"'"'.mp4'""" if compat_os_name != 'nt' else '''ffmpeg -i "ñ€ß'.mp4"''')
|
||||||
|
|
||||||
def test_str_to_int(self):
|
def test_str_to_int(self):
|
||||||
self.assertEqual(str_to_int('123,456'), 123456)
|
self.assertEqual(str_to_int('123,456'), 123456)
|
||||||
@ -536,6 +540,7 @@ class TestUtil(unittest.TestCase):
|
|||||||
self.assertEqual(parse_duration('87 Min.'), 5220)
|
self.assertEqual(parse_duration('87 Min.'), 5220)
|
||||||
self.assertEqual(parse_duration('PT1H0.040S'), 3600.04)
|
self.assertEqual(parse_duration('PT1H0.040S'), 3600.04)
|
||||||
self.assertEqual(parse_duration('PT00H03M30SZ'), 210)
|
self.assertEqual(parse_duration('PT00H03M30SZ'), 210)
|
||||||
|
self.assertEqual(parse_duration('P0Y0M0DT0H4M20.880S'), 260.88)
|
||||||
|
|
||||||
def test_fix_xml_ampersands(self):
|
def test_fix_xml_ampersands(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
@ -932,7 +937,7 @@ class TestUtil(unittest.TestCase):
|
|||||||
def test_args_to_str(self):
|
def test_args_to_str(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
args_to_str(['foo', 'ba/r', '-baz', '2 be', '']),
|
args_to_str(['foo', 'ba/r', '-baz', '2 be', '']),
|
||||||
'foo ba/r -baz \'2 be\' \'\''
|
'foo ba/r -baz \'2 be\' \'\'' if compat_os_name != 'nt' else 'foo ba/r -baz "2 be" ""'
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_parse_filesize(self):
|
def test_parse_filesize(self):
|
||||||
@ -1060,7 +1065,7 @@ ffmpeg version 2.4.4 Copyright (c) 2000-2014 the FFmpeg ...'''), '2.4.4')
|
|||||||
<p begin="3" dur="-1">Ignored, three</p>
|
<p begin="3" dur="-1">Ignored, three</p>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</tt>'''
|
</tt>'''.encode('utf-8')
|
||||||
srt_data = '''1
|
srt_data = '''1
|
||||||
00:00:00,000 --> 00:00:01,000
|
00:00:00,000 --> 00:00:01,000
|
||||||
The following line contains Chinese characters and special symbols
|
The following line contains Chinese characters and special symbols
|
||||||
@ -1085,7 +1090,7 @@ Line
|
|||||||
<p begin="0" end="1">The first line</p>
|
<p begin="0" end="1">The first line</p>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</tt>'''
|
</tt>'''.encode('utf-8')
|
||||||
srt_data = '''1
|
srt_data = '''1
|
||||||
00:00:00,000 --> 00:00:01,000
|
00:00:00,000 --> 00:00:01,000
|
||||||
The first line
|
The first line
|
||||||
@ -1111,7 +1116,7 @@ The first line
|
|||||||
<p style="s1" tts:textDecoration="underline" begin="00:00:09.56" id="p2" end="00:00:12.36"><span style="s2" tts:color="lime">inner<br /> </span>style</p>
|
<p style="s1" tts:textDecoration="underline" begin="00:00:09.56" id="p2" end="00:00:12.36"><span style="s2" tts:color="lime">inner<br /> </span>style</p>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</tt>'''
|
</tt>'''.encode('utf-8')
|
||||||
srt_data = '''1
|
srt_data = '''1
|
||||||
00:00:02,080 --> 00:00:05,839
|
00:00:02,080 --> 00:00:05,839
|
||||||
<font color="white" face="sansSerif" size="16">default style<font color="red">custom style</font></font>
|
<font color="white" face="sansSerif" size="16">default style<font color="red">custom style</font></font>
|
||||||
@ -1134,6 +1139,26 @@ part 3</font></u>
|
|||||||
'''
|
'''
|
||||||
self.assertEqual(dfxp2srt(dfxp_data_with_style), srt_data)
|
self.assertEqual(dfxp2srt(dfxp_data_with_style), srt_data)
|
||||||
|
|
||||||
|
dfxp_data_non_utf8 = '''<?xml version="1.0" encoding="UTF-16"?>
|
||||||
|
<tt xmlns="http://www.w3.org/ns/ttml" xml:lang="en" xmlns:tts="http://www.w3.org/ns/ttml#parameter">
|
||||||
|
<body>
|
||||||
|
<div xml:lang="en">
|
||||||
|
<p begin="0" end="1">Line 1</p>
|
||||||
|
<p begin="1" end="2">第二行</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</tt>'''.encode('utf-16')
|
||||||
|
srt_data = '''1
|
||||||
|
00:00:00,000 --> 00:00:01,000
|
||||||
|
Line 1
|
||||||
|
|
||||||
|
2
|
||||||
|
00:00:01,000 --> 00:00:02,000
|
||||||
|
第二行
|
||||||
|
|
||||||
|
'''
|
||||||
|
self.assertEqual(dfxp2srt(dfxp_data_non_utf8), srt_data)
|
||||||
|
|
||||||
def test_cli_option(self):
|
def test_cli_option(self):
|
||||||
self.assertEqual(cli_option({'proxy': '127.0.0.1:3128'}, '--proxy', 'proxy'), ['--proxy', '127.0.0.1:3128'])
|
self.assertEqual(cli_option({'proxy': '127.0.0.1:3128'}, '--proxy', 'proxy'), ['--proxy', '127.0.0.1:3128'])
|
||||||
self.assertEqual(cli_option({'proxy': None}, '--proxy', 'proxy'), [])
|
self.assertEqual(cli_option({'proxy': None}, '--proxy', 'proxy'), [])
|
||||||
@ -1179,6 +1204,10 @@ part 3</font></u>
|
|||||||
cli_bool_option(
|
cli_bool_option(
|
||||||
{'nocheckcertificate': False}, '--check-certificate', 'nocheckcertificate', 'false', 'true', '='),
|
{'nocheckcertificate': False}, '--check-certificate', 'nocheckcertificate', 'false', 'true', '='),
|
||||||
['--check-certificate=true'])
|
['--check-certificate=true'])
|
||||||
|
self.assertEqual(
|
||||||
|
cli_bool_option(
|
||||||
|
{}, '--check-certificate', 'nocheckcertificate', 'false', 'true', '='),
|
||||||
|
[])
|
||||||
|
|
||||||
def test_ohdave_rsa_encrypt(self):
|
def test_ohdave_rsa_encrypt(self):
|
||||||
N = 0xab86b6371b5318aaa1d3c9e612a9f1264f372323c8c0f19875b5fc3b3fd3afcc1e5bec527aa94bfa85bffc157e4245aebda05389a5357b75115ac94f074aefcd
|
N = 0xab86b6371b5318aaa1d3c9e612a9f1264f372323c8c0f19875b5fc3b3fd3afcc1e5bec527aa94bfa85bffc157e4245aebda05389a5357b75115ac94f074aefcd
|
||||||
@ -1228,6 +1257,12 @@ part 3</font></u>
|
|||||||
self.assertEqual(get_element_by_attribute('class', 'foo', html), None)
|
self.assertEqual(get_element_by_attribute('class', 'foo', html), None)
|
||||||
self.assertEqual(get_element_by_attribute('class', 'no-such-foo', html), None)
|
self.assertEqual(get_element_by_attribute('class', 'no-such-foo', html), None)
|
||||||
|
|
||||||
|
html = '''
|
||||||
|
<div itemprop="author" itemscope>foo</div>
|
||||||
|
'''
|
||||||
|
|
||||||
|
self.assertEqual(get_element_by_attribute('itemprop', 'author', html), 'foo')
|
||||||
|
|
||||||
def test_get_elements_by_class(self):
|
def test_get_elements_by_class(self):
|
||||||
html = '''
|
html = '''
|
||||||
<span class="foo bar">nice</span><span class="foo bar">also nice</span>
|
<span class="foo bar">nice</span><span class="foo bar">also nice</span>
|
||||||
|
10
test/testdata/f4m/custom_base_url.f4m
vendored
Normal file
10
test/testdata/f4m/custom_base_url.f4m
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<manifest xmlns="http://ns.adobe.com/f4m/1.0">
|
||||||
|
<streamType>recorded</streamType>
|
||||||
|
<baseURL>http://vod.livestream.com/events/0000000000673980/</baseURL>
|
||||||
|
<duration>269.293</duration>
|
||||||
|
<bootstrapInfo profile="named" id="bootstrap_1">AAAAm2Fic3QAAAAAAAAAAQAAAAPoAAAAAAAEG+0AAAAAAAAAAAAAAAAAAQAAABlhc3J0AAAAAAAAAAABAAAAAQAAAC4BAAAAVmFmcnQAAAAAAAAD6AAAAAAEAAAAAQAAAAAAAAAAAAAXcAAAAC0AAAAAAAQHQAAAE5UAAAAuAAAAAAAEGtUAAAEYAAAAAAAAAAAAAAAAAAAAAAA=</bootstrapInfo>
|
||||||
|
<media url="b90f532f-b0f6-4f4e-8289-706d490b2fd8_2292" bootstrapInfoId="bootstrap_1" bitrate="2148" width="1280" height="720" videoCodec="avc1.4d401f" audioCodec="mp4a.40.2">
|
||||||
|
<metadata>AgAKb25NZXRhRGF0YQgAAAAIAAhkdXJhdGlvbgBAcNSwIMSbpgAFd2lkdGgAQJQAAAAAAAAABmhlaWdodABAhoAAAAAAAAAJZnJhbWVyYXRlAEA4/7DoLwW3AA12aWRlb2RhdGFyYXRlAECe1DLgjcobAAx2aWRlb2NvZGVjaWQAQBwAAAAAAAAADWF1ZGlvZGF0YXJhdGUAQGSimlvaPKQADGF1ZGlvY29kZWNpZABAJAAAAAAAAAAACQ==</metadata>
|
||||||
|
</media>
|
||||||
|
</manifest>
|
18
test/testdata/mpd/float_duration.mpd
vendored
Normal file
18
test/testdata/mpd/float_duration.mpd
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:mpeg:dash:schema:mpd:2011" type="static" minBufferTime="PT2S" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" mediaPresentationDuration="PT6014S">
|
||||||
|
<Period bitstreamSwitching="true">
|
||||||
|
<AdaptationSet mimeType="audio/mp4" codecs="mp4a.40.2" startWithSAP="1" segmentAlignment="true">
|
||||||
|
<SegmentTemplate timescale="1000000" presentationTimeOffset="0" initialization="ai_$RepresentationID$.mp4d" media="a_$RepresentationID$_$Number$.mp4d" duration="2000000.0" startNumber="0"></SegmentTemplate>
|
||||||
|
<Representation id="318597" bandwidth="61587"></Representation>
|
||||||
|
</AdaptationSet>
|
||||||
|
<AdaptationSet mimeType="video/mp4" startWithSAP="1" segmentAlignment="true">
|
||||||
|
<SegmentTemplate timescale="1000000" presentationTimeOffset="0" initialization="vi_$RepresentationID$.mp4d" media="v_$RepresentationID$_$Number$.mp4d" duration="2000000.0" startNumber="0"></SegmentTemplate>
|
||||||
|
<Representation id="318597" codecs="avc1.42001f" width="340" height="192" bandwidth="318597"></Representation>
|
||||||
|
<Representation id="638590" codecs="avc1.42001f" width="512" height="288" bandwidth="638590"></Representation>
|
||||||
|
<Representation id="1022565" codecs="avc1.4d001f" width="688" height="384" bandwidth="1022565"></Representation>
|
||||||
|
<Representation id="2046506" codecs="avc1.4d001f" width="1024" height="576" bandwidth="2046506"></Representation>
|
||||||
|
<Representation id="3998017" codecs="avc1.640029" width="1280" height="720" bandwidth="3998017"></Representation>
|
||||||
|
<Representation id="5997485" codecs="avc1.640032" width="1920" height="1080" bandwidth="5997485"></Representation>
|
||||||
|
</AdaptationSet>
|
||||||
|
</Period>
|
||||||
|
</MPD>
|
218
test/testdata/mpd/urls_only.mpd
vendored
Normal file
218
test/testdata/mpd/urls_only.mpd
vendored
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
<?xml version="1.0" ?>
|
||||||
|
<MPD maxSegmentDuration="PT0H0M10.000S" mediaPresentationDuration="PT0H4M1.728S" minBufferTime="PT1.500S" profiles="urn:mpeg:dash:profile:isoff-main:2011" type="static" xmlns="urn:mpeg:dash:schema:mpd:2011">
|
||||||
|
<Period duration="PT0H4M1.728S">
|
||||||
|
<AdaptationSet bitstreamSwitching="true" lang="und" maxHeight="1080" maxWidth="1920" par="16:9" segmentAlignment="true">
|
||||||
|
<ContentComponent contentType="video" id="1"/>
|
||||||
|
<Representation audioSamplingRate="44100" bandwidth="200000" codecs="avc3.42c01e,mp4a.40.2" frameRate="25" height="144" id="h264_aac_144p_m4s" mimeType="video/mp4" sar="1:1" startWithSAP="1" width="256">
|
||||||
|
<SegmentList duration="10000" timescale="1000">
|
||||||
|
<Initialization sourceURL="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/init/432f65a0.mp4"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/0/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/1/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/2/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/3/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/4/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/5/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/6/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/7/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/8/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/9/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/10/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/11/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/12/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/13/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/14/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/15/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/16/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/17/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/18/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/19/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/20/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/21/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/22/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/23/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/24/432f65a0.m4s"/>
|
||||||
|
</SegmentList>
|
||||||
|
</Representation>
|
||||||
|
<Representation audioSamplingRate="44100" bandwidth="400000" codecs="avc3.42c01e,mp4a.40.2" frameRate="25" height="240" id="h264_aac_240p_m4s" mimeType="video/mp4" sar="160:159" startWithSAP="1" width="424">
|
||||||
|
<SegmentList duration="10000" timescale="1000">
|
||||||
|
<Initialization sourceURL="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/init/432f65a0.mp4"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/0/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/1/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/2/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/3/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/4/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/5/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/6/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/7/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/8/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/9/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/10/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/11/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/12/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/13/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/14/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/15/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/16/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/17/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/18/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/19/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/20/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/21/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/22/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/23/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/24/432f65a0.m4s"/>
|
||||||
|
</SegmentList>
|
||||||
|
</Representation>
|
||||||
|
<Representation audioSamplingRate="44100" bandwidth="800000" codecs="avc3.42c01e,mp4a.40.2" frameRate="25" height="360" id="h264_aac_360p_m4s" mimeType="video/mp4" sar="1:1" startWithSAP="1" width="640">
|
||||||
|
<SegmentList duration="10000" timescale="1000">
|
||||||
|
<Initialization sourceURL="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/init/432f65a0.mp4"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/0/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/1/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/2/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/3/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/4/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/5/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/6/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/7/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/8/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/9/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/10/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/11/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/12/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/13/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/14/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/15/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/16/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/17/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/18/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/19/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/20/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/21/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/22/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/23/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/24/432f65a0.m4s"/>
|
||||||
|
</SegmentList>
|
||||||
|
</Representation>
|
||||||
|
<Representation audioSamplingRate="44100" bandwidth="1200000" codecs="avc3.42c01e,mp4a.40.2" frameRate="25" height="480" id="h264_aac_480p_m4s" mimeType="video/mp4" sar="320:321" startWithSAP="1" width="856">
|
||||||
|
<SegmentList duration="10000" timescale="1000">
|
||||||
|
<Initialization sourceURL="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/init/432f65a0.mp4"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/0/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/1/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/2/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/3/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/4/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/5/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/6/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/7/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/8/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/9/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/10/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/11/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/12/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/13/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/14/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/15/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/16/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/17/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/18/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/19/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/20/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/21/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/22/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/23/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/24/432f65a0.m4s"/>
|
||||||
|
</SegmentList>
|
||||||
|
</Representation>
|
||||||
|
<Representation audioSamplingRate="44100" bandwidth="1600000" codecs="avc3.42c01e,mp4a.40.2" frameRate="25" height="576" id="h264_aac_576p_m4s" mimeType="video/mp4" sar="1:1" startWithSAP="1" width="1024">
|
||||||
|
<SegmentList duration="10000" timescale="1000">
|
||||||
|
<Initialization sourceURL="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/init/432f65a0.mp4"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/0/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/1/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/2/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/3/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/4/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/5/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/6/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/7/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/8/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/9/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/10/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/11/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/12/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/13/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/14/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/15/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/16/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/17/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/18/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/19/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/20/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/21/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/22/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/23/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/24/432f65a0.m4s"/>
|
||||||
|
</SegmentList>
|
||||||
|
</Representation>
|
||||||
|
<Representation audioSamplingRate="44100" bandwidth="2400000" codecs="avc3.42c01e,mp4a.40.2" frameRate="25" height="720" id="h264_aac_720p_m4s" mimeType="video/mp4" sar="1:1" startWithSAP="1" width="1280">
|
||||||
|
<SegmentList duration="10000" timescale="1000">
|
||||||
|
<Initialization sourceURL="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/init/432f65a0.mp4"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/0/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/1/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/2/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/3/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/4/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/5/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/6/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/7/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/8/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/9/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/10/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/11/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/12/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/13/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/14/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/15/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/16/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/17/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/18/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/19/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/20/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/21/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/22/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/23/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/24/432f65a0.m4s"/>
|
||||||
|
</SegmentList>
|
||||||
|
</Representation>
|
||||||
|
<Representation audioSamplingRate="44100" bandwidth="4400000" codecs="avc3.42c01e,mp4a.40.2" frameRate="25" height="1080" id="h264_aac_1080p_m4s" mimeType="video/mp4" sar="1:1" startWithSAP="1" width="1920">
|
||||||
|
<SegmentList duration="10000" timescale="1000">
|
||||||
|
<Initialization sourceURL="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/init/432f65a0.mp4"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/0/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/1/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/2/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/3/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/4/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/5/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/6/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/7/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/8/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/9/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/10/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/11/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/12/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/13/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/14/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/15/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/16/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/17/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/18/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/19/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/20/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/21/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/22/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/23/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/24/432f65a0.m4s"/>
|
||||||
|
</SegmentList>
|
||||||
|
</Representation>
|
||||||
|
</AdaptationSet>
|
||||||
|
</Period>
|
||||||
|
</MPD>
|
@ -26,6 +26,8 @@ import tokenize
|
|||||||
import traceback
|
import traceback
|
||||||
import random
|
import random
|
||||||
|
|
||||||
|
from string import ascii_letters
|
||||||
|
|
||||||
from .compat import (
|
from .compat import (
|
||||||
compat_basestring,
|
compat_basestring,
|
||||||
compat_cookiejar,
|
compat_cookiejar,
|
||||||
@ -63,6 +65,7 @@ from .utils import (
|
|||||||
locked_file,
|
locked_file,
|
||||||
make_HTTPS_handler,
|
make_HTTPS_handler,
|
||||||
MaxDownloadsReached,
|
MaxDownloadsReached,
|
||||||
|
orderedSet,
|
||||||
PagedList,
|
PagedList,
|
||||||
parse_filesize,
|
parse_filesize,
|
||||||
PerRequestProxyHandler,
|
PerRequestProxyHandler,
|
||||||
@ -90,6 +93,7 @@ from .utils import (
|
|||||||
)
|
)
|
||||||
from .cache import Cache
|
from .cache import Cache
|
||||||
from .extractor import get_info_extractor, gen_extractor_classes, _LAZY_LOADER
|
from .extractor import get_info_extractor, gen_extractor_classes, _LAZY_LOADER
|
||||||
|
from .extractor.openload import PhantomJSwrapper
|
||||||
from .downloader import get_suitable_downloader
|
from .downloader import get_suitable_downloader
|
||||||
from .downloader.rtmp import rtmpdump_version
|
from .downloader.rtmp import rtmpdump_version
|
||||||
from .postprocessor import (
|
from .postprocessor import (
|
||||||
@ -301,6 +305,12 @@ class YoutubeDL(object):
|
|||||||
otherwise prefer avconv.
|
otherwise prefer avconv.
|
||||||
postprocessor_args: A list of additional command-line arguments for the
|
postprocessor_args: A list of additional command-line arguments for the
|
||||||
postprocessor.
|
postprocessor.
|
||||||
|
|
||||||
|
The following options are used by the Youtube extractor:
|
||||||
|
youtube_include_dash_manifest: If True (default), DASH manifests and related
|
||||||
|
data will be downloaded and processed by extractor.
|
||||||
|
You can reduce network I/O by disabling it if you don't
|
||||||
|
care about DASH.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_NUMERIC_FIELDS = set((
|
_NUMERIC_FIELDS = set((
|
||||||
@ -674,7 +684,19 @@ class YoutubeDL(object):
|
|||||||
FORMAT_RE.format(numeric_field),
|
FORMAT_RE.format(numeric_field),
|
||||||
r'%({0})s'.format(numeric_field), outtmpl)
|
r'%({0})s'.format(numeric_field), outtmpl)
|
||||||
|
|
||||||
filename = expand_path(outtmpl % template_dict)
|
# expand_path translates '%%' into '%' and '$$' into '$'
|
||||||
|
# correspondingly that is not what we want since we need to keep
|
||||||
|
# '%%' intact for template dict substitution step. Working around
|
||||||
|
# with boundary-alike separator hack.
|
||||||
|
sep = ''.join([random.choice(ascii_letters) for _ in range(32)])
|
||||||
|
outtmpl = outtmpl.replace('%%', '%{0}%'.format(sep)).replace('$$', '${0}$'.format(sep))
|
||||||
|
|
||||||
|
# outtmpl should be expand_path'ed before template dict substitution
|
||||||
|
# because meta fields may contain env variables we don't want to
|
||||||
|
# be expanded. For example, for outtmpl "%(title)s.%(ext)s" and
|
||||||
|
# title "Hello $PATH", we don't want `$PATH` to be expanded.
|
||||||
|
filename = expand_path(outtmpl).replace(sep, '') % template_dict
|
||||||
|
|
||||||
# Temporary fix for #4787
|
# Temporary fix for #4787
|
||||||
# 'Treat' all problem characters by passing filename through preferredencoding
|
# 'Treat' all problem characters by passing filename through preferredencoding
|
||||||
# to workaround encoding issues with subprocess on python2 @ Windows
|
# to workaround encoding issues with subprocess on python2 @ Windows
|
||||||
@ -846,7 +868,7 @@ class YoutubeDL(object):
|
|||||||
|
|
||||||
force_properties = dict(
|
force_properties = dict(
|
||||||
(k, v) for k, v in ie_result.items() if v is not None)
|
(k, v) for k, v in ie_result.items() if v is not None)
|
||||||
for f in ('_type', 'url', 'ie_key'):
|
for f in ('_type', 'url', 'id', 'extractor', 'extractor_key', 'ie_key'):
|
||||||
if f in force_properties:
|
if f in force_properties:
|
||||||
del force_properties[f]
|
del force_properties[f]
|
||||||
new_result = info.copy()
|
new_result = info.copy()
|
||||||
@ -887,15 +909,25 @@ class YoutubeDL(object):
|
|||||||
yield int(item)
|
yield int(item)
|
||||||
else:
|
else:
|
||||||
yield int(string_segment)
|
yield int(string_segment)
|
||||||
playlistitems = iter_playlistitems(playlistitems_str)
|
playlistitems = orderedSet(iter_playlistitems(playlistitems_str))
|
||||||
|
|
||||||
ie_entries = ie_result['entries']
|
ie_entries = ie_result['entries']
|
||||||
|
|
||||||
|
def make_playlistitems_entries(list_ie_entries):
|
||||||
|
num_entries = len(list_ie_entries)
|
||||||
|
return [
|
||||||
|
list_ie_entries[i - 1] for i in playlistitems
|
||||||
|
if -num_entries <= i - 1 < num_entries]
|
||||||
|
|
||||||
|
def report_download(num_entries):
|
||||||
|
self.to_screen(
|
||||||
|
'[%s] playlist %s: Downloading %d videos' %
|
||||||
|
(ie_result['extractor'], playlist, num_entries))
|
||||||
|
|
||||||
if isinstance(ie_entries, list):
|
if isinstance(ie_entries, list):
|
||||||
n_all_entries = len(ie_entries)
|
n_all_entries = len(ie_entries)
|
||||||
if playlistitems:
|
if playlistitems:
|
||||||
entries = [
|
entries = make_playlistitems_entries(ie_entries)
|
||||||
ie_entries[i - 1] for i in playlistitems
|
|
||||||
if -n_all_entries <= i - 1 < n_all_entries]
|
|
||||||
else:
|
else:
|
||||||
entries = ie_entries[playliststart:playlistend]
|
entries = ie_entries[playliststart:playlistend]
|
||||||
n_entries = len(entries)
|
n_entries = len(entries)
|
||||||
@ -913,20 +945,16 @@ class YoutubeDL(object):
|
|||||||
entries = ie_entries.getslice(
|
entries = ie_entries.getslice(
|
||||||
playliststart, playlistend)
|
playliststart, playlistend)
|
||||||
n_entries = len(entries)
|
n_entries = len(entries)
|
||||||
self.to_screen(
|
report_download(n_entries)
|
||||||
'[%s] playlist %s: Downloading %d videos' %
|
|
||||||
(ie_result['extractor'], playlist, n_entries))
|
|
||||||
else: # iterable
|
else: # iterable
|
||||||
if playlistitems:
|
if playlistitems:
|
||||||
entry_list = list(ie_entries)
|
entries = make_playlistitems_entries(list(itertools.islice(
|
||||||
entries = [entry_list[i - 1] for i in playlistitems]
|
ie_entries, 0, max(playlistitems))))
|
||||||
else:
|
else:
|
||||||
entries = list(itertools.islice(
|
entries = list(itertools.islice(
|
||||||
ie_entries, playliststart, playlistend))
|
ie_entries, playliststart, playlistend))
|
||||||
n_entries = len(entries)
|
n_entries = len(entries)
|
||||||
self.to_screen(
|
report_download(n_entries)
|
||||||
'[%s] playlist %s: Downloading %d videos' %
|
|
||||||
(ie_result['extractor'], playlist, n_entries))
|
|
||||||
|
|
||||||
if self.params.get('playlistreverse', False):
|
if self.params.get('playlistreverse', False):
|
||||||
entries = entries[::-1]
|
entries = entries[::-1]
|
||||||
@ -1050,6 +1078,30 @@ class YoutubeDL(object):
|
|||||||
return op(actual_value, comparison_value)
|
return op(actual_value, comparison_value)
|
||||||
return _filter
|
return _filter
|
||||||
|
|
||||||
|
def _default_format_spec(self, info_dict, download=True):
|
||||||
|
|
||||||
|
def can_merge():
|
||||||
|
merger = FFmpegMergerPP(self)
|
||||||
|
return merger.available and merger.can_merge()
|
||||||
|
|
||||||
|
def prefer_best():
|
||||||
|
if self.params.get('simulate', False):
|
||||||
|
return False
|
||||||
|
if not download:
|
||||||
|
return False
|
||||||
|
if self.params.get('outtmpl', DEFAULT_OUTTMPL) == '-':
|
||||||
|
return True
|
||||||
|
if info_dict.get('is_live'):
|
||||||
|
return True
|
||||||
|
if not can_merge():
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
req_format_list = ['bestvideo+bestaudio', 'best']
|
||||||
|
if prefer_best():
|
||||||
|
req_format_list.reverse()
|
||||||
|
return '/'.join(req_format_list)
|
||||||
|
|
||||||
def build_format_selector(self, format_spec):
|
def build_format_selector(self, format_spec):
|
||||||
def syntax_error(note, start):
|
def syntax_error(note, start):
|
||||||
message = (
|
message = (
|
||||||
@ -1448,18 +1500,28 @@ class YoutubeDL(object):
|
|||||||
if not formats:
|
if not formats:
|
||||||
raise ExtractorError('No video formats found!')
|
raise ExtractorError('No video formats found!')
|
||||||
|
|
||||||
|
def is_wellformed(f):
|
||||||
|
url = f.get('url')
|
||||||
|
if not url:
|
||||||
|
self.report_warning(
|
||||||
|
'"url" field is missing or empty - skipping format, '
|
||||||
|
'there is an error in extractor')
|
||||||
|
return False
|
||||||
|
if isinstance(url, bytes):
|
||||||
|
sanitize_string_field(f, 'url')
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Filter out malformed formats for better extraction robustness
|
||||||
|
formats = list(filter(is_wellformed, formats))
|
||||||
|
|
||||||
formats_dict = {}
|
formats_dict = {}
|
||||||
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
sanitize_string_field(format, 'format_id')
|
sanitize_string_field(format, 'format_id')
|
||||||
sanitize_numeric_fields(format)
|
sanitize_numeric_fields(format)
|
||||||
format['url'] = sanitize_url(format['url'])
|
format['url'] = sanitize_url(format['url'])
|
||||||
|
if not format.get('format_id'):
|
||||||
if format.get('format_id') is None:
|
|
||||||
format['format_id'] = compat_str(i)
|
format['format_id'] = compat_str(i)
|
||||||
else:
|
else:
|
||||||
# Sanitize format_id from characters used in format selector expression
|
# Sanitize format_id from characters used in format selector expression
|
||||||
@ -1512,14 +1574,10 @@ class YoutubeDL(object):
|
|||||||
|
|
||||||
req_format = self.params.get('format')
|
req_format = self.params.get('format')
|
||||||
if req_format is None:
|
if req_format is None:
|
||||||
req_format_list = []
|
req_format = self._default_format_spec(info_dict, download=download)
|
||||||
if (self.params.get('outtmpl', DEFAULT_OUTTMPL) != '-' and
|
if self.params.get('verbose'):
|
||||||
not info_dict.get('is_live')):
|
self.to_stdout('[debug] Default format spec: %s' % req_format)
|
||||||
merger = FFmpegMergerPP(self)
|
|
||||||
if merger.available and merger.can_merge():
|
|
||||||
req_format_list.append('bestvideo+bestaudio')
|
|
||||||
req_format_list.append('best')
|
|
||||||
req_format = '/'.join(req_format_list)
|
|
||||||
format_selector = self.build_format_selector(req_format)
|
format_selector = self.build_format_selector(req_format)
|
||||||
|
|
||||||
# While in format selection we may need to have an access to the original
|
# While in format selection we may need to have an access to the original
|
||||||
@ -1671,12 +1729,17 @@ class YoutubeDL(object):
|
|||||||
if filename is None:
|
if filename is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def ensure_dir_exists(path):
|
||||||
try:
|
try:
|
||||||
dn = os.path.dirname(sanitize_path(encodeFilename(filename)))
|
dn = os.path.dirname(path)
|
||||||
if dn and not os.path.exists(dn):
|
if dn and not os.path.exists(dn):
|
||||||
os.makedirs(dn)
|
os.makedirs(dn)
|
||||||
|
return True
|
||||||
except (OSError, IOError) as err:
|
except (OSError, IOError) as err:
|
||||||
self.report_error('unable to create directory ' + error_to_compat_str(err))
|
self.report_error('unable to create directory ' + error_to_compat_str(err))
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not ensure_dir_exists(sanitize_path(encodeFilename(filename))):
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.params.get('writedescription', False):
|
if self.params.get('writedescription', False):
|
||||||
@ -1719,29 +1782,30 @@ class YoutubeDL(object):
|
|||||||
ie = self.get_info_extractor(info_dict['extractor_key'])
|
ie = self.get_info_extractor(info_dict['extractor_key'])
|
||||||
for sub_lang, sub_info in subtitles.items():
|
for sub_lang, sub_info in subtitles.items():
|
||||||
sub_format = sub_info['ext']
|
sub_format = sub_info['ext']
|
||||||
if sub_info.get('data') is not None:
|
|
||||||
sub_data = sub_info['data']
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
sub_data = ie._download_webpage(
|
|
||||||
sub_info['url'], info_dict['id'], note=False)
|
|
||||||
except ExtractorError as err:
|
|
||||||
self.report_warning('Unable to download subtitle for "%s": %s' %
|
|
||||||
(sub_lang, error_to_compat_str(err.cause)))
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
sub_filename = subtitles_filename(filename, sub_lang, sub_format)
|
sub_filename = subtitles_filename(filename, sub_lang, sub_format)
|
||||||
if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(sub_filename)):
|
if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(sub_filename)):
|
||||||
self.to_screen('[info] Video subtitle %s.%s is already_present' % (sub_lang, sub_format))
|
self.to_screen('[info] Video subtitle %s.%s is already present' % (sub_lang, sub_format))
|
||||||
else:
|
else:
|
||||||
self.to_screen('[info] Writing video subtitles to: ' + sub_filename)
|
self.to_screen('[info] Writing video subtitles to: ' + sub_filename)
|
||||||
|
if sub_info.get('data') is not None:
|
||||||
|
try:
|
||||||
# Use newline='' to prevent conversion of newline characters
|
# Use newline='' to prevent conversion of newline characters
|
||||||
# See https://github.com/rg3/youtube-dl/issues/10268
|
# See https://github.com/rg3/youtube-dl/issues/10268
|
||||||
with io.open(encodeFilename(sub_filename), 'w', encoding='utf-8', newline='') as subfile:
|
with io.open(encodeFilename(sub_filename), 'w', encoding='utf-8', newline='') as subfile:
|
||||||
subfile.write(sub_data)
|
subfile.write(sub_info['data'])
|
||||||
except (OSError, IOError):
|
except (OSError, IOError):
|
||||||
self.report_error('Cannot write subtitles file ' + sub_filename)
|
self.report_error('Cannot write subtitles file ' + sub_filename)
|
||||||
return
|
return
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
sub_data = ie._request_webpage(
|
||||||
|
sub_info['url'], info_dict['id'], note=False).read()
|
||||||
|
with io.open(encodeFilename(sub_filename), 'wb') as subfile:
|
||||||
|
subfile.write(sub_data)
|
||||||
|
except (ExtractorError, IOError, OSError, ValueError) as err:
|
||||||
|
self.report_warning('Unable to download subtitle for "%s": %s' %
|
||||||
|
(sub_lang, error_to_compat_str(err)))
|
||||||
|
continue
|
||||||
|
|
||||||
if self.params.get('writeinfojson', False):
|
if self.params.get('writeinfojson', False):
|
||||||
infofn = replace_extension(filename, 'info.json', info_dict.get('ext'))
|
infofn = replace_extension(filename, 'info.json', info_dict.get('ext'))
|
||||||
@ -1814,8 +1878,11 @@ class YoutubeDL(object):
|
|||||||
for f in requested_formats:
|
for f in requested_formats:
|
||||||
new_info = dict(info_dict)
|
new_info = dict(info_dict)
|
||||||
new_info.update(f)
|
new_info.update(f)
|
||||||
fname = self.prepare_filename(new_info)
|
fname = prepend_extension(
|
||||||
fname = prepend_extension(fname, 'f%s' % f['format_id'], new_info['ext'])
|
self.prepare_filename(new_info),
|
||||||
|
'f%s' % f['format_id'], new_info['ext'])
|
||||||
|
if not ensure_dir_exists(fname):
|
||||||
|
return
|
||||||
downloaded.append(fname)
|
downloaded.append(fname)
|
||||||
partial_success = dl(fname, new_info)
|
partial_success = dl(fname, new_info)
|
||||||
success = success and partial_success
|
success = success and partial_success
|
||||||
@ -1882,7 +1949,7 @@ class YoutubeDL(object):
|
|||||||
info_dict.get('protocol') == 'm3u8' and
|
info_dict.get('protocol') == 'm3u8' and
|
||||||
self.params.get('hls_prefer_native')):
|
self.params.get('hls_prefer_native')):
|
||||||
if fixup_policy == 'warn':
|
if fixup_policy == 'warn':
|
||||||
self.report_warning('%s: malformated aac bitstream.' % (
|
self.report_warning('%s: malformed AAC bitstream detected.' % (
|
||||||
info_dict['id']))
|
info_dict['id']))
|
||||||
elif fixup_policy == 'detect_or_warn':
|
elif fixup_policy == 'detect_or_warn':
|
||||||
fixup_pp = FFmpegFixupM3u8PP(self)
|
fixup_pp = FFmpegFixupM3u8PP(self)
|
||||||
@ -1891,7 +1958,7 @@ class YoutubeDL(object):
|
|||||||
info_dict['__postprocessors'].append(fixup_pp)
|
info_dict['__postprocessors'].append(fixup_pp)
|
||||||
else:
|
else:
|
||||||
self.report_warning(
|
self.report_warning(
|
||||||
'%s: malformated aac bitstream. %s'
|
'%s: malformed AAC bitstream detected. %s'
|
||||||
% (info_dict['id'], INSTALL_FFMPEG_MESSAGE))
|
% (info_dict['id'], INSTALL_FFMPEG_MESSAGE))
|
||||||
else:
|
else:
|
||||||
assert fixup_policy in ('ignore', 'never')
|
assert fixup_policy in ('ignore', 'never')
|
||||||
@ -2169,6 +2236,7 @@ class YoutubeDL(object):
|
|||||||
|
|
||||||
exe_versions = FFmpegPostProcessor.get_versions(self)
|
exe_versions = FFmpegPostProcessor.get_versions(self)
|
||||||
exe_versions['rtmpdump'] = rtmpdump_version()
|
exe_versions['rtmpdump'] = rtmpdump_version()
|
||||||
|
exe_versions['phantomjs'] = PhantomJSwrapper._version()
|
||||||
exe_str = ', '.join(
|
exe_str = ', '.join(
|
||||||
'%s %s' % (exe, v)
|
'%s %s' % (exe, v)
|
||||||
for exe, v in sorted(exe_versions.items())
|
for exe, v in sorted(exe_versions.items())
|
||||||
|
@ -206,7 +206,7 @@ def _real_main(argv=None):
|
|||||||
if opts.recodevideo not in ['mp4', 'flv', 'webm', 'ogg', 'mkv', 'avi']:
|
if opts.recodevideo not in ['mp4', 'flv', 'webm', 'ogg', 'mkv', 'avi']:
|
||||||
parser.error('invalid video recode format specified')
|
parser.error('invalid video recode format specified')
|
||||||
if opts.convertsubtitles is not None:
|
if opts.convertsubtitles is not None:
|
||||||
if opts.convertsubtitles not in ['srt', 'vtt', 'ass']:
|
if opts.convertsubtitles not in ['srt', 'vtt', 'ass', 'lrc']:
|
||||||
parser.error('invalid subtitle format specified')
|
parser.error('invalid subtitle format specified')
|
||||||
|
|
||||||
if opts.date is not None:
|
if opts.date is not None:
|
||||||
|
@ -6,6 +6,7 @@ import collections
|
|||||||
import email
|
import email
|
||||||
import getpass
|
import getpass
|
||||||
import io
|
import io
|
||||||
|
import itertools
|
||||||
import optparse
|
import optparse
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
@ -15,7 +16,6 @@ import socket
|
|||||||
import struct
|
import struct
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import itertools
|
|
||||||
import xml.etree.ElementTree
|
import xml.etree.ElementTree
|
||||||
|
|
||||||
|
|
||||||
@ -2898,6 +2898,13 @@ else:
|
|||||||
compat_struct_pack = struct.pack
|
compat_struct_pack = struct.pack
|
||||||
compat_struct_unpack = struct.unpack
|
compat_struct_unpack = struct.unpack
|
||||||
|
|
||||||
|
try:
|
||||||
|
from future_builtins import zip as compat_zip
|
||||||
|
except ImportError: # not 2.6+ or is 3.x
|
||||||
|
try:
|
||||||
|
from itertools import izip as compat_zip # < 2.5 or 3.x
|
||||||
|
except ImportError:
|
||||||
|
compat_zip = zip
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'compat_HTMLParseError',
|
'compat_HTMLParseError',
|
||||||
@ -2948,5 +2955,6 @@ __all__ = [
|
|||||||
'compat_urlretrieve',
|
'compat_urlretrieve',
|
||||||
'compat_xml_parse_error',
|
'compat_xml_parse_error',
|
||||||
'compat_xpath',
|
'compat_xpath',
|
||||||
|
'compat_zip',
|
||||||
'workaround_optparse_bug9161',
|
'workaround_optparse_bug9161',
|
||||||
]
|
]
|
||||||
|
@ -304,11 +304,11 @@ class FileDownloader(object):
|
|||||||
"""Report attempt to resume at given byte."""
|
"""Report attempt to resume at given byte."""
|
||||||
self.to_screen('[download] Resuming download at byte %s' % resume_len)
|
self.to_screen('[download] Resuming download at byte %s' % resume_len)
|
||||||
|
|
||||||
def report_retry(self, count, retries):
|
def report_retry(self, err, count, retries):
|
||||||
"""Report retry in case of HTTP error 5xx"""
|
"""Report retry in case of HTTP error 5xx"""
|
||||||
self.to_screen(
|
self.to_screen(
|
||||||
'[download] Got server HTTP error. Retrying (attempt %d of %s)...'
|
'[download] Got server HTTP error: %s. Retrying (attempt %d of %s)...'
|
||||||
% (count, self.format_retries(retries)))
|
% (error_to_compat_str(err), count, self.format_retries(retries)))
|
||||||
|
|
||||||
def report_file_already_downloaded(self, file_name):
|
def report_file_already_downloaded(self, file_name):
|
||||||
"""Report file has already been fully downloaded."""
|
"""Report file has already been fully downloaded."""
|
||||||
|
@ -2,6 +2,7 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from .fragment import FragmentFD
|
from .fragment import FragmentFD
|
||||||
from ..compat import compat_urllib_error
|
from ..compat import compat_urllib_error
|
||||||
|
from ..utils import urljoin
|
||||||
|
|
||||||
|
|
||||||
class DashSegmentsFD(FragmentFD):
|
class DashSegmentsFD(FragmentFD):
|
||||||
@ -12,12 +13,13 @@ class DashSegmentsFD(FragmentFD):
|
|||||||
FD_NAME = 'dashsegments'
|
FD_NAME = 'dashsegments'
|
||||||
|
|
||||||
def real_download(self, filename, info_dict):
|
def real_download(self, filename, info_dict):
|
||||||
segments = info_dict['fragments'][:1] if self.params.get(
|
fragment_base_url = info_dict.get('fragment_base_url')
|
||||||
|
fragments = info_dict['fragments'][:1] if self.params.get(
|
||||||
'test', False) else info_dict['fragments']
|
'test', False) else info_dict['fragments']
|
||||||
|
|
||||||
ctx = {
|
ctx = {
|
||||||
'filename': filename,
|
'filename': filename,
|
||||||
'total_frags': len(segments),
|
'total_frags': len(fragments),
|
||||||
}
|
}
|
||||||
|
|
||||||
self._prepare_and_start_frag_download(ctx)
|
self._prepare_and_start_frag_download(ctx)
|
||||||
@ -26,7 +28,7 @@ class DashSegmentsFD(FragmentFD):
|
|||||||
skip_unavailable_fragments = self.params.get('skip_unavailable_fragments', True)
|
skip_unavailable_fragments = self.params.get('skip_unavailable_fragments', True)
|
||||||
|
|
||||||
frag_index = 0
|
frag_index = 0
|
||||||
for i, segment in enumerate(segments):
|
for i, fragment in enumerate(fragments):
|
||||||
frag_index += 1
|
frag_index += 1
|
||||||
if frag_index <= ctx['fragment_index']:
|
if frag_index <= ctx['fragment_index']:
|
||||||
continue
|
continue
|
||||||
@ -36,7 +38,11 @@ class DashSegmentsFD(FragmentFD):
|
|||||||
count = 0
|
count = 0
|
||||||
while count <= fragment_retries:
|
while count <= fragment_retries:
|
||||||
try:
|
try:
|
||||||
success, frag_content = self._download_fragment(ctx, segment['url'], info_dict)
|
fragment_url = fragment.get('url')
|
||||||
|
if not fragment_url:
|
||||||
|
assert fragment_base_url
|
||||||
|
fragment_url = urljoin(fragment_base_url, fragment['path'])
|
||||||
|
success, frag_content = self._download_fragment(ctx, fragment_url, info_dict)
|
||||||
if not success:
|
if not success:
|
||||||
return False
|
return False
|
||||||
self._append_fragment(ctx, frag_content)
|
self._append_fragment(ctx, frag_content)
|
||||||
|
@ -243,8 +243,17 @@ def remove_encrypted_media(media):
|
|||||||
media))
|
media))
|
||||||
|
|
||||||
|
|
||||||
def _add_ns(prop):
|
def _add_ns(prop, ver=1):
|
||||||
return '{http://ns.adobe.com/f4m/1.0}%s' % prop
|
return '{http://ns.adobe.com/f4m/%d.0}%s' % (ver, prop)
|
||||||
|
|
||||||
|
|
||||||
|
def get_base_url(manifest):
|
||||||
|
base_url = xpath_text(
|
||||||
|
manifest, [_add_ns('baseURL'), _add_ns('baseURL', 2)],
|
||||||
|
'base URL', default=None)
|
||||||
|
if base_url:
|
||||||
|
base_url = base_url.strip()
|
||||||
|
return base_url
|
||||||
|
|
||||||
|
|
||||||
class F4mFD(FragmentFD):
|
class F4mFD(FragmentFD):
|
||||||
@ -330,13 +339,13 @@ class F4mFD(FragmentFD):
|
|||||||
rate, media = list(filter(
|
rate, media = list(filter(
|
||||||
lambda f: int(f[0]) == requested_bitrate, formats))[0]
|
lambda f: int(f[0]) == requested_bitrate, formats))[0]
|
||||||
|
|
||||||
base_url = compat_urlparse.urljoin(man_url, media.attrib['url'])
|
# Prefer baseURL for relative URLs as per 11.2 of F4M 3.0 spec.
|
||||||
|
man_base_url = get_base_url(doc) or man_url
|
||||||
|
|
||||||
|
base_url = compat_urlparse.urljoin(man_base_url, media.attrib['url'])
|
||||||
bootstrap_node = doc.find(_add_ns('bootstrapInfo'))
|
bootstrap_node = doc.find(_add_ns('bootstrapInfo'))
|
||||||
# From Adobe F4M 3.0 spec:
|
boot_info, bootstrap_url = self._parse_bootstrap_node(
|
||||||
# The <baseURL> element SHALL be the base URL for all relative
|
bootstrap_node, man_base_url)
|
||||||
# (HTTP-based) URLs in the manifest. If <baseURL> is not present, said
|
|
||||||
# URLs should be relative to the location of the containing document.
|
|
||||||
boot_info, bootstrap_url = self._parse_bootstrap_node(bootstrap_node, man_url)
|
|
||||||
live = boot_info['live']
|
live = boot_info['live']
|
||||||
metadata_node = media.find(_add_ns('metadata'))
|
metadata_node = media.find(_add_ns('metadata'))
|
||||||
if metadata_node is not None:
|
if metadata_node is not None:
|
||||||
|
@ -107,6 +107,7 @@ class FragmentFD(FileDownloader):
|
|||||||
def _append_fragment(self, ctx, frag_content):
|
def _append_fragment(self, ctx, frag_content):
|
||||||
try:
|
try:
|
||||||
ctx['dest_stream'].write(frag_content)
|
ctx['dest_stream'].write(frag_content)
|
||||||
|
ctx['dest_stream'].flush()
|
||||||
finally:
|
finally:
|
||||||
if self.__do_ytdl_file(ctx):
|
if self.__do_ytdl_file(ctx):
|
||||||
self._write_ytdl_file(ctx)
|
self._write_ytdl_file(ctx)
|
||||||
@ -117,9 +118,15 @@ class FragmentFD(FileDownloader):
|
|||||||
def _prepare_frag_download(self, ctx):
|
def _prepare_frag_download(self, ctx):
|
||||||
if 'live' not in ctx:
|
if 'live' not in ctx:
|
||||||
ctx['live'] = False
|
ctx['live'] = False
|
||||||
|
if not ctx['live']:
|
||||||
|
total_frags_str = '%d' % ctx['total_frags']
|
||||||
|
ad_frags = ctx.get('ad_frags', 0)
|
||||||
|
if ad_frags:
|
||||||
|
total_frags_str += ' (not including %d ad)' % ad_frags
|
||||||
|
else:
|
||||||
|
total_frags_str = 'unknown (live)'
|
||||||
self.to_screen(
|
self.to_screen(
|
||||||
'[%s] Total fragments: %s'
|
'[%s] Total fragments: %s' % (self.FD_NAME, total_frags_str))
|
||||||
% (self.FD_NAME, ctx['total_frags'] if not ctx['live'] else 'unknown (live)'))
|
|
||||||
self.report_destination(ctx['filename'])
|
self.report_destination(ctx['filename'])
|
||||||
dl = HttpQuietDownloader(
|
dl = HttpQuietDownloader(
|
||||||
self.ydl,
|
self.ydl,
|
||||||
@ -151,10 +158,15 @@ class FragmentFD(FileDownloader):
|
|||||||
if self.__do_ytdl_file(ctx):
|
if self.__do_ytdl_file(ctx):
|
||||||
if os.path.isfile(encodeFilename(self.ytdl_filename(ctx['filename']))):
|
if os.path.isfile(encodeFilename(self.ytdl_filename(ctx['filename']))):
|
||||||
self._read_ytdl_file(ctx)
|
self._read_ytdl_file(ctx)
|
||||||
|
if ctx['fragment_index'] > 0 and resume_len == 0:
|
||||||
|
self.report_warning(
|
||||||
|
'Inconsistent state of incomplete fragment download. '
|
||||||
|
'Restarting from the beginning...')
|
||||||
|
ctx['fragment_index'] = resume_len = 0
|
||||||
|
self._write_ytdl_file(ctx)
|
||||||
else:
|
else:
|
||||||
self._write_ytdl_file(ctx)
|
self._write_ytdl_file(ctx)
|
||||||
if ctx['fragment_index'] > 0:
|
assert ctx['fragment_index'] == 0
|
||||||
assert resume_len > 0
|
|
||||||
|
|
||||||
dest_stream, tmpfilename = sanitize_open(tmpfilename, open_mode)
|
dest_stream, tmpfilename = sanitize_open(tmpfilename, open_mode)
|
||||||
|
|
||||||
|
@ -59,9 +59,9 @@ class HlsFD(FragmentFD):
|
|||||||
man_url = info_dict['url']
|
man_url = info_dict['url']
|
||||||
self.to_screen('[%s] Downloading m3u8 manifest' % self.FD_NAME)
|
self.to_screen('[%s] Downloading m3u8 manifest' % self.FD_NAME)
|
||||||
|
|
||||||
manifest = self.ydl.urlopen(self._prepare_url(info_dict, man_url)).read()
|
urlh = self.ydl.urlopen(self._prepare_url(info_dict, man_url))
|
||||||
|
man_url = urlh.geturl()
|
||||||
s = manifest.decode('utf-8', 'ignore')
|
s = urlh.read().decode('utf-8', 'ignore')
|
||||||
|
|
||||||
if not self.can_download(s, info_dict):
|
if not self.can_download(s, info_dict):
|
||||||
if info_dict.get('extra_param_to_segment_url'):
|
if info_dict.get('extra_param_to_segment_url'):
|
||||||
@ -75,15 +75,30 @@ class HlsFD(FragmentFD):
|
|||||||
fd.add_progress_hook(ph)
|
fd.add_progress_hook(ph)
|
||||||
return fd.real_download(filename, info_dict)
|
return fd.real_download(filename, info_dict)
|
||||||
|
|
||||||
total_frags = 0
|
def anvato_ad(s):
|
||||||
|
return s.startswith('#ANVATO-SEGMENT-INFO') and 'type=ad' in s
|
||||||
|
|
||||||
|
media_frags = 0
|
||||||
|
ad_frags = 0
|
||||||
|
ad_frag_next = False
|
||||||
for line in s.splitlines():
|
for line in s.splitlines():
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
if line and not line.startswith('#'):
|
if not line:
|
||||||
total_frags += 1
|
continue
|
||||||
|
if line.startswith('#'):
|
||||||
|
if anvato_ad(line):
|
||||||
|
ad_frags += 1
|
||||||
|
ad_frag_next = True
|
||||||
|
continue
|
||||||
|
if ad_frag_next:
|
||||||
|
ad_frag_next = False
|
||||||
|
continue
|
||||||
|
media_frags += 1
|
||||||
|
|
||||||
ctx = {
|
ctx = {
|
||||||
'filename': filename,
|
'filename': filename,
|
||||||
'total_frags': total_frags,
|
'total_frags': media_frags,
|
||||||
|
'ad_frags': ad_frags,
|
||||||
}
|
}
|
||||||
|
|
||||||
self._prepare_and_start_frag_download(ctx)
|
self._prepare_and_start_frag_download(ctx)
|
||||||
@ -101,10 +116,14 @@ class HlsFD(FragmentFD):
|
|||||||
decrypt_info = {'METHOD': 'NONE'}
|
decrypt_info = {'METHOD': 'NONE'}
|
||||||
byte_range = {}
|
byte_range = {}
|
||||||
frag_index = 0
|
frag_index = 0
|
||||||
|
ad_frag_next = False
|
||||||
for line in s.splitlines():
|
for line in s.splitlines():
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
if line:
|
if line:
|
||||||
if not line.startswith('#'):
|
if not line.startswith('#'):
|
||||||
|
if ad_frag_next:
|
||||||
|
ad_frag_next = False
|
||||||
|
continue
|
||||||
frag_index += 1
|
frag_index += 1
|
||||||
if frag_index <= ctx['fragment_index']:
|
if frag_index <= ctx['fragment_index']:
|
||||||
continue
|
continue
|
||||||
@ -175,6 +194,8 @@ class HlsFD(FragmentFD):
|
|||||||
'start': sub_range_start,
|
'start': sub_range_start,
|
||||||
'end': sub_range_start + int(splitted_byte_range[0]),
|
'end': sub_range_start + int(splitted_byte_range[0]),
|
||||||
}
|
}
|
||||||
|
elif anvato_ad(line):
|
||||||
|
ad_frag_next = True
|
||||||
|
|
||||||
self._finish_frag_download(ctx)
|
self._finish_frag_download(ctx)
|
||||||
|
|
||||||
|
@ -22,8 +22,16 @@ from ..utils import (
|
|||||||
class HttpFD(FileDownloader):
|
class HttpFD(FileDownloader):
|
||||||
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)
|
|
||||||
stream = None
|
class DownloadContext(dict):
|
||||||
|
__getattr__ = dict.get
|
||||||
|
__setattr__ = dict.__setitem__
|
||||||
|
__delattr__ = dict.__delitem__
|
||||||
|
|
||||||
|
ctx = DownloadContext()
|
||||||
|
ctx.filename = filename
|
||||||
|
ctx.tmpfilename = self.temp_name(filename)
|
||||||
|
ctx.stream = None
|
||||||
|
|
||||||
# Do not include the Accept-Encoding header
|
# Do not include the Accept-Encoding header
|
||||||
headers = {'Youtubedl-no-compression': 'True'}
|
headers = {'Youtubedl-no-compression': 'True'}
|
||||||
@ -38,46 +46,51 @@ class HttpFD(FileDownloader):
|
|||||||
if is_test:
|
if is_test:
|
||||||
request.add_header('Range', 'bytes=0-%s' % str(self._TEST_FILE_SIZE - 1))
|
request.add_header('Range', 'bytes=0-%s' % str(self._TEST_FILE_SIZE - 1))
|
||||||
|
|
||||||
# Establish possible resume length
|
ctx.open_mode = 'wb'
|
||||||
if os.path.isfile(encodeFilename(tmpfilename)):
|
ctx.resume_len = 0
|
||||||
resume_len = os.path.getsize(encodeFilename(tmpfilename))
|
|
||||||
else:
|
|
||||||
resume_len = 0
|
|
||||||
|
|
||||||
open_mode = 'wb'
|
|
||||||
if resume_len != 0:
|
|
||||||
if self.params.get('continuedl', True):
|
if self.params.get('continuedl', True):
|
||||||
self.report_resuming_byte(resume_len)
|
# Establish possible resume length
|
||||||
request.add_header('Range', 'bytes=%d-' % resume_len)
|
if os.path.isfile(encodeFilename(ctx.tmpfilename)):
|
||||||
open_mode = 'ab'
|
ctx.resume_len = os.path.getsize(encodeFilename(ctx.tmpfilename))
|
||||||
else:
|
|
||||||
resume_len = 0
|
|
||||||
|
|
||||||
count = 0
|
count = 0
|
||||||
retries = self.params.get('retries', 0)
|
retries = self.params.get('retries', 0)
|
||||||
while count <= retries:
|
|
||||||
|
class SucceedDownload(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class RetryDownload(Exception):
|
||||||
|
def __init__(self, source_error):
|
||||||
|
self.source_error = source_error
|
||||||
|
|
||||||
|
def establish_connection():
|
||||||
|
if ctx.resume_len != 0:
|
||||||
|
self.report_resuming_byte(ctx.resume_len)
|
||||||
|
request.add_header('Range', 'bytes=%d-' % ctx.resume_len)
|
||||||
|
ctx.open_mode = 'ab'
|
||||||
# Establish connection
|
# Establish connection
|
||||||
try:
|
try:
|
||||||
data = self.ydl.urlopen(request)
|
ctx.data = self.ydl.urlopen(request)
|
||||||
# When trying to resume, Content-Range HTTP header of response has to be checked
|
# When trying to resume, Content-Range HTTP header of response has to be checked
|
||||||
# to match the value of requested Range HTTP header. This is due to a webservers
|
# to match the value of requested Range HTTP header. This is due to a webservers
|
||||||
# that don't support resuming and serve a whole file with no Content-Range
|
# that don't support resuming and serve a whole file with no Content-Range
|
||||||
# set in response despite of requested Range (see
|
# set in response despite of requested Range (see
|
||||||
# https://github.com/rg3/youtube-dl/issues/6057#issuecomment-126129799)
|
# https://github.com/rg3/youtube-dl/issues/6057#issuecomment-126129799)
|
||||||
if resume_len > 0:
|
if ctx.resume_len > 0:
|
||||||
content_range = data.headers.get('Content-Range')
|
content_range = ctx.data.headers.get('Content-Range')
|
||||||
if content_range:
|
if content_range:
|
||||||
content_range_m = re.search(r'bytes (\d+)-', content_range)
|
content_range_m = re.search(r'bytes (\d+)-', content_range)
|
||||||
# Content-Range is present and matches requested Range, resume is possible
|
# Content-Range is present and matches requested Range, resume is possible
|
||||||
if content_range_m and resume_len == int(content_range_m.group(1)):
|
if content_range_m and ctx.resume_len == int(content_range_m.group(1)):
|
||||||
break
|
return
|
||||||
# Content-Range is either not present or invalid. Assuming remote webserver is
|
# Content-Range is either not present or invalid. Assuming remote webserver is
|
||||||
# trying to send the whole file, resume is not possible, so wiping the local file
|
# trying to send the whole file, resume is not possible, so wiping the local file
|
||||||
# and performing entire redownload
|
# and performing entire redownload
|
||||||
self.report_unable_to_resume()
|
self.report_unable_to_resume()
|
||||||
resume_len = 0
|
ctx.resume_len = 0
|
||||||
open_mode = 'wb'
|
ctx.open_mode = 'wb'
|
||||||
break
|
return
|
||||||
except (compat_urllib_error.HTTPError, ) as err:
|
except (compat_urllib_error.HTTPError, ) as err:
|
||||||
if (err.code < 500 or err.code >= 600) and err.code != 416:
|
if (err.code < 500 or err.code >= 600) and err.code != 416:
|
||||||
# Unexpected HTTP error
|
# Unexpected HTTP error
|
||||||
@ -86,15 +99,15 @@ class HttpFD(FileDownloader):
|
|||||||
# Unable to resume (requested range not satisfiable)
|
# Unable to resume (requested range not satisfiable)
|
||||||
try:
|
try:
|
||||||
# Open the connection again without the range header
|
# Open the connection again without the range header
|
||||||
data = self.ydl.urlopen(basic_request)
|
ctx.data = self.ydl.urlopen(basic_request)
|
||||||
content_length = data.info()['Content-Length']
|
content_length = ctx.data.info()['Content-Length']
|
||||||
except (compat_urllib_error.HTTPError, ) as err:
|
except (compat_urllib_error.HTTPError, ) as err:
|
||||||
if err.code < 500 or err.code >= 600:
|
if err.code < 500 or err.code >= 600:
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
# Examine the reported length
|
# Examine the reported length
|
||||||
if (content_length is not None and
|
if (content_length is not None and
|
||||||
(resume_len - 100 < int(content_length) < resume_len + 100)):
|
(ctx.resume_len - 100 < int(content_length) < ctx.resume_len + 100)):
|
||||||
# The file had already been fully downloaded.
|
# The file had already been fully downloaded.
|
||||||
# Explanation to the above condition: in issue #175 it was revealed that
|
# Explanation to the above condition: in issue #175 it was revealed that
|
||||||
# YouTube sometimes adds or removes a few bytes from the end of the file,
|
# YouTube sometimes adds or removes a few bytes from the end of the file,
|
||||||
@ -102,36 +115,30 @@ class HttpFD(FileDownloader):
|
|||||||
# I decided to implement a suggested change and consider the file
|
# I decided to implement a suggested change and consider the file
|
||||||
# completely downloaded if the file size differs less than 100 bytes from
|
# completely downloaded if the file size differs less than 100 bytes from
|
||||||
# the one in the hard drive.
|
# the one in the hard drive.
|
||||||
self.report_file_already_downloaded(filename)
|
self.report_file_already_downloaded(ctx.filename)
|
||||||
self.try_rename(tmpfilename, filename)
|
self.try_rename(ctx.tmpfilename, ctx.filename)
|
||||||
self._hook_progress({
|
self._hook_progress({
|
||||||
'filename': filename,
|
'filename': ctx.filename,
|
||||||
'status': 'finished',
|
'status': 'finished',
|
||||||
'downloaded_bytes': resume_len,
|
'downloaded_bytes': ctx.resume_len,
|
||||||
'total_bytes': resume_len,
|
'total_bytes': ctx.resume_len,
|
||||||
})
|
})
|
||||||
return True
|
raise SucceedDownload()
|
||||||
else:
|
else:
|
||||||
# The length does not match, we start the download over
|
# The length does not match, we start the download over
|
||||||
self.report_unable_to_resume()
|
self.report_unable_to_resume()
|
||||||
resume_len = 0
|
ctx.resume_len = 0
|
||||||
open_mode = 'wb'
|
ctx.open_mode = 'wb'
|
||||||
break
|
return
|
||||||
except socket.error as e:
|
raise RetryDownload(err)
|
||||||
if e.errno != errno.ECONNRESET:
|
except socket.error as err:
|
||||||
|
if err.errno != errno.ECONNRESET:
|
||||||
# Connection reset is no problem, just retry
|
# Connection reset is no problem, just retry
|
||||||
raise
|
raise
|
||||||
|
raise RetryDownload(err)
|
||||||
|
|
||||||
# Retry
|
def download():
|
||||||
count += 1
|
data_len = ctx.data.info().get('Content-length', None)
|
||||||
if count <= retries:
|
|
||||||
self.report_retry(count, retries)
|
|
||||||
|
|
||||||
if count > retries:
|
|
||||||
self.report_error('giving up after %s retries' % retries)
|
|
||||||
return False
|
|
||||||
|
|
||||||
data_len = data.info().get('Content-length', None)
|
|
||||||
|
|
||||||
# Range HTTP header may be ignored/unsupported by a webserver
|
# Range HTTP header may be ignored/unsupported by a webserver
|
||||||
# (e.g. extractor/scivee.py, extractor/bambuser.py).
|
# (e.g. extractor/scivee.py, extractor/bambuser.py).
|
||||||
@ -142,7 +149,7 @@ class HttpFD(FileDownloader):
|
|||||||
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) + ctx.resume_len
|
||||||
min_data_len = self.params.get('min_filesize')
|
min_data_len = self.params.get('min_filesize')
|
||||||
max_data_len = self.params.get('max_filesize')
|
max_data_len = self.params.get('max_filesize')
|
||||||
if min_data_len is not None and data_len < min_data_len:
|
if min_data_len is not None and data_len < min_data_len:
|
||||||
@ -152,17 +159,34 @@ class HttpFD(FileDownloader):
|
|||||||
self.to_screen('\r[download] File is larger than max-filesize (%s bytes > %s bytes). Aborting.' % (data_len, max_data_len))
|
self.to_screen('\r[download] File is larger than max-filesize (%s bytes > %s bytes). Aborting.' % (data_len, max_data_len))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
byte_counter = 0 + resume_len
|
byte_counter = 0 + ctx.resume_len
|
||||||
block_size = self.params.get('buffersize', 1024)
|
block_size = self.params.get('buffersize', 1024)
|
||||||
start = time.time()
|
start = time.time()
|
||||||
|
|
||||||
# measure time over whole while-loop, so slow_down() and best_block_size() work together properly
|
# measure time over whole while-loop, so slow_down() and best_block_size() work together properly
|
||||||
now = None # needed for slow_down() in the first loop run
|
now = None # needed for slow_down() in the first loop run
|
||||||
before = start # start measuring
|
before = start # start measuring
|
||||||
while True:
|
|
||||||
|
|
||||||
|
def retry(e):
|
||||||
|
if ctx.tmpfilename != '-':
|
||||||
|
ctx.stream.close()
|
||||||
|
ctx.stream = None
|
||||||
|
ctx.resume_len = os.path.getsize(encodeFilename(ctx.tmpfilename))
|
||||||
|
raise RetryDownload(e)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
# Download and write
|
# Download and write
|
||||||
data_block = data.read(block_size if not is_test else min(block_size, data_len - byte_counter))
|
data_block = ctx.data.read(block_size if not is_test else min(block_size, data_len - byte_counter))
|
||||||
|
# socket.timeout is a subclass of socket.error but may not have
|
||||||
|
# errno set
|
||||||
|
except socket.timeout as e:
|
||||||
|
retry(e)
|
||||||
|
except socket.error as e:
|
||||||
|
if e.errno not in (errno.ECONNRESET, errno.ETIMEDOUT):
|
||||||
|
raise
|
||||||
|
retry(e)
|
||||||
|
|
||||||
byte_counter += len(data_block)
|
byte_counter += len(data_block)
|
||||||
|
|
||||||
# exit loop when download is finished
|
# exit loop when download is finished
|
||||||
@ -170,31 +194,32 @@ class HttpFD(FileDownloader):
|
|||||||
break
|
break
|
||||||
|
|
||||||
# Open destination file just in time
|
# Open destination file just in time
|
||||||
if stream is None:
|
if ctx.stream is None:
|
||||||
try:
|
try:
|
||||||
(stream, tmpfilename) = sanitize_open(tmpfilename, open_mode)
|
ctx.stream, ctx.tmpfilename = sanitize_open(
|
||||||
assert stream is not None
|
ctx.tmpfilename, ctx.open_mode)
|
||||||
filename = self.undo_temp_name(tmpfilename)
|
assert ctx.stream is not None
|
||||||
self.report_destination(filename)
|
ctx.filename = self.undo_temp_name(ctx.tmpfilename)
|
||||||
|
self.report_destination(ctx.filename)
|
||||||
except (OSError, IOError) as err:
|
except (OSError, IOError) as err:
|
||||||
self.report_error('unable to open for writing: %s' % str(err))
|
self.report_error('unable to open for writing: %s' % str(err))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if self.params.get('xattr_set_filesize', False) and data_len is not None:
|
if self.params.get('xattr_set_filesize', False) and data_len is not None:
|
||||||
try:
|
try:
|
||||||
write_xattr(tmpfilename, 'user.ytdl.filesize', str(data_len).encode('utf-8'))
|
write_xattr(ctx.tmpfilename, 'user.ytdl.filesize', str(data_len).encode('utf-8'))
|
||||||
except (XAttrUnavailableError, XAttrMetadataError) as err:
|
except (XAttrUnavailableError, XAttrMetadataError) as err:
|
||||||
self.report_error('unable to set filesize xattr: %s' % str(err))
|
self.report_error('unable to set filesize xattr: %s' % str(err))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
stream.write(data_block)
|
ctx.stream.write(data_block)
|
||||||
except (IOError, OSError) as err:
|
except (IOError, OSError) as err:
|
||||||
self.to_stderr('\n')
|
self.to_stderr('\n')
|
||||||
self.report_error('unable to write data: %s' % str(err))
|
self.report_error('unable to write data: %s' % str(err))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Apply rate limit
|
# Apply rate limit
|
||||||
self.slow_down(start, now, byte_counter - resume_len)
|
self.slow_down(start, now, byte_counter - ctx.resume_len)
|
||||||
|
|
||||||
# end measuring of one loop run
|
# end measuring of one loop run
|
||||||
now = time.time()
|
now = time.time()
|
||||||
@ -207,18 +232,18 @@ class HttpFD(FileDownloader):
|
|||||||
before = after
|
before = after
|
||||||
|
|
||||||
# Progress message
|
# Progress message
|
||||||
speed = self.calc_speed(start, now, byte_counter - resume_len)
|
speed = self.calc_speed(start, now, byte_counter - ctx.resume_len)
|
||||||
if data_len is None:
|
if data_len is None:
|
||||||
eta = None
|
eta = None
|
||||||
else:
|
else:
|
||||||
eta = self.calc_eta(start, time.time(), data_len - resume_len, byte_counter - resume_len)
|
eta = self.calc_eta(start, time.time(), data_len - ctx.resume_len, byte_counter - ctx.resume_len)
|
||||||
|
|
||||||
self._hook_progress({
|
self._hook_progress({
|
||||||
'status': 'downloading',
|
'status': 'downloading',
|
||||||
'downloaded_bytes': byte_counter,
|
'downloaded_bytes': byte_counter,
|
||||||
'total_bytes': data_len,
|
'total_bytes': data_len,
|
||||||
'tmpfilename': tmpfilename,
|
'tmpfilename': ctx.tmpfilename,
|
||||||
'filename': filename,
|
'filename': ctx.filename,
|
||||||
'eta': eta,
|
'eta': eta,
|
||||||
'speed': speed,
|
'speed': speed,
|
||||||
'elapsed': now - start,
|
'elapsed': now - start,
|
||||||
@ -227,27 +252,47 @@ class HttpFD(FileDownloader):
|
|||||||
if is_test and byte_counter == data_len:
|
if is_test and byte_counter == data_len:
|
||||||
break
|
break
|
||||||
|
|
||||||
if stream is None:
|
if ctx.stream is None:
|
||||||
self.to_stderr('\n')
|
self.to_stderr('\n')
|
||||||
self.report_error('Did not get any data blocks')
|
self.report_error('Did not get any data blocks')
|
||||||
return False
|
return False
|
||||||
if tmpfilename != '-':
|
if ctx.tmpfilename != '-':
|
||||||
stream.close()
|
ctx.stream.close()
|
||||||
|
|
||||||
if data_len is not None and byte_counter != data_len:
|
if data_len is not None and byte_counter != data_len:
|
||||||
raise ContentTooShortError(byte_counter, int(data_len))
|
err = ContentTooShortError(byte_counter, int(data_len))
|
||||||
self.try_rename(tmpfilename, filename)
|
if count <= retries:
|
||||||
|
retry(err)
|
||||||
|
raise err
|
||||||
|
|
||||||
|
self.try_rename(ctx.tmpfilename, ctx.filename)
|
||||||
|
|
||||||
# Update file modification time
|
# Update file modification time
|
||||||
if self.params.get('updatetime', True):
|
if self.params.get('updatetime', True):
|
||||||
info_dict['filetime'] = self.try_utime(filename, data.info().get('last-modified', None))
|
info_dict['filetime'] = self.try_utime(ctx.filename, ctx.data.info().get('last-modified', None))
|
||||||
|
|
||||||
self._hook_progress({
|
self._hook_progress({
|
||||||
'downloaded_bytes': byte_counter,
|
'downloaded_bytes': byte_counter,
|
||||||
'total_bytes': byte_counter,
|
'total_bytes': byte_counter,
|
||||||
'filename': filename,
|
'filename': ctx.filename,
|
||||||
'status': 'finished',
|
'status': 'finished',
|
||||||
'elapsed': time.time() - start,
|
'elapsed': time.time() - start,
|
||||||
})
|
})
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
while count <= retries:
|
||||||
|
try:
|
||||||
|
establish_connection()
|
||||||
|
download()
|
||||||
|
return True
|
||||||
|
except RetryDownload as e:
|
||||||
|
count += 1
|
||||||
|
if count <= retries:
|
||||||
|
self.report_retry(e.source_error, count, retries)
|
||||||
|
continue
|
||||||
|
except SucceedDownload:
|
||||||
|
return True
|
||||||
|
|
||||||
|
self.report_error('giving up after %s retries' % retries)
|
||||||
|
return False
|
||||||
|
@ -98,7 +98,7 @@ def write_piff_header(stream, params):
|
|||||||
|
|
||||||
if is_audio:
|
if is_audio:
|
||||||
smhd_payload = s88.pack(0) # balance
|
smhd_payload = s88.pack(0) # balance
|
||||||
smhd_payload = u16.pack(0) # reserved
|
smhd_payload += u16.pack(0) # reserved
|
||||||
media_header_box = full_box(b'smhd', 0, 0, smhd_payload) # Sound Media Header
|
media_header_box = full_box(b'smhd', 0, 0, smhd_payload) # Sound Media Header
|
||||||
else:
|
else:
|
||||||
vmhd_payload = u16.pack(0) # graphics mode
|
vmhd_payload = u16.pack(0) # graphics mode
|
||||||
@ -126,7 +126,6 @@ def write_piff_header(stream, params):
|
|||||||
if fourcc == 'AACL':
|
if fourcc == 'AACL':
|
||||||
sample_entry_box = box(b'mp4a', sample_entry_payload)
|
sample_entry_box = box(b'mp4a', sample_entry_payload)
|
||||||
else:
|
else:
|
||||||
sample_entry_payload = sample_entry_payload
|
|
||||||
sample_entry_payload += u16.pack(0) # pre defined
|
sample_entry_payload += u16.pack(0) # pre defined
|
||||||
sample_entry_payload += u16.pack(0) # reserved
|
sample_entry_payload += u16.pack(0) # reserved
|
||||||
sample_entry_payload += u32.pack(0) * 3 # pre defined
|
sample_entry_payload += u32.pack(0) * 3 # pre defined
|
||||||
|
@ -3,11 +3,13 @@ from __future__ import unicode_literals
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from ..compat import compat_str
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
js_to_json,
|
js_to_json,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
parse_iso8601,
|
parse_iso8601,
|
||||||
|
try_get,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -124,7 +126,20 @@ class ABCIViewIE(InfoExtractor):
|
|||||||
title = video_params.get('title') or video_params['seriesTitle']
|
title = video_params.get('title') or video_params['seriesTitle']
|
||||||
stream = next(s for s in video_params['playlist'] if s.get('type') == 'program')
|
stream = next(s for s in video_params['playlist'] if s.get('type') == 'program')
|
||||||
|
|
||||||
formats = self._extract_akamai_formats(stream['hds-unmetered'], video_id)
|
format_urls = [
|
||||||
|
try_get(stream, lambda x: x['hds-unmetered'], compat_str)]
|
||||||
|
|
||||||
|
# May have higher quality video
|
||||||
|
sd_url = try_get(
|
||||||
|
stream, lambda x: x['streams']['hds']['sd'], compat_str)
|
||||||
|
if sd_url:
|
||||||
|
format_urls.append(sd_url.replace('metered', 'um'))
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
for format_url in format_urls:
|
||||||
|
if format_url:
|
||||||
|
formats.extend(
|
||||||
|
self._extract_akamai_formats(format_url, video_id))
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
subtitles = {}
|
subtitles = {}
|
||||||
|
@ -7,6 +7,7 @@ import time
|
|||||||
|
|
||||||
from .amp import AMPIE
|
from .amp import AMPIE
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from .youtube import YoutubeIE
|
||||||
from ..compat import compat_urlparse
|
from ..compat import compat_urlparse
|
||||||
|
|
||||||
|
|
||||||
@ -108,9 +109,7 @@ class AbcNewsIE(InfoExtractor):
|
|||||||
r'window\.abcnvideo\.url\s*=\s*"([^"]+)"', webpage, 'video URL')
|
r'window\.abcnvideo\.url\s*=\s*"([^"]+)"', webpage, 'video URL')
|
||||||
full_video_url = compat_urlparse.urljoin(url, video_url)
|
full_video_url = compat_urlparse.urljoin(url, video_url)
|
||||||
|
|
||||||
youtube_url = self._html_search_regex(
|
youtube_url = YoutubeIE._extract_url(webpage)
|
||||||
r'<iframe[^>]+src="(https://www\.youtube\.com/embed/[^"]+)"',
|
|
||||||
webpage, 'YouTube URL', default=None)
|
|
||||||
|
|
||||||
timestamp = None
|
timestamp = None
|
||||||
date_str = self._html_search_regex(
|
date_str = self._html_search_regex(
|
||||||
@ -140,7 +139,7 @@ class AbcNewsIE(InfoExtractor):
|
|||||||
}
|
}
|
||||||
|
|
||||||
if youtube_url:
|
if youtube_url:
|
||||||
entries = [entry, self.url_result(youtube_url, 'Youtube')]
|
entries = [entry, self.url_result(youtube_url, ie=YoutubeIE.ie_key())]
|
||||||
return self.playlist_result(entries)
|
return self.playlist_result(entries)
|
||||||
|
|
||||||
return entry
|
return entry
|
||||||
|
@ -107,11 +107,13 @@ class ADNIE(InfoExtractor):
|
|||||||
metas = options.get('metas') or {}
|
metas = options.get('metas') or {}
|
||||||
title = metas.get('title') or video_info['title']
|
title = metas.get('title') or video_info['title']
|
||||||
links = player_config.get('links') or {}
|
links = player_config.get('links') or {}
|
||||||
|
error = None
|
||||||
if not links:
|
if not links:
|
||||||
links_url = player_config['linksurl']
|
links_url = player_config['linksurl']
|
||||||
links_data = self._download_json(urljoin(
|
links_data = self._download_json(urljoin(
|
||||||
self._BASE_URL, links_url), video_id)
|
self._BASE_URL, links_url), video_id)
|
||||||
links = links_data.get('links') or {}
|
links = links_data.get('links') or {}
|
||||||
|
error = links_data.get('error')
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
for format_id, qualities in links.items():
|
for format_id, qualities in links.items():
|
||||||
@ -130,6 +132,7 @@ class ADNIE(InfoExtractor):
|
|||||||
for f in m3u8_formats:
|
for f in m3u8_formats:
|
||||||
f['language'] = 'fr'
|
f['language'] = 'fr'
|
||||||
formats.extend(m3u8_formats)
|
formats.extend(m3u8_formats)
|
||||||
|
if not error:
|
||||||
error = options.get('error')
|
error = options.get('error')
|
||||||
if not formats and error:
|
if not formats and error:
|
||||||
raise ExtractorError('%s said: %s' % (self.IE_NAME, error), expected=True)
|
raise ExtractorError('%s said: %s' % (self.IE_NAME, error), expected=True)
|
||||||
|
@ -15,6 +15,7 @@ from ..utils import (
|
|||||||
urlencode_postdata,
|
urlencode_postdata,
|
||||||
unified_timestamp,
|
unified_timestamp,
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
|
NO_DEFAULT,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -24,6 +25,11 @@ MSO_INFO = {
|
|||||||
'username_field': 'username',
|
'username_field': 'username',
|
||||||
'password_field': 'password',
|
'password_field': 'password',
|
||||||
},
|
},
|
||||||
|
'ATTOTT': {
|
||||||
|
'name': 'DIRECTV NOW',
|
||||||
|
'username_field': 'email',
|
||||||
|
'password_field': 'loginpassword',
|
||||||
|
},
|
||||||
'Rogers': {
|
'Rogers': {
|
||||||
'name': 'Rogers',
|
'name': 'Rogers',
|
||||||
'username_field': 'UserName',
|
'username_field': 'UserName',
|
||||||
@ -1316,6 +1322,8 @@ class AdobePassIE(InfoExtractor):
|
|||||||
_USER_AGENT = 'Mozilla/5.0 (X11; Linux i686; rv:47.0) Gecko/20100101 Firefox/47.0'
|
_USER_AGENT = 'Mozilla/5.0 (X11; Linux i686; rv:47.0) Gecko/20100101 Firefox/47.0'
|
||||||
_MVPD_CACHE = 'ap-mvpd'
|
_MVPD_CACHE = 'ap-mvpd'
|
||||||
|
|
||||||
|
_DOWNLOADING_LOGIN_PAGE = 'Downloading Provider Login Page'
|
||||||
|
|
||||||
def _download_webpage_handle(self, *args, **kwargs):
|
def _download_webpage_handle(self, *args, **kwargs):
|
||||||
headers = kwargs.get('headers', {})
|
headers = kwargs.get('headers', {})
|
||||||
headers.update(self.geo_verification_headers())
|
headers.update(self.geo_verification_headers())
|
||||||
@ -1365,6 +1373,21 @@ class AdobePassIE(InfoExtractor):
|
|||||||
'Use --ap-mso to specify Adobe Pass Multiple-system operator Identifier '
|
'Use --ap-mso to specify Adobe Pass Multiple-system operator Identifier '
|
||||||
'and --ap-username and --ap-password or --netrc to provide account credentials.', expected=True)
|
'and --ap-username and --ap-password or --netrc to provide account credentials.', expected=True)
|
||||||
|
|
||||||
|
def extract_redirect_url(html, url=None, fatal=False):
|
||||||
|
# TODO: eliminate code duplication with generic extractor and move
|
||||||
|
# redirection code into _download_webpage_handle
|
||||||
|
REDIRECT_REGEX = r'[0-9]{,2};\s*(?:URL|url)=\'?([^\'"]+)'
|
||||||
|
redirect_url = self._search_regex(
|
||||||
|
r'(?i)<meta\s+(?=(?:[a-z-]+="[^"]+"\s+)*http-equiv="refresh")'
|
||||||
|
r'(?:[a-z-]+="[^"]+"\s+)*?content="%s' % REDIRECT_REGEX,
|
||||||
|
html, 'meta refresh redirect',
|
||||||
|
default=NO_DEFAULT if fatal else None, fatal=fatal)
|
||||||
|
if not redirect_url:
|
||||||
|
return None
|
||||||
|
if url:
|
||||||
|
redirect_url = compat_urlparse.urljoin(url, unescapeHTML(redirect_url))
|
||||||
|
return redirect_url
|
||||||
|
|
||||||
mvpd_headers = {
|
mvpd_headers = {
|
||||||
'ap_42': 'anonymous',
|
'ap_42': 'anonymous',
|
||||||
'ap_11': 'Linux i686',
|
'ap_11': 'Linux i686',
|
||||||
@ -1414,16 +1437,15 @@ class AdobePassIE(InfoExtractor):
|
|||||||
if '<form name="signin"' in provider_redirect_page:
|
if '<form name="signin"' in provider_redirect_page:
|
||||||
provider_login_page_res = provider_redirect_page_res
|
provider_login_page_res = provider_redirect_page_res
|
||||||
elif 'http-equiv="refresh"' in provider_redirect_page:
|
elif 'http-equiv="refresh"' in provider_redirect_page:
|
||||||
oauth_redirect_url = self._html_search_regex(
|
oauth_redirect_url = extract_redirect_url(
|
||||||
r'content="0;\s*url=([^\'"]+)',
|
provider_redirect_page, fatal=True)
|
||||||
provider_redirect_page, 'meta refresh redirect')
|
|
||||||
provider_login_page_res = self._download_webpage_handle(
|
provider_login_page_res = self._download_webpage_handle(
|
||||||
oauth_redirect_url, video_id,
|
oauth_redirect_url, video_id,
|
||||||
'Downloading Provider Login Page')
|
self._DOWNLOADING_LOGIN_PAGE)
|
||||||
else:
|
else:
|
||||||
provider_login_page_res = post_form(
|
provider_login_page_res = post_form(
|
||||||
provider_redirect_page_res,
|
provider_redirect_page_res,
|
||||||
'Downloading Provider Login Page')
|
self._DOWNLOADING_LOGIN_PAGE)
|
||||||
|
|
||||||
mvpd_confirm_page_res = post_form(
|
mvpd_confirm_page_res = post_form(
|
||||||
provider_login_page_res, 'Logging in', {
|
provider_login_page_res, 'Logging in', {
|
||||||
@ -1470,8 +1492,17 @@ class AdobePassIE(InfoExtractor):
|
|||||||
'Content-Type': 'application/x-www-form-urlencoded'
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
|
# Some providers (e.g. DIRECTV NOW) have another meta refresh
|
||||||
|
# based redirect that should be followed.
|
||||||
|
provider_redirect_page, urlh = provider_redirect_page_res
|
||||||
|
provider_refresh_redirect_url = extract_redirect_url(
|
||||||
|
provider_redirect_page, url=urlh.geturl())
|
||||||
|
if provider_refresh_redirect_url:
|
||||||
|
provider_redirect_page_res = self._download_webpage_handle(
|
||||||
|
provider_refresh_redirect_url, video_id,
|
||||||
|
'Downloading Provider Redirect Page (meta refresh)')
|
||||||
provider_login_page_res = post_form(
|
provider_login_page_res = post_form(
|
||||||
provider_redirect_page_res, 'Downloading Provider Login Page')
|
provider_redirect_page_res, self._DOWNLOADING_LOGIN_PAGE)
|
||||||
mvpd_confirm_page_res = post_form(provider_login_page_res, 'Logging in', {
|
mvpd_confirm_page_res = post_form(provider_login_page_res, 'Logging in', {
|
||||||
mso_info.get('username_field', 'username'): username,
|
mso_info.get('username_field', 'username'): username,
|
||||||
mso_info.get('password_field', 'password'): password,
|
mso_info.get('password_field', 'password'): password,
|
||||||
|
@ -131,7 +131,7 @@ class AENetworksIE(AENetworksBaseIE):
|
|||||||
r'data-media-url=(["\'])(?P<url>(?:(?!\1).)+?)\1'],
|
r'data-media-url=(["\'])(?P<url>(?:(?!\1).)+?)\1'],
|
||||||
webpage, 'video url', group='url')
|
webpage, 'video url', group='url')
|
||||||
theplatform_metadata = self._download_theplatform_metadata(self._search_regex(
|
theplatform_metadata = self._download_theplatform_metadata(self._search_regex(
|
||||||
r'https?://link.theplatform.com/s/([^?]+)', media_url, 'theplatform_path'), video_id)
|
r'https?://link\.theplatform\.com/s/([^?]+)', media_url, 'theplatform_path'), video_id)
|
||||||
info = self._parse_theplatform_metadata(theplatform_metadata)
|
info = self._parse_theplatform_metadata(theplatform_metadata)
|
||||||
if theplatform_metadata.get('AETN$isBehindWall'):
|
if theplatform_metadata.get('AETN$isBehindWall'):
|
||||||
requestor_id = self._DOMAIN_TO_REQUESTOR_ID[domain]
|
requestor_id = self._DOMAIN_TO_REQUESTOR_ID[domain]
|
||||||
|
@ -138,6 +138,23 @@ class AfreecaTVIE(InfoExtractor):
|
|||||||
'params': {
|
'params': {
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
},
|
},
|
||||||
|
}, {
|
||||||
|
# adult video
|
||||||
|
'url': 'http://vod.afreecatv.com/PLAYER/STATION/26542731',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '20171001_F1AE1711_196617479_1',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': '[생]서아 초심 찾기 방송 (part 1)',
|
||||||
|
'thumbnail': 're:^https?://(?:video|st)img.afreecatv.com/.*$',
|
||||||
|
'uploader': 'BJ서아',
|
||||||
|
'uploader_id': 'bjdyrksu',
|
||||||
|
'upload_date': '20171001',
|
||||||
|
'duration': 3600,
|
||||||
|
'age_limit': 18,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.afreecatv.com/player/Player.swf?szType=szBjId=djleegoon&nStationNo=11273158&nBbsNo=13161095&nTitleNo=36327652',
|
'url': 'http://www.afreecatv.com/player/Player.swf?szType=szBjId=djleegoon&nStationNo=11273158&nBbsNo=13161095&nTitleNo=36327652',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
@ -160,7 +177,15 @@ class AfreecaTVIE(InfoExtractor):
|
|||||||
|
|
||||||
video_xml = self._download_xml(
|
video_xml = self._download_xml(
|
||||||
'http://afbbs.afreecatv.com:8080/api/video/get_video_info.php',
|
'http://afbbs.afreecatv.com:8080/api/video/get_video_info.php',
|
||||||
video_id, query={'nTitleNo': video_id})
|
video_id, query={
|
||||||
|
'nTitleNo': video_id,
|
||||||
|
'partialView': 'SKIP_ADULT',
|
||||||
|
})
|
||||||
|
|
||||||
|
flag = xpath_text(video_xml, './track/flag', 'flag', default=None)
|
||||||
|
if flag and flag != 'SUCCEED':
|
||||||
|
raise ExtractorError(
|
||||||
|
'%s said: %s' % (self.IE_NAME, flag), expected=True)
|
||||||
|
|
||||||
video_element = video_xml.findall(compat_xpath('./track/video'))[1]
|
video_element = video_xml.findall(compat_xpath('./track/video'))[1]
|
||||||
if video_element is None or video_element.text is None:
|
if video_element is None or video_element.text is None:
|
||||||
@ -246,107 +271,3 @@ class AfreecaTVIE(InfoExtractor):
|
|||||||
})
|
})
|
||||||
|
|
||||||
return info
|
return info
|
||||||
|
|
||||||
|
|
||||||
class AfreecaTVGlobalIE(AfreecaTVIE):
|
|
||||||
IE_NAME = 'afreecatv:global'
|
|
||||||
_VALID_URL = r'https?://(?:www\.)?afreeca\.tv/(?P<channel_id>\d+)(?:/v/(?P<video_id>\d+))?'
|
|
||||||
_TESTS = [{
|
|
||||||
'url': 'http://afreeca.tv/36853014/v/58301',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '58301',
|
|
||||||
'title': 'tryhard top100',
|
|
||||||
'uploader_id': '36853014',
|
|
||||||
'uploader': 'makgi Hearthstone Live!',
|
|
||||||
},
|
|
||||||
'playlist_count': 3,
|
|
||||||
}]
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
|
||||||
channel_id, video_id = re.match(self._VALID_URL, url).groups()
|
|
||||||
video_type = 'video' if video_id else 'live'
|
|
||||||
query = {
|
|
||||||
'pt': 'view',
|
|
||||||
'bid': channel_id,
|
|
||||||
}
|
|
||||||
if video_id:
|
|
||||||
query['vno'] = video_id
|
|
||||||
video_data = self._download_json(
|
|
||||||
'http://api.afreeca.tv/%s/view_%s.php' % (video_type, video_type),
|
|
||||||
video_id or channel_id, query=query)['channel']
|
|
||||||
|
|
||||||
if video_data.get('result') != 1:
|
|
||||||
raise ExtractorError('%s said: %s' % (self.IE_NAME, video_data['remsg']))
|
|
||||||
|
|
||||||
title = video_data['title']
|
|
||||||
|
|
||||||
info = {
|
|
||||||
'thumbnail': video_data.get('thumb'),
|
|
||||||
'view_count': int_or_none(video_data.get('vcnt')),
|
|
||||||
'age_limit': int_or_none(video_data.get('grade')),
|
|
||||||
'uploader_id': channel_id,
|
|
||||||
'uploader': video_data.get('cname'),
|
|
||||||
}
|
|
||||||
|
|
||||||
if video_id:
|
|
||||||
entries = []
|
|
||||||
for i, f in enumerate(video_data.get('flist', [])):
|
|
||||||
video_key = self.parse_video_key(f.get('key', ''))
|
|
||||||
f_url = f.get('file')
|
|
||||||
if not video_key or not f_url:
|
|
||||||
continue
|
|
||||||
entries.append({
|
|
||||||
'id': '%s_%s' % (video_id, video_key.get('part', i + 1)),
|
|
||||||
'title': title,
|
|
||||||
'upload_date': video_key.get('upload_date'),
|
|
||||||
'duration': int_or_none(f.get('length')),
|
|
||||||
'url': f_url,
|
|
||||||
'protocol': 'm3u8_native',
|
|
||||||
'ext': 'mp4',
|
|
||||||
})
|
|
||||||
|
|
||||||
info.update({
|
|
||||||
'id': video_id,
|
|
||||||
'title': title,
|
|
||||||
'duration': int_or_none(video_data.get('length')),
|
|
||||||
})
|
|
||||||
if len(entries) > 1:
|
|
||||||
info['_type'] = 'multi_video'
|
|
||||||
info['entries'] = entries
|
|
||||||
elif len(entries) == 1:
|
|
||||||
i = entries[0].copy()
|
|
||||||
i.update(info)
|
|
||||||
info = i
|
|
||||||
else:
|
|
||||||
formats = []
|
|
||||||
for s in video_data.get('strm', []):
|
|
||||||
s_url = s.get('purl')
|
|
||||||
if not s_url:
|
|
||||||
continue
|
|
||||||
stype = s.get('stype')
|
|
||||||
if stype == 'HLS':
|
|
||||||
formats.extend(self._extract_m3u8_formats(
|
|
||||||
s_url, channel_id, 'mp4', m3u8_id=stype, fatal=False))
|
|
||||||
elif stype == 'RTMP':
|
|
||||||
format_id = [stype]
|
|
||||||
label = s.get('label')
|
|
||||||
if label:
|
|
||||||
format_id.append(label)
|
|
||||||
formats.append({
|
|
||||||
'format_id': '-'.join(format_id),
|
|
||||||
'url': s_url,
|
|
||||||
'tbr': int_or_none(s.get('bps')),
|
|
||||||
'height': int_or_none(s.get('brt')),
|
|
||||||
'ext': 'flv',
|
|
||||||
'rtmp_live': True,
|
|
||||||
})
|
|
||||||
self._sort_formats(formats)
|
|
||||||
|
|
||||||
info.update({
|
|
||||||
'id': channel_id,
|
|
||||||
'title': self._live_title(title),
|
|
||||||
'is_live': True,
|
|
||||||
'formats': formats,
|
|
||||||
})
|
|
||||||
|
|
||||||
return info
|
|
||||||
|
53
youtube_dl/extractor/aliexpress.py
Normal file
53
youtube_dl/extractor/aliexpress.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..compat import compat_str
|
||||||
|
from ..utils import (
|
||||||
|
float_or_none,
|
||||||
|
try_get,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AliExpressLiveIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://live\.aliexpress\.com/live/(?P<id>\d+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'https://live.aliexpress.com/live/2800002704436634',
|
||||||
|
'md5': 'e729e25d47c5e557f2630eaf99b740a5',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '2800002704436634',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'CASIMA7.22',
|
||||||
|
'thumbnail': r're:http://.*\.jpg',
|
||||||
|
'uploader': 'CASIMA Official Store',
|
||||||
|
'timestamp': 1500717600,
|
||||||
|
'upload_date': '20170722',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
data = self._parse_json(
|
||||||
|
self._search_regex(
|
||||||
|
r'(?s)runParams\s*=\s*({.+?})\s*;?\s*var',
|
||||||
|
webpage, 'runParams'),
|
||||||
|
video_id)
|
||||||
|
|
||||||
|
title = data['title']
|
||||||
|
|
||||||
|
formats = self._extract_m3u8_formats(
|
||||||
|
data['replyStreamUrl'], video_id, 'mp4',
|
||||||
|
entry_protocol='m3u8_native', m3u8_id='hls')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'thumbnail': data.get('coverUrl'),
|
||||||
|
'uploader': try_get(
|
||||||
|
data, lambda x: x['followBar']['name'], compat_str),
|
||||||
|
'timestamp': float_or_none(data.get('startTimeLong'), scale=1000),
|
||||||
|
'formats': formats,
|
||||||
|
}
|
@ -3,9 +3,10 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from .theplatform import ThePlatformIE
|
from .theplatform import ThePlatformIE
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
update_url_query,
|
|
||||||
parse_age_limit,
|
|
||||||
int_or_none,
|
int_or_none,
|
||||||
|
parse_age_limit,
|
||||||
|
try_get,
|
||||||
|
update_url_query,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -68,7 +69,8 @@ class AMCNetworksIE(ThePlatformIE):
|
|||||||
info = self._parse_theplatform_metadata(theplatform_metadata)
|
info = self._parse_theplatform_metadata(theplatform_metadata)
|
||||||
video_id = theplatform_metadata['pid']
|
video_id = theplatform_metadata['pid']
|
||||||
title = theplatform_metadata['title']
|
title = theplatform_metadata['title']
|
||||||
rating = theplatform_metadata['ratings'][0]['rating']
|
rating = try_get(
|
||||||
|
theplatform_metadata, lambda x: x['ratings'][0]['rating'])
|
||||||
auth_required = self._search_regex(
|
auth_required = self._search_regex(
|
||||||
r'window\.authRequired\s*=\s*(true|false);',
|
r'window\.authRequired\s*=\s*(true|false);',
|
||||||
webpage, 'auth required')
|
webpage, 'auth required')
|
||||||
|
85
youtube_dl/extractor/americastestkitchen.py
Executable file
85
youtube_dl/extractor/americastestkitchen.py
Executable file
@ -0,0 +1,85 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
clean_html,
|
||||||
|
int_or_none,
|
||||||
|
try_get,
|
||||||
|
unified_strdate,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AmericasTestKitchenIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?americastestkitchen\.com/(?:episode|videos)/(?P<id>\d+)'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://www.americastestkitchen.com/episode/548-summer-dinner-party',
|
||||||
|
'md5': 'b861c3e365ac38ad319cfd509c30577f',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '1_5g5zua6e',
|
||||||
|
'title': 'Summer Dinner Party',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'description': 'md5:858d986e73a4826979b6a5d9f8f6a1ec',
|
||||||
|
'thumbnail': r're:^https?://.*\.jpg',
|
||||||
|
'timestamp': 1497285541,
|
||||||
|
'upload_date': '20170612',
|
||||||
|
'uploader_id': 'roger.metcalf@americastestkitchen.com',
|
||||||
|
'release_date': '20170617',
|
||||||
|
'series': "America's Test Kitchen",
|
||||||
|
'season_number': 17,
|
||||||
|
'episode': 'Summer Dinner Party',
|
||||||
|
'episode_number': 24,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.americastestkitchen.com/videos/3420-pan-seared-salmon',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
partner_id = self._search_regex(
|
||||||
|
r'src=["\'](?:https?:)?//(?:[^/]+\.)kaltura\.com/(?:[^/]+/)*(?:p|partner_id)/(\d+)',
|
||||||
|
webpage, 'kaltura partner id')
|
||||||
|
|
||||||
|
video_data = self._parse_json(
|
||||||
|
self._search_regex(
|
||||||
|
r'window\.__INITIAL_STATE__\s*=\s*({.+?})\s*;\s*</script>',
|
||||||
|
webpage, 'initial context'),
|
||||||
|
video_id)
|
||||||
|
|
||||||
|
ep_data = try_get(
|
||||||
|
video_data,
|
||||||
|
(lambda x: x['episodeDetail']['content']['data'],
|
||||||
|
lambda x: x['videoDetail']['content']['data']), dict)
|
||||||
|
ep_meta = ep_data.get('full_video', {})
|
||||||
|
external_id = ep_data.get('external_id') or ep_meta['external_id']
|
||||||
|
|
||||||
|
title = ep_data.get('title') or ep_meta.get('title')
|
||||||
|
description = clean_html(ep_meta.get('episode_description') or ep_data.get(
|
||||||
|
'description') or ep_meta.get('description'))
|
||||||
|
thumbnail = try_get(ep_meta, lambda x: x['photo']['image_url'])
|
||||||
|
release_date = unified_strdate(ep_data.get('aired_at'))
|
||||||
|
|
||||||
|
season_number = int_or_none(ep_meta.get('season_number'))
|
||||||
|
episode = ep_meta.get('title')
|
||||||
|
episode_number = int_or_none(ep_meta.get('episode_number'))
|
||||||
|
|
||||||
|
return {
|
||||||
|
'_type': 'url_transparent',
|
||||||
|
'url': 'kaltura:%s:%s' % (partner_id, external_id),
|
||||||
|
'ie_key': 'Kaltura',
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'release_date': release_date,
|
||||||
|
'series': "America's Test Kitchen",
|
||||||
|
'season_number': season_number,
|
||||||
|
'episode': episode,
|
||||||
|
'episode_number': episode_number,
|
||||||
|
}
|
@ -3,16 +3,13 @@ from __future__ import unicode_literals
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import compat_str
|
||||||
compat_urlparse,
|
|
||||||
compat_str,
|
|
||||||
)
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
determine_ext,
|
determine_ext,
|
||||||
extract_attributes,
|
extract_attributes,
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
sanitized_Request,
|
|
||||||
urlencode_postdata,
|
urlencode_postdata,
|
||||||
|
urljoin,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -21,6 +18,8 @@ class AnimeOnDemandIE(InfoExtractor):
|
|||||||
_LOGIN_URL = 'https://www.anime-on-demand.de/users/sign_in'
|
_LOGIN_URL = 'https://www.anime-on-demand.de/users/sign_in'
|
||||||
_APPLY_HTML5_URL = 'https://www.anime-on-demand.de/html5apply'
|
_APPLY_HTML5_URL = 'https://www.anime-on-demand.de/html5apply'
|
||||||
_NETRC_MACHINE = 'animeondemand'
|
_NETRC_MACHINE = 'animeondemand'
|
||||||
|
# German-speaking countries of Europe
|
||||||
|
_GEO_COUNTRIES = ['AT', 'CH', 'DE', 'LI', 'LU']
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
# jap, OmU
|
# jap, OmU
|
||||||
'url': 'https://www.anime-on-demand.de/anime/161',
|
'url': 'https://www.anime-on-demand.de/anime/161',
|
||||||
@ -46,6 +45,10 @@ class AnimeOnDemandIE(InfoExtractor):
|
|||||||
# Full length film, non-series, ger/jap, Dub/OmU, account required
|
# Full length film, non-series, ger/jap, Dub/OmU, account required
|
||||||
'url': 'https://www.anime-on-demand.de/anime/185',
|
'url': 'https://www.anime-on-demand.de/anime/185',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
# Flash videos
|
||||||
|
'url': 'https://www.anime-on-demand.de/anime/12',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _login(self):
|
def _login(self):
|
||||||
@ -72,14 +75,13 @@ class AnimeOnDemandIE(InfoExtractor):
|
|||||||
'post url', default=self._LOGIN_URL, group='url')
|
'post url', default=self._LOGIN_URL, group='url')
|
||||||
|
|
||||||
if not post_url.startswith('http'):
|
if not post_url.startswith('http'):
|
||||||
post_url = compat_urlparse.urljoin(self._LOGIN_URL, post_url)
|
post_url = urljoin(self._LOGIN_URL, post_url)
|
||||||
|
|
||||||
request = sanitized_Request(
|
|
||||||
post_url, urlencode_postdata(login_form))
|
|
||||||
request.add_header('Referer', self._LOGIN_URL)
|
|
||||||
|
|
||||||
response = self._download_webpage(
|
response = self._download_webpage(
|
||||||
request, None, 'Logging in as %s' % username)
|
post_url, None, 'Logging in',
|
||||||
|
data=urlencode_postdata(login_form), headers={
|
||||||
|
'Referer': self._LOGIN_URL,
|
||||||
|
})
|
||||||
|
|
||||||
if all(p not in response for p in ('>Logout<', 'href="/users/sign_out"')):
|
if all(p not in response for p in ('>Logout<', 'href="/users/sign_out"')):
|
||||||
error = self._search_regex(
|
error = self._search_regex(
|
||||||
@ -120,10 +122,11 @@ class AnimeOnDemandIE(InfoExtractor):
|
|||||||
formats = []
|
formats = []
|
||||||
|
|
||||||
for input_ in re.findall(
|
for input_ in re.findall(
|
||||||
r'<input[^>]+class=["\'].*?streamstarter_html5[^>]+>', html):
|
r'<input[^>]+class=["\'].*?streamstarter[^>]+>', html):
|
||||||
attributes = extract_attributes(input_)
|
attributes = extract_attributes(input_)
|
||||||
|
title = attributes.get('data-dialog-header')
|
||||||
playlist_urls = []
|
playlist_urls = []
|
||||||
for playlist_key in ('data-playlist', 'data-otherplaylist'):
|
for playlist_key in ('data-playlist', 'data-otherplaylist', 'data-stream'):
|
||||||
playlist_url = attributes.get(playlist_key)
|
playlist_url = attributes.get(playlist_key)
|
||||||
if isinstance(playlist_url, compat_str) and re.match(
|
if isinstance(playlist_url, compat_str) and re.match(
|
||||||
r'/?[\da-zA-Z]+', playlist_url):
|
r'/?[\da-zA-Z]+', playlist_url):
|
||||||
@ -147,19 +150,38 @@ class AnimeOnDemandIE(InfoExtractor):
|
|||||||
format_id_list.append(compat_str(num))
|
format_id_list.append(compat_str(num))
|
||||||
format_id = '-'.join(format_id_list)
|
format_id = '-'.join(format_id_list)
|
||||||
format_note = ', '.join(filter(None, (kind, lang_note)))
|
format_note = ', '.join(filter(None, (kind, lang_note)))
|
||||||
request = sanitized_Request(
|
item_id_list = []
|
||||||
compat_urlparse.urljoin(url, playlist_url),
|
if format_id:
|
||||||
|
item_id_list.append(format_id)
|
||||||
|
item_id_list.append('videomaterial')
|
||||||
|
playlist = self._download_json(
|
||||||
|
urljoin(url, playlist_url), video_id,
|
||||||
|
'Downloading %s JSON' % ' '.join(item_id_list),
|
||||||
headers={
|
headers={
|
||||||
'X-Requested-With': 'XMLHttpRequest',
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
'X-CSRF-Token': csrf_token,
|
'X-CSRF-Token': csrf_token,
|
||||||
'Referer': url,
|
'Referer': url,
|
||||||
'Accept': 'application/json, text/javascript, */*; q=0.01',
|
'Accept': 'application/json, text/javascript, */*; q=0.01',
|
||||||
})
|
}, fatal=False)
|
||||||
playlist = self._download_json(
|
|
||||||
request, video_id, 'Downloading %s playlist JSON' % format_id,
|
|
||||||
fatal=False)
|
|
||||||
if not playlist:
|
if not playlist:
|
||||||
continue
|
continue
|
||||||
|
stream_url = playlist.get('streamurl')
|
||||||
|
if stream_url:
|
||||||
|
rtmp = re.search(
|
||||||
|
r'^(?P<url>rtmpe?://(?P<host>[^/]+)/(?P<app>.+/))(?P<playpath>mp[34]:.+)',
|
||||||
|
stream_url)
|
||||||
|
if rtmp:
|
||||||
|
formats.append({
|
||||||
|
'url': rtmp.group('url'),
|
||||||
|
'app': rtmp.group('app'),
|
||||||
|
'play_path': rtmp.group('playpath'),
|
||||||
|
'page_url': url,
|
||||||
|
'player_url': 'https://www.anime-on-demand.de/assets/jwplayer.flash-55abfb34080700304d49125ce9ffb4a6.swf',
|
||||||
|
'rtmp_real_time': True,
|
||||||
|
'format_id': 'rtmp',
|
||||||
|
'ext': 'flv',
|
||||||
|
})
|
||||||
|
continue
|
||||||
start_video = playlist.get('startvideo', 0)
|
start_video = playlist.get('startvideo', 0)
|
||||||
playlist = playlist.get('playlist')
|
playlist = playlist.get('playlist')
|
||||||
if not playlist or not isinstance(playlist, list):
|
if not playlist or not isinstance(playlist, list):
|
||||||
@ -222,7 +244,7 @@ class AnimeOnDemandIE(InfoExtractor):
|
|||||||
f.update({
|
f.update({
|
||||||
'id': '%s-%s' % (f['id'], m.group('kind').lower()),
|
'id': '%s-%s' % (f['id'], m.group('kind').lower()),
|
||||||
'title': m.group('title'),
|
'title': m.group('title'),
|
||||||
'url': compat_urlparse.urljoin(url, m.group('href')),
|
'url': urljoin(url, m.group('href')),
|
||||||
})
|
})
|
||||||
entries.append(f)
|
entries.append(f)
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ from ..utils import (
|
|||||||
int_or_none,
|
int_or_none,
|
||||||
strip_jsonp,
|
strip_jsonp,
|
||||||
unescapeHTML,
|
unescapeHTML,
|
||||||
|
unsmuggle_url,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -197,12 +198,16 @@ class AnvatoIE(InfoExtractor):
|
|||||||
'tbr': tbr if tbr != 0 else None,
|
'tbr': tbr if tbr != 0 else None,
|
||||||
}
|
}
|
||||||
|
|
||||||
if ext == 'm3u8' or media_format in ('m3u8', 'm3u8-variant'):
|
if media_format == 'm3u8' and tbr is not None:
|
||||||
if tbr is not None:
|
|
||||||
a_format.update({
|
a_format.update({
|
||||||
'format_id': '-'.join(filter(None, ['hls', compat_str(tbr)])),
|
'format_id': '-'.join(filter(None, ['hls', compat_str(tbr)])),
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
})
|
})
|
||||||
|
elif media_format == 'm3u8-variant' or ext == 'm3u8':
|
||||||
|
formats.extend(self._extract_m3u8_formats(
|
||||||
|
video_url, video_id, 'mp4', entry_protocol='m3u8_native',
|
||||||
|
m3u8_id='hls', fatal=False))
|
||||||
|
continue
|
||||||
elif ext == 'mp3' or media_format == 'mp3':
|
elif ext == 'mp3' or media_format == 'mp3':
|
||||||
a_format['vcodec'] = 'none'
|
a_format['vcodec'] = 'none'
|
||||||
else:
|
else:
|
||||||
@ -271,6 +276,9 @@ class AnvatoIE(InfoExtractor):
|
|||||||
anvplayer_data['accessKey'], anvplayer_data['video'])
|
anvplayer_data['accessKey'], anvplayer_data['video'])
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
|
url, smuggled_data = unsmuggle_url(url, {})
|
||||||
|
self._initialize_geo_bypass(smuggled_data.get('geo_countries'))
|
||||||
|
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
access_key, video_id = mobj.group('access_key_or_mcp', 'id')
|
access_key, video_id = mobj.group('access_key_or_mcp', 'id')
|
||||||
if access_key not in self._ANVACK_TABLE:
|
if access_key not in self._ANVACK_TABLE:
|
||||||
|
@ -3,13 +3,13 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
int_or_none,
|
||||||
HEADRequest,
|
mimetype2ext,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class AparatIE(InfoExtractor):
|
class AparatIE(InfoExtractor):
|
||||||
_VALID_URL = r'^https?://(?:www\.)?aparat\.com/(?:v/|video/video/embed/videohash/)(?P<id>[a-zA-Z0-9]+)'
|
_VALID_URL = r'https?://(?:www\.)?aparat\.com/(?:v/|video/video/embed/videohash/)(?P<id>[a-zA-Z0-9]+)'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.aparat.com/v/wP8On',
|
'url': 'http://www.aparat.com/v/wP8On',
|
||||||
@ -29,30 +29,41 @@ class AparatIE(InfoExtractor):
|
|||||||
# Note: There is an easier-to-parse configuration at
|
# Note: There is an easier-to-parse configuration at
|
||||||
# http://www.aparat.com/video/video/config/videohash/%video_id
|
# http://www.aparat.com/video/video/config/videohash/%video_id
|
||||||
# but the URL in there does not work
|
# but the URL in there does not work
|
||||||
embed_url = 'http://www.aparat.com/video/video/embed/vt/frame/showvideo/yes/videohash/' + video_id
|
webpage = self._download_webpage(
|
||||||
webpage = self._download_webpage(embed_url, video_id)
|
'http://www.aparat.com/video/video/embed/vt/frame/showvideo/yes/videohash/' + video_id,
|
||||||
|
video_id)
|
||||||
file_list = self._parse_json(self._search_regex(
|
|
||||||
r'fileList\s*=\s*JSON\.parse\(\'([^\']+)\'\)', webpage, 'file list'), video_id)
|
|
||||||
for i, item in enumerate(file_list[0]):
|
|
||||||
video_url = item['file']
|
|
||||||
req = HEADRequest(video_url)
|
|
||||||
res = self._request_webpage(
|
|
||||||
req, video_id, note='Testing video URL %d' % i, errnote=False)
|
|
||||||
if res:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
raise ExtractorError('No working video URLs found')
|
|
||||||
|
|
||||||
title = self._search_regex(r'\s+title:\s*"([^"]+)"', webpage, 'title')
|
title = self._search_regex(r'\s+title:\s*"([^"]+)"', webpage, 'title')
|
||||||
|
|
||||||
|
file_list = self._parse_json(
|
||||||
|
self._search_regex(
|
||||||
|
r'fileList\s*=\s*JSON\.parse\(\'([^\']+)\'\)', webpage,
|
||||||
|
'file list'),
|
||||||
|
video_id)
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
for item in file_list[0]:
|
||||||
|
file_url = item.get('file')
|
||||||
|
if not file_url:
|
||||||
|
continue
|
||||||
|
ext = mimetype2ext(item.get('type'))
|
||||||
|
label = item.get('label')
|
||||||
|
formats.append({
|
||||||
|
'url': file_url,
|
||||||
|
'ext': ext,
|
||||||
|
'format_id': label or ext,
|
||||||
|
'height': int_or_none(self._search_regex(
|
||||||
|
r'(\d+)[pP]', label or '', 'height', default=None)),
|
||||||
|
})
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
thumbnail = self._search_regex(
|
thumbnail = self._search_regex(
|
||||||
r'image:\s*"([^"]+)"', webpage, 'thumbnail', fatal=False)
|
r'image:\s*"([^"]+)"', webpage, 'thumbnail', fatal=False)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': title,
|
'title': title,
|
||||||
'url': video_url,
|
|
||||||
'ext': 'mp4',
|
|
||||||
'thumbnail': thumbnail,
|
'thumbnail': thumbnail,
|
||||||
'age_limit': self._family_friendly_search(webpage),
|
'age_limit': self._family_friendly_search(webpage),
|
||||||
|
'formats': formats,
|
||||||
}
|
}
|
||||||
|
@ -117,7 +117,7 @@ class AppleTrailersIE(InfoExtractor):
|
|||||||
continue
|
continue
|
||||||
formats.append({
|
formats.append({
|
||||||
'format_id': '%s-%s' % (version, size),
|
'format_id': '%s-%s' % (version, size),
|
||||||
'url': re.sub(r'_(\d+p.mov)', r'_h\1', src),
|
'url': re.sub(r'_(\d+p\.mov)', r'_h\1', src),
|
||||||
'width': int_or_none(size_data.get('width')),
|
'width': int_or_none(size_data.get('width')),
|
||||||
'height': int_or_none(size_data.get('height')),
|
'height': int_or_none(size_data.get('height')),
|
||||||
'language': version[:2],
|
'language': version[:2],
|
||||||
@ -179,7 +179,7 @@ class AppleTrailersIE(InfoExtractor):
|
|||||||
formats = []
|
formats = []
|
||||||
for format in settings['metadata']['sizes']:
|
for format in settings['metadata']['sizes']:
|
||||||
# The src is a file pointing to the real video file
|
# The src is a file pointing to the real video file
|
||||||
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,
|
||||||
'format': format['type'],
|
'format': format['type'],
|
||||||
|
@ -93,6 +93,7 @@ class ARDMediathekIE(InfoExtractor):
|
|||||||
|
|
||||||
duration = int_or_none(media_info.get('_duration'))
|
duration = int_or_none(media_info.get('_duration'))
|
||||||
thumbnail = media_info.get('_previewImage')
|
thumbnail = media_info.get('_previewImage')
|
||||||
|
is_live = media_info.get('_isLive') is True
|
||||||
|
|
||||||
subtitles = {}
|
subtitles = {}
|
||||||
subtitle_url = media_info.get('_subtitleUrl')
|
subtitle_url = media_info.get('_subtitleUrl')
|
||||||
@ -106,6 +107,7 @@ class ARDMediathekIE(InfoExtractor):
|
|||||||
'id': video_id,
|
'id': video_id,
|
||||||
'duration': duration,
|
'duration': duration,
|
||||||
'thumbnail': thumbnail,
|
'thumbnail': thumbnail,
|
||||||
|
'is_live': is_live,
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
'subtitles': subtitles,
|
'subtitles': subtitles,
|
||||||
}
|
}
|
||||||
@ -166,9 +168,11 @@ class ARDMediathekIE(InfoExtractor):
|
|||||||
# determine video id from url
|
# determine video id from url
|
||||||
m = re.match(self._VALID_URL, url)
|
m = re.match(self._VALID_URL, url)
|
||||||
|
|
||||||
|
document_id = None
|
||||||
|
|
||||||
numid = re.search(r'documentId=([0-9]+)', url)
|
numid = re.search(r'documentId=([0-9]+)', url)
|
||||||
if numid:
|
if numid:
|
||||||
video_id = numid.group(1)
|
document_id = video_id = numid.group(1)
|
||||||
else:
|
else:
|
||||||
video_id = m.group('video_id')
|
video_id = m.group('video_id')
|
||||||
|
|
||||||
@ -191,7 +195,7 @@ class ARDMediathekIE(InfoExtractor):
|
|||||||
|
|
||||||
title = self._html_search_regex(
|
title = self._html_search_regex(
|
||||||
[r'<h1(?:\s+class="boxTopHeadline")?>(.*?)</h1>',
|
[r'<h1(?:\s+class="boxTopHeadline")?>(.*?)</h1>',
|
||||||
r'<meta name="dcterms.title" content="(.*?)"/>',
|
r'<meta name="dcterms\.title" content="(.*?)"/>',
|
||||||
r'<h4 class="headline">(.*?)</h4>'],
|
r'<h4 class="headline">(.*?)</h4>'],
|
||||||
webpage, 'title')
|
webpage, 'title')
|
||||||
description = self._html_search_meta(
|
description = self._html_search_meta(
|
||||||
@ -228,12 +232,16 @@ class ARDMediathekIE(InfoExtractor):
|
|||||||
'formats': formats,
|
'formats': formats,
|
||||||
}
|
}
|
||||||
else: # request JSON file
|
else: # request JSON file
|
||||||
|
if not document_id:
|
||||||
|
video_id = self._search_regex(
|
||||||
|
r'/play/(?:config|media)/(\d+)', webpage, 'media id')
|
||||||
info = self._extract_media_info(
|
info = self._extract_media_info(
|
||||||
'http://www.ardmediathek.de/play/media/%s' % video_id, webpage, video_id)
|
'http://www.ardmediathek.de/play/media/%s' % video_id,
|
||||||
|
webpage, video_id)
|
||||||
|
|
||||||
info.update({
|
info.update({
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': title,
|
'title': self._live_title(title) if info.get('is_live') else title,
|
||||||
'description': description,
|
'description': description,
|
||||||
'thumbnail': thumbnail,
|
'thumbnail': thumbnail,
|
||||||
})
|
})
|
||||||
|
@ -6,15 +6,18 @@ import re
|
|||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import (
|
||||||
compat_parse_qs,
|
compat_parse_qs,
|
||||||
|
compat_str,
|
||||||
compat_urllib_parse_urlparse,
|
compat_urllib_parse_urlparse,
|
||||||
)
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
|
ExtractorError,
|
||||||
find_xpath_attr,
|
find_xpath_attr,
|
||||||
unified_strdate,
|
|
||||||
get_element_by_attribute,
|
get_element_by_attribute,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
NO_DEFAULT,
|
NO_DEFAULT,
|
||||||
qualities,
|
qualities,
|
||||||
|
try_get,
|
||||||
|
unified_strdate,
|
||||||
)
|
)
|
||||||
|
|
||||||
# There are different sources of video in arte.tv, the extraction process
|
# There are different sources of video in arte.tv, the extraction process
|
||||||
@ -79,6 +82,16 @@ class ArteTVBaseIE(InfoExtractor):
|
|||||||
info = self._download_json(json_url, video_id)
|
info = self._download_json(json_url, video_id)
|
||||||
player_info = info['videoJsonPlayer']
|
player_info = info['videoJsonPlayer']
|
||||||
|
|
||||||
|
vsr = try_get(player_info, lambda x: x['VSR'], dict)
|
||||||
|
if not vsr:
|
||||||
|
error = None
|
||||||
|
if try_get(player_info, lambda x: x['custom_msg']['type']) == 'error':
|
||||||
|
error = try_get(
|
||||||
|
player_info, lambda x: x['custom_msg']['msg'], compat_str)
|
||||||
|
if not error:
|
||||||
|
error = 'Video %s is not available' % player_info.get('VID') or video_id
|
||||||
|
raise ExtractorError(error, expected=True)
|
||||||
|
|
||||||
upload_date_str = player_info.get('shootingDate')
|
upload_date_str = player_info.get('shootingDate')
|
||||||
if not upload_date_str:
|
if not upload_date_str:
|
||||||
upload_date_str = (player_info.get('VRA') or player_info.get('VDA') or '').split(' ')[0]
|
upload_date_str = (player_info.get('VRA') or player_info.get('VDA') or '').split(' ')[0]
|
||||||
@ -107,7 +120,7 @@ class ArteTVBaseIE(InfoExtractor):
|
|||||||
langcode = LANGS.get(lang, lang)
|
langcode = LANGS.get(lang, lang)
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
for format_id, format_dict in player_info['VSR'].items():
|
for format_id, format_dict in vsr.items():
|
||||||
f = dict(format_dict)
|
f = dict(format_dict)
|
||||||
versionCode = f.get('versionCode')
|
versionCode = f.get('versionCode')
|
||||||
l = re.escape(langcode)
|
l = re.escape(langcode)
|
||||||
|
@ -87,7 +87,7 @@ class AtresPlayerIE(InfoExtractor):
|
|||||||
self._LOGIN_URL, urlencode_postdata(login_form))
|
self._LOGIN_URL, urlencode_postdata(login_form))
|
||||||
request.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
request.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
||||||
response = self._download_webpage(
|
response = self._download_webpage(
|
||||||
request, None, 'Logging in as %s' % username)
|
request, None, 'Logging in')
|
||||||
|
|
||||||
error = self._html_search_regex(
|
error = self._html_search_regex(
|
||||||
r'(?s)<ul[^>]+class="[^"]*\blist_error\b[^"]*">(.+?)</ul>',
|
r'(?s)<ul[^>]+class="[^"]*\blist_error\b[^"]*">(.+?)</ul>',
|
||||||
|
@ -43,7 +43,7 @@ class AudioBoomIE(InfoExtractor):
|
|||||||
|
|
||||||
def from_clip(field):
|
def from_clip(field):
|
||||||
if clip:
|
if clip:
|
||||||
clip.get(field)
|
return clip.get(field)
|
||||||
|
|
||||||
audio_url = from_clip('clipURLPriorToLoading') or self._og_search_property(
|
audio_url = from_clip('clipURLPriorToLoading') or self._og_search_property(
|
||||||
'audio', webpage, 'audio url')
|
'audio', webpage, 'audio url')
|
||||||
|
@ -47,7 +47,7 @@ class AZMedienIE(AZMedienBaseIE):
|
|||||||
'url': 'http://www.telezueri.ch/62-show-zuerinews/13772-episode-sonntag-18-dezember-2016/32419-segment-massenabweisungen-beim-hiltl-club-wegen-pelzboom',
|
'url': 'http://www.telezueri.ch/62-show-zuerinews/13772-episode-sonntag-18-dezember-2016/32419-segment-massenabweisungen-beim-hiltl-club-wegen-pelzboom',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '1_2444peh4',
|
'id': '1_2444peh4',
|
||||||
'ext': 'mov',
|
'ext': 'mp4',
|
||||||
'title': 'Massenabweisungen beim Hiltl Club wegen Pelzboom',
|
'title': 'Massenabweisungen beim Hiltl Club wegen Pelzboom',
|
||||||
'description': 'md5:9ea9dd1b159ad65b36ddcf7f0d7c76a8',
|
'description': 'md5:9ea9dd1b159ad65b36ddcf7f0d7c76a8',
|
||||||
'uploader_id': 'TeleZ?ri',
|
'uploader_id': 'TeleZ?ri',
|
||||||
|
@ -59,7 +59,7 @@ class BambuserIE(InfoExtractor):
|
|||||||
self._LOGIN_URL, urlencode_postdata(login_form))
|
self._LOGIN_URL, urlencode_postdata(login_form))
|
||||||
request.add_header('Referer', self._LOGIN_URL)
|
request.add_header('Referer', self._LOGIN_URL)
|
||||||
response = self._download_webpage(
|
response = self._download_webpage(
|
||||||
request, None, 'Logging in as %s' % username)
|
request, None, 'Logging in')
|
||||||
|
|
||||||
login_error = self._html_search_regex(
|
login_error = self._html_search_regex(
|
||||||
r'(?s)<div class="messages error">(.+?)</div>',
|
r'(?s)<div class="messages error">(.+?)</div>',
|
||||||
|
@ -242,7 +242,12 @@ class BandcampAlbumIE(InfoExtractor):
|
|||||||
raise ExtractorError('The page doesn\'t contain any tracks')
|
raise ExtractorError('The page doesn\'t contain any tracks')
|
||||||
# Only tracks with duration info have songs
|
# Only tracks with duration info have songs
|
||||||
entries = [
|
entries = [
|
||||||
self.url_result(compat_urlparse.urljoin(url, t_path), ie=BandcampIE.ie_key())
|
self.url_result(
|
||||||
|
compat_urlparse.urljoin(url, t_path),
|
||||||
|
ie=BandcampIE.ie_key(),
|
||||||
|
video_title=self._search_regex(
|
||||||
|
r'<span\b[^>]+\bitemprop=["\']name["\'][^>]*>([^<]+)',
|
||||||
|
elem_content, 'track title', fatal=False))
|
||||||
for elem_content, t_path in track_elements
|
for elem_content, t_path in track_elements
|
||||||
if self._html_search_meta('duration', elem_content, default=None)]
|
if self._html_search_meta('duration', elem_content, default=None)]
|
||||||
|
|
||||||
|
@ -29,15 +29,16 @@ from ..compat import (
|
|||||||
class BBCCoUkIE(InfoExtractor):
|
class BBCCoUkIE(InfoExtractor):
|
||||||
IE_NAME = 'bbc.co.uk'
|
IE_NAME = 'bbc.co.uk'
|
||||||
IE_DESC = 'BBC iPlayer'
|
IE_DESC = 'BBC iPlayer'
|
||||||
_ID_REGEX = r'[pb][\da-z]{7}'
|
_ID_REGEX = r'[pbw][\da-z]{7}'
|
||||||
_VALID_URL = r'''(?x)
|
_VALID_URL = r'''(?x)
|
||||||
https?://
|
https?://
|
||||||
(?:www\.)?bbc\.co\.uk/
|
(?:www\.)?bbc\.co\.uk/
|
||||||
(?:
|
(?:
|
||||||
programmes/(?!articles/)|
|
programmes/(?!articles/)|
|
||||||
iplayer(?:/[^/]+)?/(?:episode/|playlist/)|
|
iplayer(?:/[^/]+)?/(?:episode/|playlist/)|
|
||||||
music/clips[/#]|
|
music/(?:clips|audiovideo/popular)[/#]|
|
||||||
radio/player/
|
radio/player/|
|
||||||
|
events/[^/]+/play/[^/]+/
|
||||||
)
|
)
|
||||||
(?P<id>%s)(?!/(?:episodes|broadcasts|clips))
|
(?P<id>%s)(?!/(?:episodes|broadcasts|clips))
|
||||||
''' % _ID_REGEX
|
''' % _ID_REGEX
|
||||||
@ -229,8 +230,13 @@ class BBCCoUkIE(InfoExtractor):
|
|||||||
}, {
|
}, {
|
||||||
'url': 'http://www.bbc.co.uk/radio/player/p03cchwf',
|
'url': 'http://www.bbc.co.uk/radio/player/p03cchwf',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
}
|
}, {
|
||||||
]
|
'url': 'https://www.bbc.co.uk/music/audiovideo/popular#p055bc55',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.bbc.co.uk/programmes/w3csv1y9',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
_USP_RE = r'/([^/]+?)\.ism(?:\.hlsv2\.ism)?/[^/]+\.m3u8'
|
_USP_RE = r'/([^/]+?)\.ism(?:\.hlsv2\.ism)?/[^/]+\.m3u8'
|
||||||
|
|
||||||
@ -380,7 +386,7 @@ class BBCCoUkIE(InfoExtractor):
|
|||||||
m3u8_id=format_id, fatal=False))
|
m3u8_id=format_id, fatal=False))
|
||||||
if re.search(self._USP_RE, href):
|
if re.search(self._USP_RE, href):
|
||||||
usp_formats = self._extract_m3u8_formats(
|
usp_formats = self._extract_m3u8_formats(
|
||||||
re.sub(self._USP_RE, r'/\1.ism/\1.m3u8', href),
|
re.sub(self._USP_RE, r'/\1\.ism/\1\.m3u8', href),
|
||||||
programme_id, ext='mp4', entry_protocol='m3u8_native',
|
programme_id, ext='mp4', entry_protocol='m3u8_native',
|
||||||
m3u8_id=format_id, fatal=False)
|
m3u8_id=format_id, fatal=False)
|
||||||
for f in usp_formats:
|
for f in usp_formats:
|
||||||
@ -523,6 +529,12 @@ class BBCCoUkIE(InfoExtractor):
|
|||||||
|
|
||||||
webpage = self._download_webpage(url, group_id, 'Downloading video page')
|
webpage = self._download_webpage(url, group_id, 'Downloading video page')
|
||||||
|
|
||||||
|
error = self._search_regex(
|
||||||
|
r'<div\b[^>]+\bclass=["\']smp__message delta["\'][^>]*>([^<]+)<',
|
||||||
|
webpage, 'error', default=None)
|
||||||
|
if error:
|
||||||
|
raise ExtractorError(error, expected=True)
|
||||||
|
|
||||||
programme_id = None
|
programme_id = None
|
||||||
duration = None
|
duration = None
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ from ..compat import (
|
|||||||
from ..utils import (
|
from ..utils import (
|
||||||
int_or_none,
|
int_or_none,
|
||||||
parse_iso8601,
|
parse_iso8601,
|
||||||
|
urljoin,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -36,9 +37,11 @@ class BeegIE(InfoExtractor):
|
|||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
cpl_url = self._search_regex(
|
cpl_url = self._search_regex(
|
||||||
r'<script[^>]+src=(["\'])(?P<url>(?:https?:)?//static\.beeg\.com/cpl/\d+\.js.*?)\1',
|
r'<script[^>]+src=(["\'])(?P<url>(?:/static|(?:https?:)?//static\.beeg\.com)/cpl/\d+\.js.*?)\1',
|
||||||
webpage, 'cpl', default=None, group='url')
|
webpage, 'cpl', default=None, group='url')
|
||||||
|
|
||||||
|
cpl_url = urljoin(url, cpl_url)
|
||||||
|
|
||||||
beeg_version, beeg_salt = [None] * 2
|
beeg_version, beeg_salt = [None] * 2
|
||||||
|
|
||||||
if cpl_url:
|
if cpl_url:
|
||||||
@ -54,12 +57,16 @@ class BeegIE(InfoExtractor):
|
|||||||
r'beeg_salt\s*=\s*(["\'])(?P<beeg_salt>.+?)\1', cpl, 'beeg salt',
|
r'beeg_salt\s*=\s*(["\'])(?P<beeg_salt>.+?)\1', cpl, 'beeg salt',
|
||||||
default=None, group='beeg_salt')
|
default=None, group='beeg_salt')
|
||||||
|
|
||||||
beeg_version = beeg_version or '2000'
|
beeg_version = beeg_version or '2185'
|
||||||
beeg_salt = beeg_salt or 'pmweAkq8lAYKdfWcFCUj0yoVgoPlinamH5UE1CB3H'
|
beeg_salt = beeg_salt or 'pmweAkq8lAYKdfWcFCUj0yoVgoPlinamH5UE1CB3H'
|
||||||
|
|
||||||
|
for api_path in ('', 'api.'):
|
||||||
video = self._download_json(
|
video = self._download_json(
|
||||||
'https://api.beeg.com/api/v6/%s/video/%s' % (beeg_version, video_id),
|
'https://%sbeeg.com/api/v6/%s/video/%s'
|
||||||
video_id)
|
% (api_path, beeg_version, video_id), video_id,
|
||||||
|
fatal=api_path == 'api.')
|
||||||
|
if video:
|
||||||
|
break
|
||||||
|
|
||||||
def split(o, e):
|
def split(o, e):
|
||||||
def cut(s, x):
|
def cut(s, x):
|
||||||
|
@ -33,13 +33,18 @@ class BpbIE(InfoExtractor):
|
|||||||
title = self._html_search_regex(
|
title = self._html_search_regex(
|
||||||
r'<h2 class="white">(.*?)</h2>', webpage, 'title')
|
r'<h2 class="white">(.*?)</h2>', webpage, 'title')
|
||||||
video_info_dicts = re.findall(
|
video_info_dicts = re.findall(
|
||||||
r"({\s*src:\s*'http://film\.bpb\.de/[^}]+})", webpage)
|
r"({\s*src\s*:\s*'https?://film\.bpb\.de/[^}]+})", webpage)
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
for video_info in video_info_dicts:
|
for video_info in video_info_dicts:
|
||||||
video_info = self._parse_json(video_info, video_id, transform_source=js_to_json)
|
video_info = self._parse_json(
|
||||||
quality = video_info['quality']
|
video_info, video_id, transform_source=js_to_json, fatal=False)
|
||||||
video_url = video_info['src']
|
if not video_info:
|
||||||
|
continue
|
||||||
|
video_url = video_info.get('src')
|
||||||
|
if not video_url:
|
||||||
|
continue
|
||||||
|
quality = 'high' if '_high' in video_url else 'low'
|
||||||
formats.append({
|
formats.append({
|
||||||
'url': video_url,
|
'url': video_url,
|
||||||
'preference': 10 if quality == 'high' else 0,
|
'preference': 10 if quality == 'high' else 0,
|
||||||
|
@ -1,20 +1,23 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import json
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
|
determine_ext,
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
parse_duration,
|
parse_duration,
|
||||||
|
parse_iso8601,
|
||||||
xpath_element,
|
xpath_element,
|
||||||
xpath_text,
|
xpath_text,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class BRIE(InfoExtractor):
|
class BRIE(InfoExtractor):
|
||||||
IE_DESC = 'Bayerischer Rundfunk Mediathek'
|
IE_DESC = 'Bayerischer Rundfunk'
|
||||||
_VALID_URL = r'(?P<base_url>https?://(?:www\.)?br(?:-klassik)?\.de)/(?:[a-z0-9\-_]+/)+(?P<id>[a-z0-9\-_]+)\.html'
|
_VALID_URL = r'(?P<base_url>https?://(?:www\.)?br(?:-klassik)?\.de)/(?:[a-z0-9\-_]+/)+(?P<id>[a-z0-9\-_]+)\.html'
|
||||||
|
|
||||||
_TESTS = [
|
_TESTS = [
|
||||||
@ -123,10 +126,10 @@ class BRIE(InfoExtractor):
|
|||||||
for asset in assets.findall('asset'):
|
for asset in assets.findall('asset'):
|
||||||
format_url = xpath_text(asset, ['downloadUrl', 'url'])
|
format_url = xpath_text(asset, ['downloadUrl', 'url'])
|
||||||
asset_type = asset.get('type')
|
asset_type = asset.get('type')
|
||||||
if asset_type == 'HDS':
|
if asset_type.startswith('HDS'):
|
||||||
formats.extend(self._extract_f4m_formats(
|
formats.extend(self._extract_f4m_formats(
|
||||||
format_url + '?hdcore=3.2.0', media_id, f4m_id='hds', fatal=False))
|
format_url + '?hdcore=3.2.0', media_id, f4m_id='hds', fatal=False))
|
||||||
elif asset_type == 'HLS':
|
elif asset_type.startswith('HLS'):
|
||||||
formats.extend(self._extract_m3u8_formats(
|
formats.extend(self._extract_m3u8_formats(
|
||||||
format_url, media_id, 'mp4', 'm3u8_native', m3u8_id='hds', fatal=False))
|
format_url, media_id, 'mp4', 'm3u8_native', m3u8_id='hds', fatal=False))
|
||||||
else:
|
else:
|
||||||
@ -169,3 +172,140 @@ class BRIE(InfoExtractor):
|
|||||||
} for variant in variants.findall('variant') if xpath_text(variant, 'url')]
|
} for variant in variants.findall('variant') if xpath_text(variant, 'url')]
|
||||||
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
|
||||||
|
|
||||||
|
|
||||||
|
class BRMediathekIE(InfoExtractor):
|
||||||
|
IE_DESC = 'Bayerischer Rundfunk Mediathek'
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?br\.de/mediathek/video/[^/?&#]*?-(?P<id>av:[0-9a-f]{24})'
|
||||||
|
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://www.br.de/mediathek/video/gesundheit-die-sendung-vom-28112017-av:5a1e6a6e8fce6d001871cc8e',
|
||||||
|
'md5': 'fdc3d485835966d1622587d08ba632ec',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'av:5a1e6a6e8fce6d001871cc8e',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Die Sendung vom 28.11.2017',
|
||||||
|
'description': 'md5:6000cdca5912ab2277e5b7339f201ccc',
|
||||||
|
'timestamp': 1511942766,
|
||||||
|
'upload_date': '20171129',
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
clip_id = self._match_id(url)
|
||||||
|
|
||||||
|
clip = self._download_json(
|
||||||
|
'https://proxy-base.master.mango.express/graphql',
|
||||||
|
clip_id, data=json.dumps({
|
||||||
|
"query": """{
|
||||||
|
viewer {
|
||||||
|
clip(id: "%s") {
|
||||||
|
title
|
||||||
|
description
|
||||||
|
duration
|
||||||
|
createdAt
|
||||||
|
ageRestriction
|
||||||
|
videoFiles {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
publicLocation
|
||||||
|
fileSize
|
||||||
|
videoProfile {
|
||||||
|
width
|
||||||
|
height
|
||||||
|
bitrate
|
||||||
|
encoding
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
captionFiles {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
publicLocation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
teaserImages {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
imageFiles {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
publicLocation
|
||||||
|
width
|
||||||
|
height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}""" % clip_id}).encode(), headers={
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
})['data']['viewer']['clip']
|
||||||
|
title = clip['title']
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
for edge in clip.get('videoFiles', {}).get('edges', []):
|
||||||
|
node = edge.get('node', {})
|
||||||
|
n_url = node.get('publicLocation')
|
||||||
|
if not n_url:
|
||||||
|
continue
|
||||||
|
ext = determine_ext(n_url)
|
||||||
|
if ext == 'm3u8':
|
||||||
|
formats.extend(self._extract_m3u8_formats(
|
||||||
|
n_url, clip_id, 'mp4', 'm3u8_native',
|
||||||
|
m3u8_id='hls', fatal=False))
|
||||||
|
else:
|
||||||
|
video_profile = node.get('videoProfile', {})
|
||||||
|
tbr = int_or_none(video_profile.get('bitrate'))
|
||||||
|
format_id = 'http'
|
||||||
|
if tbr:
|
||||||
|
format_id += '-%d' % tbr
|
||||||
|
formats.append({
|
||||||
|
'format_id': format_id,
|
||||||
|
'url': n_url,
|
||||||
|
'width': int_or_none(video_profile.get('width')),
|
||||||
|
'height': int_or_none(video_profile.get('height')),
|
||||||
|
'tbr': tbr,
|
||||||
|
'filesize': int_or_none(node.get('fileSize')),
|
||||||
|
})
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
subtitles = {}
|
||||||
|
for edge in clip.get('captionFiles', {}).get('edges', []):
|
||||||
|
node = edge.get('node', {})
|
||||||
|
n_url = node.get('publicLocation')
|
||||||
|
if not n_url:
|
||||||
|
continue
|
||||||
|
subtitles.setdefault('de', []).append({
|
||||||
|
'url': n_url,
|
||||||
|
})
|
||||||
|
|
||||||
|
thumbnails = []
|
||||||
|
for edge in clip.get('teaserImages', {}).get('edges', []):
|
||||||
|
for image_edge in edge.get('node', {}).get('imageFiles', {}).get('edges', []):
|
||||||
|
node = image_edge.get('node', {})
|
||||||
|
n_url = node.get('publicLocation')
|
||||||
|
if not n_url:
|
||||||
|
continue
|
||||||
|
thumbnails.append({
|
||||||
|
'url': n_url,
|
||||||
|
'width': int_or_none(node.get('width')),
|
||||||
|
'height': int_or_none(node.get('height')),
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': clip_id,
|
||||||
|
'title': title,
|
||||||
|
'description': clip.get('description'),
|
||||||
|
'duration': int_or_none(clip.get('duration')),
|
||||||
|
'timestamp': parse_iso8601(clip.get('createdAt')),
|
||||||
|
'age_limit': int_or_none(clip.get('ageRestriction')),
|
||||||
|
'formats': formats,
|
||||||
|
'subtitles': subtitles,
|
||||||
|
'thumbnails': thumbnails,
|
||||||
|
}
|
||||||
|
@ -84,9 +84,10 @@ class BuzzFeedIE(InfoExtractor):
|
|||||||
continue
|
continue
|
||||||
entries.append(self.url_result(video['url']))
|
entries.append(self.url_result(video['url']))
|
||||||
|
|
||||||
facebook_url = FacebookIE._extract_url(webpage)
|
facebook_urls = FacebookIE._extract_urls(webpage)
|
||||||
if facebook_url:
|
entries.extend([
|
||||||
entries.append(self.url_result(facebook_url))
|
self.url_result(facebook_url)
|
||||||
|
for facebook_url in facebook_urls])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'_type': 'playlist',
|
'_type': 'playlist',
|
||||||
|
@ -1,26 +1,112 @@
|
|||||||
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 float_or_none
|
from .gigya import GigyaBaseIE
|
||||||
|
from ..compat import compat_HTTPError
|
||||||
|
from ..utils import (
|
||||||
|
ExtractorError,
|
||||||
|
strip_or_none,
|
||||||
|
float_or_none,
|
||||||
|
int_or_none,
|
||||||
|
parse_iso8601,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CanvasIE(InfoExtractor):
|
class CanvasIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://mediazone\.vrt\.be/api/v1/(?P<site_id>canvas|een|ketnet|vrtvideo)/assets/(?P<id>[^/?#&]+)'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://mediazone.vrt.be/api/v1/ketnet/assets/md-ast-4ac54990-ce66-4d00-a8ca-9eac86f4c475',
|
||||||
|
'md5': '90139b746a0a9bd7bb631283f6e2a64e',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'md-ast-4ac54990-ce66-4d00-a8ca-9eac86f4c475',
|
||||||
|
'display_id': 'md-ast-4ac54990-ce66-4d00-a8ca-9eac86f4c475',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Nachtwacht: De Greystook',
|
||||||
|
'description': 'md5:1db3f5dc4c7109c821261e7512975be7',
|
||||||
|
'thumbnail': r're:^https?://.*\.jpg$',
|
||||||
|
'duration': 1468.03,
|
||||||
|
},
|
||||||
|
'expected_warnings': ['is not a supported codec', 'Unknown MIME type'],
|
||||||
|
}, {
|
||||||
|
'url': 'https://mediazone.vrt.be/api/v1/canvas/assets/mz-ast-5e5f90b6-2d72-4c40-82c2-e134f884e93e',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
site_id, video_id = mobj.group('site_id'), mobj.group('id')
|
||||||
|
|
||||||
|
data = self._download_json(
|
||||||
|
'https://mediazone.vrt.be/api/v1/%s/assets/%s'
|
||||||
|
% (site_id, video_id), video_id)
|
||||||
|
|
||||||
|
title = data['title']
|
||||||
|
description = data.get('description')
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
for target in data['targetUrls']:
|
||||||
|
format_url, format_type = target.get('url'), target.get('type')
|
||||||
|
if not format_url or not format_type:
|
||||||
|
continue
|
||||||
|
if format_type == 'HLS':
|
||||||
|
formats.extend(self._extract_m3u8_formats(
|
||||||
|
format_url, video_id, 'mp4', entry_protocol='m3u8_native',
|
||||||
|
m3u8_id=format_type, fatal=False))
|
||||||
|
elif format_type == 'HDS':
|
||||||
|
formats.extend(self._extract_f4m_formats(
|
||||||
|
format_url, video_id, f4m_id=format_type, fatal=False))
|
||||||
|
elif format_type == 'MPEG_DASH':
|
||||||
|
formats.extend(self._extract_mpd_formats(
|
||||||
|
format_url, video_id, mpd_id=format_type, fatal=False))
|
||||||
|
elif format_type == 'HSS':
|
||||||
|
formats.extend(self._extract_ism_formats(
|
||||||
|
format_url, video_id, ism_id='mss', fatal=False))
|
||||||
|
else:
|
||||||
|
formats.append({
|
||||||
|
'format_id': format_type,
|
||||||
|
'url': format_url,
|
||||||
|
})
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
subtitles = {}
|
||||||
|
subtitle_urls = data.get('subtitleUrls')
|
||||||
|
if isinstance(subtitle_urls, list):
|
||||||
|
for subtitle in subtitle_urls:
|
||||||
|
subtitle_url = subtitle.get('url')
|
||||||
|
if subtitle_url and subtitle.get('type') == 'CLOSED':
|
||||||
|
subtitles.setdefault('nl', []).append({'url': subtitle_url})
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'display_id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'formats': formats,
|
||||||
|
'duration': float_or_none(data.get('duration'), 1000),
|
||||||
|
'thumbnail': data.get('posterImageUrl'),
|
||||||
|
'subtitles': subtitles,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class CanvasEenIE(InfoExtractor):
|
||||||
IE_DESC = 'canvas.be and een.be'
|
IE_DESC = 'canvas.be and een.be'
|
||||||
_VALID_URL = r'https?://(?:www\.)?(?P<site_id>canvas|een)\.be/(?:[^/]+/)*(?P<id>[^/?#&]+)'
|
_VALID_URL = r'https?://(?:www\.)?(?P<site_id>canvas|een)\.be/(?:[^/]+/)*(?P<id>[^/?#&]+)'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.canvas.be/video/de-afspraak/najaar-2015/de-afspraak-veilt-voor-de-warmste-week',
|
'url': 'http://www.canvas.be/video/de-afspraak/najaar-2015/de-afspraak-veilt-voor-de-warmste-week',
|
||||||
'md5': 'ea838375a547ac787d4064d8c7860a6c',
|
'md5': 'ed66976748d12350b118455979cca293',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'mz-ast-5e5f90b6-2d72-4c40-82c2-e134f884e93e',
|
'id': 'mz-ast-5e5f90b6-2d72-4c40-82c2-e134f884e93e',
|
||||||
'display_id': 'de-afspraak-veilt-voor-de-warmste-week',
|
'display_id': 'de-afspraak-veilt-voor-de-warmste-week',
|
||||||
'ext': 'mp4',
|
'ext': 'flv',
|
||||||
'title': 'De afspraak veilt voor de Warmste Week',
|
'title': 'De afspraak veilt voor de Warmste Week',
|
||||||
'description': 'md5:24cb860c320dc2be7358e0e5aa317ba6',
|
'description': 'md5:24cb860c320dc2be7358e0e5aa317ba6',
|
||||||
'thumbnail': r're:^https?://.*\.jpg$',
|
'thumbnail': r're:^https?://.*\.jpg$',
|
||||||
'duration': 49.02,
|
'duration': 49.02,
|
||||||
}
|
},
|
||||||
|
'expected_warnings': ['is not a supported codec'],
|
||||||
}, {
|
}, {
|
||||||
# with subtitles
|
# with subtitles
|
||||||
'url': 'http://www.canvas.be/video/panorama/2016/pieter-0167',
|
'url': 'http://www.canvas.be/video/panorama/2016/pieter-0167',
|
||||||
@ -40,7 +126,8 @@ class CanvasIE(InfoExtractor):
|
|||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
}
|
},
|
||||||
|
'skip': 'Pagina niet gevonden',
|
||||||
}, {
|
}, {
|
||||||
'url': 'https://www.een.be/sorry-voor-alles/herbekijk-sorry-voor-alles',
|
'url': 'https://www.een.be/sorry-voor-alles/herbekijk-sorry-voor-alles',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@ -54,7 +141,8 @@ class CanvasIE(InfoExtractor):
|
|||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
}
|
},
|
||||||
|
'skip': 'Episode no longer available',
|
||||||
}, {
|
}, {
|
||||||
'url': 'https://www.canvas.be/check-point/najaar-2016/de-politie-uw-vriend',
|
'url': 'https://www.canvas.be/check-point/najaar-2016/de-politie-uw-vriend',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
@ -66,55 +154,157 @@ class CanvasIE(InfoExtractor):
|
|||||||
|
|
||||||
webpage = self._download_webpage(url, display_id)
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
|
||||||
title = (self._search_regex(
|
title = strip_or_none(self._search_regex(
|
||||||
r'<h1[^>]+class="video__body__header__title"[^>]*>(.+?)</h1>',
|
r'<h1[^>]+class="video__body__header__title"[^>]*>(.+?)</h1>',
|
||||||
webpage, 'title', default=None) or self._og_search_title(
|
webpage, 'title', default=None) or self._og_search_title(
|
||||||
webpage)).strip()
|
webpage, default=None))
|
||||||
|
|
||||||
video_id = self._html_search_regex(
|
video_id = self._html_search_regex(
|
||||||
r'data-video=(["\'])(?P<id>(?:(?!\1).)+)\1', webpage, 'video id', group='id')
|
r'data-video=(["\'])(?P<id>(?:(?!\1).)+)\1', webpage, 'video id',
|
||||||
|
group='id')
|
||||||
data = self._download_json(
|
|
||||||
'https://mediazone.vrt.be/api/v1/%s/assets/%s'
|
|
||||||
% (site_id, video_id), display_id)
|
|
||||||
|
|
||||||
formats = []
|
|
||||||
for target in data['targetUrls']:
|
|
||||||
format_url, format_type = target.get('url'), target.get('type')
|
|
||||||
if not format_url or not format_type:
|
|
||||||
continue
|
|
||||||
if format_type == 'HLS':
|
|
||||||
formats.extend(self._extract_m3u8_formats(
|
|
||||||
format_url, display_id, entry_protocol='m3u8_native',
|
|
||||||
ext='mp4', preference=0, fatal=False, m3u8_id=format_type))
|
|
||||||
elif format_type == 'HDS':
|
|
||||||
formats.extend(self._extract_f4m_formats(
|
|
||||||
format_url, display_id, f4m_id=format_type, fatal=False))
|
|
||||||
elif format_type == 'MPEG_DASH':
|
|
||||||
formats.extend(self._extract_mpd_formats(
|
|
||||||
format_url, display_id, mpd_id=format_type, fatal=False))
|
|
||||||
else:
|
|
||||||
formats.append({
|
|
||||||
'format_id': format_type,
|
|
||||||
'url': format_url,
|
|
||||||
})
|
|
||||||
self._sort_formats(formats)
|
|
||||||
|
|
||||||
subtitles = {}
|
|
||||||
subtitle_urls = data.get('subtitleUrls')
|
|
||||||
if isinstance(subtitle_urls, list):
|
|
||||||
for subtitle in subtitle_urls:
|
|
||||||
subtitle_url = subtitle.get('url')
|
|
||||||
if subtitle_url and subtitle.get('type') == 'CLOSED':
|
|
||||||
subtitles.setdefault('nl', []).append({'url': subtitle_url})
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
'_type': 'url_transparent',
|
||||||
|
'url': 'https://mediazone.vrt.be/api/v1/%s/assets/%s' % (site_id, video_id),
|
||||||
|
'ie_key': CanvasIE.ie_key(),
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'display_id': display_id,
|
'display_id': display_id,
|
||||||
'title': title,
|
'title': title,
|
||||||
'description': self._og_search_description(webpage),
|
'description': self._og_search_description(webpage),
|
||||||
'formats': formats,
|
}
|
||||||
'duration': float_or_none(data.get('duration'), 1000),
|
|
||||||
'thumbnail': data.get('posterImageUrl'),
|
|
||||||
'subtitles': subtitles,
|
class VrtNUIE(GigyaBaseIE):
|
||||||
|
IE_DESC = 'VrtNU.be'
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?vrt\.be/(?P<site_id>vrtnu)/(?:[^/]+/)*(?P<id>[^/?#&]+)'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://www.vrt.be/vrtnu/a-z/postbus-x/1/postbus-x-s1a1/',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'pbs-pub-2e2d8c27-df26-45c9-9dc6-90c78153044d$vid-90c932b1-e21d-4fb8-99b1-db7b49cf74de',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'De zwarte weduwe',
|
||||||
|
'description': 'md5:d90c21dced7db869a85db89a623998d4',
|
||||||
|
'duration': 1457.04,
|
||||||
|
'thumbnail': r're:^https?://.*\.jpg$',
|
||||||
|
'season': '1',
|
||||||
|
'season_number': 1,
|
||||||
|
'episode_number': 1,
|
||||||
|
},
|
||||||
|
'skip': 'This video is only available for registered users'
|
||||||
|
}]
|
||||||
|
_NETRC_MACHINE = 'vrtnu'
|
||||||
|
_APIKEY = '3_0Z2HujMtiWq_pkAjgnS2Md2E11a1AwZjYiBETtwNE-EoEHDINgtnvcAOpNgmrVGy'
|
||||||
|
_CONTEXT_ID = 'R3595707040'
|
||||||
|
|
||||||
|
def _real_initialize(self):
|
||||||
|
self._login()
|
||||||
|
|
||||||
|
def _login(self):
|
||||||
|
username, password = self._get_login_info()
|
||||||
|
if username is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
auth_data = {
|
||||||
|
'APIKey': self._APIKEY,
|
||||||
|
'targetEnv': 'jssdk',
|
||||||
|
'loginID': username,
|
||||||
|
'password': password,
|
||||||
|
'authMode': 'cookie',
|
||||||
|
}
|
||||||
|
|
||||||
|
auth_info = self._gigya_login(auth_data)
|
||||||
|
|
||||||
|
# Sometimes authentication fails for no good reason, retry
|
||||||
|
login_attempt = 1
|
||||||
|
while login_attempt <= 3:
|
||||||
|
try:
|
||||||
|
# When requesting a token, no actual token is returned, but the
|
||||||
|
# necessary cookies are set.
|
||||||
|
self._request_webpage(
|
||||||
|
'https://token.vrt.be',
|
||||||
|
None, note='Requesting a token', errnote='Could not get a token',
|
||||||
|
headers={
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Referer': 'https://www.vrt.be/vrtnu/',
|
||||||
|
},
|
||||||
|
data=json.dumps({
|
||||||
|
'uid': auth_info['UID'],
|
||||||
|
'uidsig': auth_info['UIDSignature'],
|
||||||
|
'ts': auth_info['signatureTimestamp'],
|
||||||
|
'email': auth_info['profile']['email'],
|
||||||
|
}).encode('utf-8'))
|
||||||
|
except ExtractorError as e:
|
||||||
|
if isinstance(e.cause, compat_HTTPError) and e.cause.code == 401:
|
||||||
|
login_attempt += 1
|
||||||
|
self.report_warning('Authentication failed')
|
||||||
|
self._sleep(1, None, msg_template='Waiting for %(timeout)s seconds before trying again')
|
||||||
|
else:
|
||||||
|
raise e
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
display_id = self._match_id(url)
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
|
||||||
|
title = self._html_search_regex(
|
||||||
|
r'(?ms)<h1 class="content__heading">(.+?)</h1>',
|
||||||
|
webpage, 'title').strip()
|
||||||
|
|
||||||
|
description = self._html_search_regex(
|
||||||
|
r'(?ms)<div class="content__description">(.+?)</div>',
|
||||||
|
webpage, 'description', default=None)
|
||||||
|
|
||||||
|
season = self._html_search_regex(
|
||||||
|
[r'''(?xms)<div\ class="tabs__tab\ tabs__tab--active">\s*
|
||||||
|
<span>seizoen\ (.+?)</span>\s*
|
||||||
|
</div>''',
|
||||||
|
r'<option value="seizoen (\d{1,3})" data-href="[^"]+?" selected>'],
|
||||||
|
webpage, 'season', default=None)
|
||||||
|
|
||||||
|
season_number = int_or_none(season)
|
||||||
|
|
||||||
|
episode_number = int_or_none(self._html_search_regex(
|
||||||
|
r'''(?xms)<div\ class="content__episode">\s*
|
||||||
|
<abbr\ title="aflevering">afl</abbr>\s*<span>(\d+)</span>
|
||||||
|
</div>''',
|
||||||
|
webpage, 'episode_number', default=None))
|
||||||
|
|
||||||
|
release_date = parse_iso8601(self._html_search_regex(
|
||||||
|
r'(?ms)<div class="content__broadcastdate">\s*<time\ datetime="(.+?)"',
|
||||||
|
webpage, 'release_date', default=None))
|
||||||
|
|
||||||
|
# If there's a ? or a # in the URL, remove them and everything after
|
||||||
|
clean_url = url.split('?')[0].split('#')[0].strip('/')
|
||||||
|
securevideo_url = clean_url + '.mssecurevideo.json'
|
||||||
|
|
||||||
|
try:
|
||||||
|
video = self._download_json(securevideo_url, display_id)
|
||||||
|
except ExtractorError as e:
|
||||||
|
if isinstance(e.cause, compat_HTTPError) and e.cause.code == 401:
|
||||||
|
self.raise_login_required()
|
||||||
|
raise
|
||||||
|
|
||||||
|
# We are dealing with a '../<show>.relevant' URL
|
||||||
|
redirect_url = video.get('url')
|
||||||
|
if redirect_url:
|
||||||
|
return self.url_result(self._proto_relative_url(redirect_url, 'https:'))
|
||||||
|
|
||||||
|
# There is only one entry, but with an unknown key, so just get
|
||||||
|
# the first one
|
||||||
|
video_id = list(video.values())[0].get('videoid')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'_type': 'url_transparent',
|
||||||
|
'url': 'https://mediazone.vrt.be/api/v1/vrtvideo/assets/%s' % video_id,
|
||||||
|
'ie_key': CanvasIE.ie_key(),
|
||||||
|
'id': video_id,
|
||||||
|
'display_id': display_id,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'season': season,
|
||||||
|
'season_number': season_number,
|
||||||
|
'episode_number': episode_number,
|
||||||
|
'release_date': release_date,
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ class CartoonNetworkIE(TurnerBaseIE):
|
|||||||
'http://www.cartoonnetwork.com/video-seo-svc/episodeservices/getCvpPlaylist?networkName=CN2&' + query, video_id, {
|
'http://www.cartoonnetwork.com/video-seo-svc/episodeservices/getCvpPlaylist?networkName=CN2&' + query, video_id, {
|
||||||
'secure': {
|
'secure': {
|
||||||
'media_src': 'http://androidhls-secure.cdn.turner.com/toon/big',
|
'media_src': 'http://androidhls-secure.cdn.turner.com/toon/big',
|
||||||
'tokenizer_src': 'http://www.cartoonnetwork.com/cntv/mvpd/processors/services/token_ipadAdobe.do',
|
'tokenizer_src': 'https://token.vgtf.net/token/token_mobile',
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
'url': url,
|
'url': url,
|
||||||
|
@ -200,6 +200,7 @@ class CBCWatchBaseIE(InfoExtractor):
|
|||||||
'media': 'http://search.yahoo.com/mrss/',
|
'media': 'http://search.yahoo.com/mrss/',
|
||||||
'clearleap': 'http://www.clearleap.com/namespace/clearleap/1.0/',
|
'clearleap': 'http://www.clearleap.com/namespace/clearleap/1.0/',
|
||||||
}
|
}
|
||||||
|
_GEO_COUNTRIES = ['CA']
|
||||||
|
|
||||||
def _call_api(self, path, video_id):
|
def _call_api(self, path, video_id):
|
||||||
url = path if path.startswith('http') else self._API_BASE_URL + path
|
url = path if path.startswith('http') else self._API_BASE_URL + path
|
||||||
@ -287,6 +288,11 @@ class CBCWatchBaseIE(InfoExtractor):
|
|||||||
class CBCWatchVideoIE(CBCWatchBaseIE):
|
class CBCWatchVideoIE(CBCWatchBaseIE):
|
||||||
IE_NAME = 'cbc.ca:watch:video'
|
IE_NAME = 'cbc.ca:watch:video'
|
||||||
_VALID_URL = r'https?://api-cbc\.cloud\.clearleap\.com/cloffice/client/web/play/?\?.*?\bcontentId=(?P<id>[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12})'
|
_VALID_URL = r'https?://api-cbc\.cloud\.clearleap\.com/cloffice/client/web/play/?\?.*?\bcontentId=(?P<id>[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12})'
|
||||||
|
_TEST = {
|
||||||
|
# geo-restricted to Canada, bypassable
|
||||||
|
'url': 'https://api-cbc.cloud.clearleap.com/cloffice/client/web/play/?contentId=3c84472a-1eea-4dee-9267-2655d5055dcf&categoryId=ebc258f5-ee40-4cca-b66b-ba6bd55b7235',
|
||||||
|
'only_matching': True,
|
||||||
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
@ -323,9 +329,10 @@ class CBCWatchIE(CBCWatchBaseIE):
|
|||||||
IE_NAME = 'cbc.ca:watch'
|
IE_NAME = 'cbc.ca:watch'
|
||||||
_VALID_URL = r'https?://watch\.cbc\.ca/(?:[^/]+/)+(?P<id>[0-9a-f-]+)'
|
_VALID_URL = r'https?://watch\.cbc\.ca/(?:[^/]+/)+(?P<id>[0-9a-f-]+)'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
|
# geo-restricted to Canada, bypassable
|
||||||
'url': 'http://watch.cbc.ca/doc-zone/season-6/customer-disservice/38e815a-009e3ab12e4',
|
'url': 'http://watch.cbc.ca/doc-zone/season-6/customer-disservice/38e815a-009e3ab12e4',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '38e815a-009e3ab12e4',
|
'id': '9673749a-5e77-484c-8b62-a1092a6b5168',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Customer (Dis)Service',
|
'title': 'Customer (Dis)Service',
|
||||||
'description': 'md5:8bdd6913a0fe03d4b2a17ebe169c7c87',
|
'description': 'md5:8bdd6913a0fe03d4b2a17ebe169c7c87',
|
||||||
@ -337,8 +344,8 @@ class CBCWatchIE(CBCWatchBaseIE):
|
|||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
'format': 'bestvideo',
|
'format': 'bestvideo',
|
||||||
},
|
},
|
||||||
'skip': 'Geo-restricted to Canada',
|
|
||||||
}, {
|
}, {
|
||||||
|
# geo-restricted to Canada, bypassable
|
||||||
'url': 'http://watch.cbc.ca/arthur/all/1ed4b385-cd84-49cf-95f0-80f004680057',
|
'url': 'http://watch.cbc.ca/arthur/all/1ed4b385-cd84-49cf-95f0-80f004680057',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '1ed4b385-cd84-49cf-95f0-80f004680057',
|
'id': '1ed4b385-cd84-49cf-95f0-80f004680057',
|
||||||
@ -346,7 +353,6 @@ class CBCWatchIE(CBCWatchBaseIE):
|
|||||||
'description': 'Arthur, the sweetest 8-year-old aardvark, and his pals solve all kinds of problems with humour, kindness and teamwork.',
|
'description': 'Arthur, the sweetest 8-year-old aardvark, and his pals solve all kinds of problems with humour, kindness and teamwork.',
|
||||||
},
|
},
|
||||||
'playlist_mincount': 30,
|
'playlist_mincount': 30,
|
||||||
'skip': 'Geo-restricted to Canada',
|
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
|
@ -15,19 +15,23 @@ class CBSNewsIE(CBSIE):
|
|||||||
|
|
||||||
_TESTS = [
|
_TESTS = [
|
||||||
{
|
{
|
||||||
'url': 'http://www.cbsnews.com/news/tesla-and-spacex-elon-musks-industrial-empire/',
|
# 60 minutes
|
||||||
|
'url': 'http://www.cbsnews.com/news/artificial-intelligence-positioned-to-be-a-game-changer/',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'tesla-and-spacex-elon-musks-industrial-empire',
|
'id': '_B6Ga3VJrI4iQNKsir_cdFo9Re_YJHE_',
|
||||||
'ext': 'flv',
|
'ext': 'mp4',
|
||||||
'title': 'Tesla and SpaceX: Elon Musk\'s industrial empire',
|
'title': 'Artificial Intelligence',
|
||||||
'thumbnail': 'http://beta.img.cbsnews.com/i/2014/03/30/60147937-2f53-4565-ad64-1bdd6eb64679/60-0330-pelley-640x360.jpg',
|
'description': 'md5:8818145f9974431e0fb58a1b8d69613c',
|
||||||
'duration': 791,
|
'thumbnail': r're:^https?://.*\.jpg$',
|
||||||
|
'duration': 1606,
|
||||||
|
'uploader': 'CBSI-NEW',
|
||||||
|
'timestamp': 1498431900,
|
||||||
|
'upload_date': '20170625',
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
# rtmp download
|
# m3u8 download
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
},
|
},
|
||||||
'skip': 'Subscribers only',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'url': 'http://www.cbsnews.com/videos/fort-hood-shooting-army-downplays-mental-illness-as-cause-of-attack/',
|
'url': 'http://www.cbsnews.com/videos/fort-hood-shooting-army-downplays-mental-illness-as-cause-of-attack/',
|
||||||
@ -52,6 +56,22 @@ class CBSNewsIE(CBSIE):
|
|||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
# 48 hours
|
||||||
|
'url': 'http://www.cbsnews.com/news/maria-ridulph-murder-will-the-nations-oldest-cold-case-to-go-to-trial-ever-get-solved/',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'QpM5BJjBVEAUFi7ydR9LusS69DPLqPJ1',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Cold as Ice',
|
||||||
|
'description': 'Can a childhood memory of a friend\'s murder solve a 1957 cold case? "48 Hours" correspondent Erin Moriarty has the latest.',
|
||||||
|
'upload_date': '20170604',
|
||||||
|
'timestamp': 1496538000,
|
||||||
|
'uploader': 'CBSI-NEW',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
@ -60,7 +80,7 @@ class CBSNewsIE(CBSIE):
|
|||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
video_info = self._parse_json(self._html_search_regex(
|
video_info = self._parse_json(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)=\'({.+?})\'',
|
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', default='{}'), video_id, fatal=False)
|
webpage, 'video JSON info', default='{}'), video_id, fatal=False)
|
||||||
|
|
||||||
if video_info:
|
if video_info:
|
||||||
|
@ -93,7 +93,7 @@ class CCMAIE(InfoExtractor):
|
|||||||
'description': clean_html(informacio.get('descripcio')),
|
'description': clean_html(informacio.get('descripcio')),
|
||||||
'duration': duration,
|
'duration': duration,
|
||||||
'timestamp': timestamp,
|
'timestamp': timestamp,
|
||||||
'thumnails': thumbnails,
|
'thumbnails': thumbnails,
|
||||||
'subtitles': subtitles,
|
'subtitles': subtitles,
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
}
|
}
|
||||||
|
@ -124,7 +124,7 @@ class CDAIE(InfoExtractor):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def extract_format(page, version):
|
def extract_format(page, version):
|
||||||
json_str = self._search_regex(
|
json_str = self._html_search_regex(
|
||||||
r'player_data=(\\?["\'])(?P<player_data>.+?)\1', page,
|
r'player_data=(\\?["\'])(?P<player_data>.+?)\1', page,
|
||||||
'%s player_json' % version, fatal=False, group='player_data')
|
'%s player_json' % version, fatal=False, group='player_data')
|
||||||
if not json_str:
|
if not json_str:
|
||||||
|
@ -81,6 +81,12 @@ class Channel9IE(InfoExtractor):
|
|||||||
|
|
||||||
_RSS_URL = 'http://channel9.msdn.com/%s/RSS'
|
_RSS_URL = 'http://channel9.msdn.com/%s/RSS'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _extract_urls(webpage):
|
||||||
|
return re.findall(
|
||||||
|
r'<iframe[^>]+src=["\'](https?://channel9\.msdn\.com/(?:[^/]+/)+)player\b',
|
||||||
|
webpage)
|
||||||
|
|
||||||
def _extract_list(self, video_id, rss_url=None):
|
def _extract_list(self, video_id, rss_url=None):
|
||||||
if not rss_url:
|
if not rss_url:
|
||||||
rss_url = self._RSS_URL % video_id
|
rss_url = self._RSS_URL % video_id
|
||||||
|
@ -5,7 +5,7 @@ from ..utils import remove_end
|
|||||||
|
|
||||||
|
|
||||||
class CharlieRoseIE(InfoExtractor):
|
class CharlieRoseIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?charlierose\.com/video(?:s|/player)/(?P<id>\d+)'
|
_VALID_URL = r'https?://(?:www\.)?charlierose\.com/(?:video|episode)(?:s|/player)/(?P<id>\d+)'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'https://charlierose.com/videos/27996',
|
'url': 'https://charlierose.com/videos/27996',
|
||||||
'md5': 'fda41d49e67d4ce7c2411fd2c4702e09',
|
'md5': 'fda41d49e67d4ce7c2411fd2c4702e09',
|
||||||
@ -24,6 +24,9 @@ class CharlieRoseIE(InfoExtractor):
|
|||||||
}, {
|
}, {
|
||||||
'url': 'https://charlierose.com/videos/27996',
|
'url': 'https://charlierose.com/videos/27996',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://charlierose.com/episodes/30887?autoplay=true',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
_PLAYER_BASE = 'https://charlierose.com/video/player/%s'
|
_PLAYER_BASE = 'https://charlierose.com/video/player/%s'
|
||||||
|
@ -5,6 +5,7 @@ import base64
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from .youtube import YoutubeIE
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
clean_html,
|
clean_html,
|
||||||
ExtractorError
|
ExtractorError
|
||||||
@ -70,11 +71,9 @@ class ChilloutzoneIE(InfoExtractor):
|
|||||||
|
|
||||||
# If nativePlatform is None a fallback mechanism is used (i.e. youtube embed)
|
# If nativePlatform is None a fallback mechanism is used (i.e. youtube embed)
|
||||||
if native_platform is None:
|
if native_platform is None:
|
||||||
youtube_url = self._html_search_regex(
|
youtube_url = YoutubeIE._extract_url(webpage)
|
||||||
r'<iframe.* src="((?:https?:)?//(?:[^.]+\.)?youtube\.com/.+?)"',
|
if youtube_url:
|
||||||
webpage, 'fallback video URL', default=None)
|
return self.url_result(youtube_url, ie=YoutubeIE.ie_key())
|
||||||
if youtube_url is not None:
|
|
||||||
return self.url_result(youtube_url, ie='Youtube')
|
|
||||||
|
|
||||||
# Non Fallback: Decide to use native source (e.g. youtube or vimeo) or
|
# Non Fallback: Decide to use native source (e.g. youtube or vimeo) or
|
||||||
# the own CDN
|
# the own CDN
|
||||||
|
@ -9,12 +9,20 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class CinchcastIE(InfoExtractor):
|
class CinchcastIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://player\.cinchcast\.com/.*?assetId=(?P<id>[0-9]+)'
|
_VALID_URL = r'https?://player\.cinchcast\.com/.*?(?:assetId|show_id)=(?P<id>[0-9]+)'
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
|
'url': 'http://player.cinchcast.com/?show_id=5258197&platformId=1&assetType=single',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '5258197',
|
||||||
|
'ext': 'mp3',
|
||||||
|
'title': 'Train Your Brain to Up Your Game with Coach Mandy',
|
||||||
|
'upload_date': '20130816',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
# Actual test is run in generic, look for undergroundwellness
|
# Actual test is run in generic, look for undergroundwellness
|
||||||
'url': 'http://player.cinchcast.com/?platformId=1&assetType=single&assetId=7141703',
|
'url': 'http://player.cinchcast.com/?platformId=1&assetType=single&assetId=7141703',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
}
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
|
72
youtube_dl/extractor/cjsw.py
Normal file
72
youtube_dl/extractor/cjsw.py
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
determine_ext,
|
||||||
|
unescapeHTML,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CJSWIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?cjsw\.com/program/(?P<program>[^/]+)/episode/(?P<id>\d+)'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://cjsw.com/program/freshly-squeezed/episode/20170620',
|
||||||
|
'md5': 'cee14d40f1e9433632c56e3d14977120',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '91d9f016-a2e7-46c5-8dcb-7cbcd7437c41',
|
||||||
|
'ext': 'mp3',
|
||||||
|
'title': 'Freshly Squeezed – Episode June 20, 2017',
|
||||||
|
'description': 'md5:c967d63366c3898a80d0c7b0ff337202',
|
||||||
|
'series': 'Freshly Squeezed',
|
||||||
|
'episode_id': '20170620',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
# no description
|
||||||
|
'url': 'http://cjsw.com/program/road-pops/episode/20170707/',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
program, episode_id = mobj.group('program', 'id')
|
||||||
|
audio_id = '%s/%s' % (program, episode_id)
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, episode_id)
|
||||||
|
|
||||||
|
title = unescapeHTML(self._search_regex(
|
||||||
|
(r'<h1[^>]+class=["\']episode-header__title["\'][^>]*>(?P<title>[^<]+)',
|
||||||
|
r'data-audio-title=(["\'])(?P<title>(?:(?!\1).)+)\1'),
|
||||||
|
webpage, 'title', group='title'))
|
||||||
|
|
||||||
|
audio_url = self._search_regex(
|
||||||
|
r'<button[^>]+data-audio-src=(["\'])(?P<url>(?:(?!\1).)+)\1',
|
||||||
|
webpage, 'audio url', group='url')
|
||||||
|
|
||||||
|
audio_id = self._search_regex(
|
||||||
|
r'/([\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12})\.mp3',
|
||||||
|
audio_url, 'audio id', default=audio_id)
|
||||||
|
|
||||||
|
formats = [{
|
||||||
|
'url': audio_url,
|
||||||
|
'ext': determine_ext(audio_url, 'mp3'),
|
||||||
|
'vcodec': 'none',
|
||||||
|
}]
|
||||||
|
|
||||||
|
description = self._html_search_regex(
|
||||||
|
r'<p>(?P<description>.+?)</p>', webpage, 'description',
|
||||||
|
default=None)
|
||||||
|
series = self._search_regex(
|
||||||
|
r'data-showname=(["\'])(?P<name>(?:(?!\1).)+)\1', webpage,
|
||||||
|
'series', default=program, group='name')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': audio_id,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'formats': formats,
|
||||||
|
'series': series,
|
||||||
|
'episode_id': episode_id,
|
||||||
|
}
|
@ -1,67 +0,0 @@
|
|||||||
# coding: utf-8
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
|
||||||
from ..utils import (
|
|
||||||
int_or_none,
|
|
||||||
unified_strdate,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ClipfishIE(InfoExtractor):
|
|
||||||
_VALID_URL = r'https?://(?:www\.)?clipfish\.de/(?:[^/]+/)+video/(?P<id>[0-9]+)'
|
|
||||||
_TEST = {
|
|
||||||
'url': 'http://www.clipfish.de/special/ugly-americans/video/4343170/s01-e01-ugly-americans-date-in-der-hoelle/',
|
|
||||||
'md5': 'b9a5dc46294154c1193e2d10e0c95693',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '4343170',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': 'S01 E01 - Ugly Americans - Date in der Hölle',
|
|
||||||
'description': 'Mark Lilly arbeitet im Sozialdienst der Stadt New York und soll Immigranten bei ihrer Einbürgerung in die USA zur Seite stehen.',
|
|
||||||
'upload_date': '20161005',
|
|
||||||
'duration': 1291,
|
|
||||||
'view_count': int,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
|
||||||
video_id = self._match_id(url)
|
|
||||||
|
|
||||||
video_info = self._download_json(
|
|
||||||
'http://www.clipfish.de/devapi/id/%s?format=json&apikey=hbbtv' % video_id,
|
|
||||||
video_id)['items'][0]
|
|
||||||
|
|
||||||
formats = []
|
|
||||||
|
|
||||||
m3u8_url = video_info.get('media_videourl_hls')
|
|
||||||
if m3u8_url:
|
|
||||||
formats.append({
|
|
||||||
'url': m3u8_url.replace('de.hls.fra.clipfish.de', 'hls.fra.clipfish.de'),
|
|
||||||
'ext': 'mp4',
|
|
||||||
'format_id': 'hls',
|
|
||||||
})
|
|
||||||
|
|
||||||
mp4_url = video_info.get('media_videourl')
|
|
||||||
if mp4_url:
|
|
||||||
formats.append({
|
|
||||||
'url': mp4_url,
|
|
||||||
'format_id': 'mp4',
|
|
||||||
'width': int_or_none(video_info.get('width')),
|
|
||||||
'height': int_or_none(video_info.get('height')),
|
|
||||||
'tbr': int_or_none(video_info.get('bitrate')),
|
|
||||||
})
|
|
||||||
|
|
||||||
descr = video_info.get('descr')
|
|
||||||
if descr:
|
|
||||||
descr = descr.strip()
|
|
||||||
|
|
||||||
return {
|
|
||||||
'id': video_id,
|
|
||||||
'title': video_info['title'],
|
|
||||||
'description': descr,
|
|
||||||
'formats': formats,
|
|
||||||
'thumbnail': video_info.get('media_content_thumbnail_large') or video_info.get('media_thumbnail'),
|
|
||||||
'duration': int_or_none(video_info.get('media_length')),
|
|
||||||
'upload_date': unified_strdate(video_info.get('pubDate')),
|
|
||||||
'view_count': int_or_none(video_info.get('media_views'))
|
|
||||||
}
|
|
74
youtube_dl/extractor/clippit.py
Normal file
74
youtube_dl/extractor/clippit.py
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
parse_iso8601,
|
||||||
|
qualities,
|
||||||
|
)
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
class ClippitIE(InfoExtractor):
|
||||||
|
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?clippituser\.tv/c/(?P<id>[a-z]+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'https://www.clippituser.tv/c/evmgm',
|
||||||
|
'md5': '963ae7a59a2ec4572ab8bf2f2d2c5f09',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'evmgm',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Bye bye Brutus. #BattleBots - Clippit',
|
||||||
|
'uploader': 'lizllove',
|
||||||
|
'uploader_url': 'https://www.clippituser.tv/p/lizllove',
|
||||||
|
'timestamp': 1472183818,
|
||||||
|
'upload_date': '20160826',
|
||||||
|
'description': 'BattleBots | ABC',
|
||||||
|
'thumbnail': r're:^https?://.*\.jpg$',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
title = self._html_search_regex(r'<title.*>(.+?)</title>', webpage, 'title')
|
||||||
|
|
||||||
|
FORMATS = ('sd', 'hd')
|
||||||
|
quality = qualities(FORMATS)
|
||||||
|
formats = []
|
||||||
|
for format_id in FORMATS:
|
||||||
|
url = self._html_search_regex(r'data-%s-file="(.+?)"' % format_id,
|
||||||
|
webpage, 'url', fatal=False)
|
||||||
|
if not url:
|
||||||
|
continue
|
||||||
|
match = re.search(r'/(?P<height>\d+)\.mp4', url)
|
||||||
|
formats.append({
|
||||||
|
'url': url,
|
||||||
|
'format_id': format_id,
|
||||||
|
'quality': quality(format_id),
|
||||||
|
'height': int(match.group('height')) if match else None,
|
||||||
|
})
|
||||||
|
|
||||||
|
uploader = self._html_search_regex(r'class="username".*>\s+(.+?)\n',
|
||||||
|
webpage, 'uploader', fatal=False)
|
||||||
|
uploader_url = ('https://www.clippituser.tv/p/' + uploader
|
||||||
|
if uploader else None)
|
||||||
|
|
||||||
|
timestamp = self._html_search_regex(r'datetime="(.+?)"',
|
||||||
|
webpage, 'date', fatal=False)
|
||||||
|
thumbnail = self._html_search_regex(r'data-image="(.+?)"',
|
||||||
|
webpage, 'thumbnail', fatal=False)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'formats': formats,
|
||||||
|
'uploader': uploader,
|
||||||
|
'uploader_url': uploader_url,
|
||||||
|
'timestamp': parse_iso8601(timestamp),
|
||||||
|
'description': self._og_search_description(webpage),
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
}
|
@ -30,7 +30,11 @@ class CloudyIE(InfoExtractor):
|
|||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
webpage = self._download_webpage(
|
webpage = self._download_webpage(
|
||||||
'http://www.cloudy.ec/embed.php?id=%s' % video_id, video_id)
|
'https://www.cloudy.ec/embed.php', video_id, query={
|
||||||
|
'id': video_id,
|
||||||
|
'playerPage': 1,
|
||||||
|
'autoplay': 1,
|
||||||
|
})
|
||||||
|
|
||||||
info = self._parse_html5_media_entries(url, webpage, video_id)[0]
|
info = self._parse_html5_media_entries(url, webpage, video_id)[0]
|
||||||
|
|
||||||
|
@ -120,13 +120,16 @@ class ComedyCentralTVIE(MTVServicesInfoExtractor):
|
|||||||
|
|
||||||
|
|
||||||
class ComedyCentralShortnameIE(InfoExtractor):
|
class ComedyCentralShortnameIE(InfoExtractor):
|
||||||
_VALID_URL = r'^:(?P<id>tds|thedailyshow)$'
|
_VALID_URL = r'^:(?P<id>tds|thedailyshow|theopposition)$'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': ':tds',
|
'url': ':tds',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
}, {
|
}, {
|
||||||
'url': ':thedailyshow',
|
'url': ':thedailyshow',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': ':theopposition',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
@ -134,5 +137,6 @@ class ComedyCentralShortnameIE(InfoExtractor):
|
|||||||
shortcut_map = {
|
shortcut_map = {
|
||||||
'tds': 'http://www.cc.com/shows/the-daily-show-with-trevor-noah/full-episodes',
|
'tds': 'http://www.cc.com/shows/the-daily-show-with-trevor-noah/full-episodes',
|
||||||
'thedailyshow': 'http://www.cc.com/shows/the-daily-show-with-trevor-noah/full-episodes',
|
'thedailyshow': 'http://www.cc.com/shows/the-daily-show-with-trevor-noah/full-episodes',
|
||||||
|
'theopposition': 'http://www.cc.com/shows/the-opposition-with-jordan-klepper/full-episodes',
|
||||||
}
|
}
|
||||||
return self.url_result(shortcut_map[video_id])
|
return self.url_result(shortcut_map[video_id])
|
||||||
|
@ -27,8 +27,12 @@ from ..compat import (
|
|||||||
compat_urllib_parse_urlencode,
|
compat_urllib_parse_urlencode,
|
||||||
compat_urllib_request,
|
compat_urllib_request,
|
||||||
compat_urlparse,
|
compat_urlparse,
|
||||||
|
compat_xml_parse_error,
|
||||||
|
)
|
||||||
|
from ..downloader.f4m import (
|
||||||
|
get_base_url,
|
||||||
|
remove_encrypted_media,
|
||||||
)
|
)
|
||||||
from ..downloader.f4m import remove_encrypted_media
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
NO_DEFAULT,
|
NO_DEFAULT,
|
||||||
age_restricted,
|
age_restricted,
|
||||||
@ -588,19 +592,11 @@ class InfoExtractor(object):
|
|||||||
if not encoding:
|
if not encoding:
|
||||||
encoding = self._guess_encoding_from_content(content_type, webpage_bytes)
|
encoding = self._guess_encoding_from_content(content_type, webpage_bytes)
|
||||||
if self._downloader.params.get('dump_intermediate_pages', False):
|
if self._downloader.params.get('dump_intermediate_pages', False):
|
||||||
try:
|
self.to_screen('Dumping request to ' + urlh.geturl())
|
||||||
url = url_or_request.get_full_url()
|
|
||||||
except AttributeError:
|
|
||||||
url = url_or_request
|
|
||||||
self.to_screen('Dumping request to ' + url)
|
|
||||||
dump = base64.b64encode(webpage_bytes).decode('ascii')
|
dump = base64.b64encode(webpage_bytes).decode('ascii')
|
||||||
self._downloader.to_screen(dump)
|
self._downloader.to_screen(dump)
|
||||||
if self._downloader.params.get('write_pages', False):
|
if self._downloader.params.get('write_pages', False):
|
||||||
try:
|
basen = '%s_%s' % (video_id, urlh.geturl())
|
||||||
url = url_or_request.get_full_url()
|
|
||||||
except AttributeError:
|
|
||||||
url = url_or_request
|
|
||||||
basen = '%s_%s' % (video_id, url)
|
|
||||||
if len(basen) > 240:
|
if len(basen) > 240:
|
||||||
h = '___' + hashlib.md5(basen.encode('utf-8')).hexdigest()
|
h = '___' + hashlib.md5(basen.encode('utf-8')).hexdigest()
|
||||||
basen = basen[:240 - len(h)] + h
|
basen = basen[:240 - len(h)] + h
|
||||||
@ -646,15 +642,29 @@ class InfoExtractor(object):
|
|||||||
|
|
||||||
def _download_xml(self, url_or_request, video_id,
|
def _download_xml(self, url_or_request, video_id,
|
||||||
note='Downloading XML', errnote='Unable to download XML',
|
note='Downloading XML', errnote='Unable to download XML',
|
||||||
transform_source=None, fatal=True, encoding=None, data=None, headers={}, query={}):
|
transform_source=None, fatal=True, encoding=None,
|
||||||
|
data=None, headers={}, query={}):
|
||||||
"""Return the xml as an xml.etree.ElementTree.Element"""
|
"""Return the xml as an xml.etree.ElementTree.Element"""
|
||||||
xml_string = self._download_webpage(
|
xml_string = self._download_webpage(
|
||||||
url_or_request, video_id, note, errnote, fatal=fatal, encoding=encoding, data=data, headers=headers, query=query)
|
url_or_request, video_id, note, errnote, fatal=fatal,
|
||||||
|
encoding=encoding, data=data, headers=headers, query=query)
|
||||||
if xml_string is False:
|
if xml_string is False:
|
||||||
return xml_string
|
return xml_string
|
||||||
|
return self._parse_xml(
|
||||||
|
xml_string, video_id, transform_source=transform_source,
|
||||||
|
fatal=fatal)
|
||||||
|
|
||||||
|
def _parse_xml(self, xml_string, video_id, transform_source=None, fatal=True):
|
||||||
if transform_source:
|
if transform_source:
|
||||||
xml_string = transform_source(xml_string)
|
xml_string = transform_source(xml_string)
|
||||||
|
try:
|
||||||
return compat_etree_fromstring(xml_string.encode('utf-8'))
|
return compat_etree_fromstring(xml_string.encode('utf-8'))
|
||||||
|
except compat_xml_parse_error as ve:
|
||||||
|
errmsg = '%s: Failed to parse XML ' % video_id
|
||||||
|
if fatal:
|
||||||
|
raise ExtractorError(errmsg, cause=ve)
|
||||||
|
else:
|
||||||
|
self.report_warning(errmsg + str(ve))
|
||||||
|
|
||||||
def _download_json(self, url_or_request, video_id,
|
def _download_json(self, url_or_request, video_id,
|
||||||
note='Downloading JSON metadata',
|
note='Downloading JSON metadata',
|
||||||
@ -730,12 +740,12 @@ class InfoExtractor(object):
|
|||||||
video_info['title'] = video_title
|
video_info['title'] = video_title
|
||||||
return video_info
|
return video_info
|
||||||
|
|
||||||
def playlist_from_matches(self, matches, video_id, video_title, getter=None, ie=None):
|
def playlist_from_matches(self, matches, playlist_id=None, playlist_title=None, getter=None, ie=None):
|
||||||
urlrs = orderedSet(
|
urls = orderedSet(
|
||||||
self.url_result(self._proto_relative_url(getter(m) if getter else m), ie)
|
self.url_result(self._proto_relative_url(getter(m) if getter else m), ie)
|
||||||
for m in matches)
|
for m in matches)
|
||||||
return self.playlist_result(
|
return self.playlist_result(
|
||||||
urlrs, playlist_id=video_id, playlist_title=video_title)
|
urls, playlist_id=playlist_id, playlist_title=playlist_title)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def playlist_result(entries, playlist_id=None, playlist_title=None, playlist_description=None):
|
def playlist_result(entries, playlist_id=None, playlist_title=None, playlist_description=None):
|
||||||
@ -940,7 +950,8 @@ class InfoExtractor(object):
|
|||||||
|
|
||||||
def _family_friendly_search(self, html):
|
def _family_friendly_search(self, html):
|
||||||
# See http://schema.org/VideoObject
|
# See http://schema.org/VideoObject
|
||||||
family_friendly = self._html_search_meta('isFamilyFriendly', html)
|
family_friendly = self._html_search_meta(
|
||||||
|
'isFamilyFriendly', html, default=None)
|
||||||
|
|
||||||
if not family_friendly:
|
if not family_friendly:
|
||||||
return None
|
return None
|
||||||
@ -1002,17 +1013,17 @@ class InfoExtractor(object):
|
|||||||
item_type = e.get('@type')
|
item_type = e.get('@type')
|
||||||
if expected_type is not None and expected_type != item_type:
|
if expected_type is not None and expected_type != item_type:
|
||||||
return info
|
return info
|
||||||
if item_type == 'TVEpisode':
|
if item_type in ('TVEpisode', 'Episode'):
|
||||||
info.update({
|
info.update({
|
||||||
'episode': unescapeHTML(e.get('name')),
|
'episode': unescapeHTML(e.get('name')),
|
||||||
'episode_number': int_or_none(e.get('episodeNumber')),
|
'episode_number': int_or_none(e.get('episodeNumber')),
|
||||||
'description': unescapeHTML(e.get('description')),
|
'description': unescapeHTML(e.get('description')),
|
||||||
})
|
})
|
||||||
part_of_season = e.get('partOfSeason')
|
part_of_season = e.get('partOfSeason')
|
||||||
if isinstance(part_of_season, dict) and part_of_season.get('@type') == 'TVSeason':
|
if isinstance(part_of_season, dict) and part_of_season.get('@type') in ('TVSeason', 'Season', 'CreativeWorkSeason'):
|
||||||
info['season_number'] = int_or_none(part_of_season.get('seasonNumber'))
|
info['season_number'] = int_or_none(part_of_season.get('seasonNumber'))
|
||||||
part_of_series = e.get('partOfSeries') or e.get('partOfTVSeries')
|
part_of_series = e.get('partOfSeries') or e.get('partOfTVSeries')
|
||||||
if isinstance(part_of_series, dict) and part_of_series.get('@type') == 'TVSeries':
|
if isinstance(part_of_series, dict) and part_of_series.get('@type') in ('TVSeries', 'Series', 'CreativeWorkSeries'):
|
||||||
info['series'] = unescapeHTML(part_of_series.get('name'))
|
info['series'] = unescapeHTML(part_of_series.get('name'))
|
||||||
elif item_type == 'Article':
|
elif item_type == 'Article':
|
||||||
info.update({
|
info.update({
|
||||||
@ -1022,7 +1033,7 @@ class InfoExtractor(object):
|
|||||||
})
|
})
|
||||||
elif item_type == 'VideoObject':
|
elif item_type == 'VideoObject':
|
||||||
extract_video_object(e)
|
extract_video_object(e)
|
||||||
elif item_type == 'WebPage':
|
continue
|
||||||
video = e.get('video')
|
video = e.get('video')
|
||||||
if isinstance(video, dict) and video.get('@type') == 'VideoObject':
|
if isinstance(video, dict) and video.get('@type') == 'VideoObject':
|
||||||
extract_video_object(video)
|
extract_video_object(video)
|
||||||
@ -1223,11 +1234,8 @@ class InfoExtractor(object):
|
|||||||
media_nodes = remove_encrypted_media(media_nodes)
|
media_nodes = remove_encrypted_media(media_nodes)
|
||||||
if not media_nodes:
|
if not media_nodes:
|
||||||
return formats
|
return formats
|
||||||
base_url = xpath_text(
|
|
||||||
manifest, ['{http://ns.adobe.com/f4m/1.0}baseURL', '{http://ns.adobe.com/f4m/2.0}baseURL'],
|
manifest_base_url = get_base_url(manifest)
|
||||||
'base URL', default=None)
|
|
||||||
if base_url:
|
|
||||||
base_url = base_url.strip()
|
|
||||||
|
|
||||||
bootstrap_info = xpath_element(
|
bootstrap_info = xpath_element(
|
||||||
manifest, ['{http://ns.adobe.com/f4m/1.0}bootstrapInfo', '{http://ns.adobe.com/f4m/2.0}bootstrapInfo'],
|
manifest, ['{http://ns.adobe.com/f4m/1.0}bootstrapInfo', '{http://ns.adobe.com/f4m/2.0}bootstrapInfo'],
|
||||||
@ -1259,7 +1267,7 @@ class InfoExtractor(object):
|
|||||||
continue
|
continue
|
||||||
manifest_url = (
|
manifest_url = (
|
||||||
media_url if media_url.startswith('http://') or media_url.startswith('https://')
|
media_url if media_url.startswith('http://') or media_url.startswith('https://')
|
||||||
else ((base_url or '/'.join(manifest_url.split('/')[:-1])) + '/' + media_url))
|
else ((manifest_base_url or '/'.join(manifest_url.split('/')[:-1])) + '/' + media_url))
|
||||||
# If media_url is itself a f4m manifest do the recursive extraction
|
# If media_url is itself a f4m manifest do the recursive extraction
|
||||||
# since bitrates in parent manifest (this one) and media_url manifest
|
# since bitrates in parent manifest (this one) and media_url manifest
|
||||||
# may differ leading to inability to resolve the format by requested
|
# may differ leading to inability to resolve the format by requested
|
||||||
@ -1294,6 +1302,7 @@ class InfoExtractor(object):
|
|||||||
'url': manifest_url,
|
'url': manifest_url,
|
||||||
'manifest_url': manifest_url,
|
'manifest_url': manifest_url,
|
||||||
'ext': 'flv' if bootstrap_info is not None else None,
|
'ext': 'flv' if bootstrap_info is not None else None,
|
||||||
|
'protocol': 'f4m',
|
||||||
'tbr': tbr,
|
'tbr': tbr,
|
||||||
'width': width,
|
'width': width,
|
||||||
'height': height,
|
'height': height,
|
||||||
@ -1339,6 +1348,9 @@ class InfoExtractor(object):
|
|||||||
if '#EXT-X-FAXS-CM:' in m3u8_doc: # Adobe Flash Access
|
if '#EXT-X-FAXS-CM:' in m3u8_doc: # Adobe Flash Access
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
if re.search(r'#EXT-X-SESSION-KEY:.*?URI="skd://', m3u8_doc): # Apple FairPlay
|
||||||
|
return []
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
|
|
||||||
format_url = lambda u: (
|
format_url = lambda u: (
|
||||||
@ -1385,7 +1397,7 @@ class InfoExtractor(object):
|
|||||||
media_url = media.get('URI')
|
media_url = media.get('URI')
|
||||||
if media_url:
|
if media_url:
|
||||||
format_id = []
|
format_id = []
|
||||||
for v in (group_id, name):
|
for v in (m3u8_id, group_id, name):
|
||||||
if v:
|
if v:
|
||||||
format_id.append(v)
|
format_id.append(v)
|
||||||
f = {
|
f = {
|
||||||
@ -1785,7 +1797,7 @@ class InfoExtractor(object):
|
|||||||
ms_info['timescale'] = int(timescale)
|
ms_info['timescale'] = int(timescale)
|
||||||
segment_duration = source.get('duration')
|
segment_duration = source.get('duration')
|
||||||
if segment_duration:
|
if segment_duration:
|
||||||
ms_info['segment_duration'] = int(segment_duration)
|
ms_info['segment_duration'] = float(segment_duration)
|
||||||
|
|
||||||
def extract_Initialization(source):
|
def extract_Initialization(source):
|
||||||
initialization = source.find(_add_ns('Initialization'))
|
initialization = source.find(_add_ns('Initialization'))
|
||||||
@ -1892,19 +1904,23 @@ class InfoExtractor(object):
|
|||||||
'Bandwidth': bandwidth,
|
'Bandwidth': bandwidth,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def location_key(location):
|
||||||
|
return 'url' if re.match(r'^https?://', location) else 'path'
|
||||||
|
|
||||||
if 'segment_urls' not in representation_ms_info and 'media' in representation_ms_info:
|
if 'segment_urls' not in representation_ms_info and 'media' in representation_ms_info:
|
||||||
|
|
||||||
media_template = prepare_template('media', ('Number', 'Bandwidth', 'Time'))
|
media_template = prepare_template('media', ('Number', 'Bandwidth', 'Time'))
|
||||||
|
media_location_key = location_key(media_template)
|
||||||
|
|
||||||
# As per [1, 5.3.9.4.4, Table 16, page 55] $Number$ and $Time$
|
# As per [1, 5.3.9.4.4, Table 16, page 55] $Number$ and $Time$
|
||||||
# can't be used at the same time
|
# can't be used at the same time
|
||||||
if '%(Number' in media_template and 's' not in representation_ms_info:
|
if '%(Number' in media_template and 's' not in representation_ms_info:
|
||||||
segment_duration = None
|
segment_duration = None
|
||||||
if 'total_number' not in representation_ms_info and 'segment_duration':
|
if 'total_number' not in representation_ms_info and 'segment_duration' in representation_ms_info:
|
||||||
segment_duration = float_or_none(representation_ms_info['segment_duration'], representation_ms_info['timescale'])
|
segment_duration = float_or_none(representation_ms_info['segment_duration'], representation_ms_info['timescale'])
|
||||||
representation_ms_info['total_number'] = int(math.ceil(float(period_duration) / segment_duration))
|
representation_ms_info['total_number'] = int(math.ceil(float(period_duration) / segment_duration))
|
||||||
representation_ms_info['fragments'] = [{
|
representation_ms_info['fragments'] = [{
|
||||||
'url': media_template % {
|
media_location_key: media_template % {
|
||||||
'Number': segment_number,
|
'Number': segment_number,
|
||||||
'Bandwidth': bandwidth,
|
'Bandwidth': bandwidth,
|
||||||
},
|
},
|
||||||
@ -1928,7 +1944,7 @@ class InfoExtractor(object):
|
|||||||
'Number': segment_number,
|
'Number': segment_number,
|
||||||
}
|
}
|
||||||
representation_ms_info['fragments'].append({
|
representation_ms_info['fragments'].append({
|
||||||
'url': segment_url,
|
media_location_key: segment_url,
|
||||||
'duration': float_or_none(segment_d, representation_ms_info['timescale']),
|
'duration': float_or_none(segment_d, representation_ms_info['timescale']),
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1952,16 +1968,34 @@ class InfoExtractor(object):
|
|||||||
for s in representation_ms_info['s']:
|
for s in representation_ms_info['s']:
|
||||||
duration = float_or_none(s['d'], timescale)
|
duration = float_or_none(s['d'], timescale)
|
||||||
for r in range(s.get('r', 0) + 1):
|
for r in range(s.get('r', 0) + 1):
|
||||||
|
segment_uri = representation_ms_info['segment_urls'][segment_index]
|
||||||
fragments.append({
|
fragments.append({
|
||||||
'url': representation_ms_info['segment_urls'][segment_index],
|
location_key(segment_uri): segment_uri,
|
||||||
'duration': duration,
|
'duration': duration,
|
||||||
})
|
})
|
||||||
segment_index += 1
|
segment_index += 1
|
||||||
representation_ms_info['fragments'] = fragments
|
representation_ms_info['fragments'] = fragments
|
||||||
|
elif 'segment_urls' in representation_ms_info:
|
||||||
|
# Segment URLs with no SegmentTimeline
|
||||||
|
# Example: https://www.seznam.cz/zpravy/clanek/cesko-zasahne-vitr-o-sile-vichrice-muze-byt-i-zivotu-nebezpecny-39091
|
||||||
|
# https://github.com/rg3/youtube-dl/pull/14844
|
||||||
|
fragments = []
|
||||||
|
segment_duration = float_or_none(
|
||||||
|
representation_ms_info['segment_duration'],
|
||||||
|
representation_ms_info['timescale']) if 'segment_duration' in representation_ms_info else None
|
||||||
|
for segment_url in representation_ms_info['segment_urls']:
|
||||||
|
fragment = {
|
||||||
|
location_key(segment_url): segment_url,
|
||||||
|
}
|
||||||
|
if segment_duration:
|
||||||
|
fragment['duration'] = segment_duration
|
||||||
|
fragments.append(fragment)
|
||||||
|
representation_ms_info['fragments'] = fragments
|
||||||
# NB: MPD manifest may contain direct URLs to unfragmented media.
|
# NB: MPD manifest may contain direct URLs to unfragmented media.
|
||||||
# No fragments key is present in this case.
|
# No fragments key is present in this case.
|
||||||
if 'fragments' in representation_ms_info:
|
if 'fragments' in representation_ms_info:
|
||||||
f.update({
|
f.update({
|
||||||
|
'fragment_base_url': base_url,
|
||||||
'fragments': [],
|
'fragments': [],
|
||||||
'protocol': 'http_dash_segments',
|
'protocol': 'http_dash_segments',
|
||||||
})
|
})
|
||||||
@ -1969,10 +2003,8 @@ class InfoExtractor(object):
|
|||||||
initialization_url = representation_ms_info['initialization_url']
|
initialization_url = representation_ms_info['initialization_url']
|
||||||
if not f.get('url'):
|
if not f.get('url'):
|
||||||
f['url'] = initialization_url
|
f['url'] = initialization_url
|
||||||
f['fragments'].append({'url': initialization_url})
|
f['fragments'].append({location_key(initialization_url): initialization_url})
|
||||||
f['fragments'].extend(representation_ms_info['fragments'])
|
f['fragments'].extend(representation_ms_info['fragments'])
|
||||||
for fragment in f['fragments']:
|
|
||||||
fragment['url'] = urljoin(base_url, fragment['url'])
|
|
||||||
try:
|
try:
|
||||||
existing_format = next(
|
existing_format = next(
|
||||||
fo for fo in formats
|
fo for fo in formats
|
||||||
@ -2110,19 +2142,19 @@ class InfoExtractor(object):
|
|||||||
return f
|
return f
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def _media_formats(src, cur_media_type):
|
def _media_formats(src, cur_media_type, type_info={}):
|
||||||
full_url = absolute_url(src)
|
full_url = absolute_url(src)
|
||||||
ext = determine_ext(full_url)
|
ext = type_info.get('ext') or determine_ext(full_url)
|
||||||
if ext == 'm3u8':
|
if ext == 'm3u8':
|
||||||
is_plain_url = False
|
is_plain_url = False
|
||||||
formats = self._extract_m3u8_formats(
|
formats = self._extract_m3u8_formats(
|
||||||
full_url, video_id, ext='mp4',
|
full_url, video_id, ext='mp4',
|
||||||
entry_protocol=m3u8_entry_protocol, m3u8_id=m3u8_id,
|
entry_protocol=m3u8_entry_protocol, m3u8_id=m3u8_id,
|
||||||
preference=preference)
|
preference=preference, fatal=False)
|
||||||
elif ext == 'mpd':
|
elif ext == 'mpd':
|
||||||
is_plain_url = False
|
is_plain_url = False
|
||||||
formats = self._extract_mpd_formats(
|
formats = self._extract_mpd_formats(
|
||||||
full_url, video_id, mpd_id=mpd_id)
|
full_url, video_id, mpd_id=mpd_id, fatal=False)
|
||||||
else:
|
else:
|
||||||
is_plain_url = True
|
is_plain_url = True
|
||||||
formats = [{
|
formats = [{
|
||||||
@ -2132,15 +2164,18 @@ class InfoExtractor(object):
|
|||||||
return is_plain_url, formats
|
return is_plain_url, formats
|
||||||
|
|
||||||
entries = []
|
entries = []
|
||||||
|
# amp-video and amp-audio are very similar to their HTML5 counterparts
|
||||||
|
# so we wll include them right here (see
|
||||||
|
# https://www.ampproject.org/docs/reference/components/amp-video)
|
||||||
media_tags = [(media_tag, media_type, '')
|
media_tags = [(media_tag, media_type, '')
|
||||||
for media_tag, media_type
|
for media_tag, media_type
|
||||||
in re.findall(r'(?s)(<(video|audio)[^>]*/>)', webpage)]
|
in re.findall(r'(?s)(<(?:amp-)?(video|audio)[^>]*/>)', webpage)]
|
||||||
media_tags.extend(re.findall(
|
media_tags.extend(re.findall(
|
||||||
# We only allow video|audio followed by a whitespace or '>'.
|
# We only allow video|audio followed by a whitespace or '>'.
|
||||||
# Allowing more characters may end up in significant slow down (see
|
# Allowing more characters may end up in significant slow down (see
|
||||||
# https://github.com/rg3/youtube-dl/issues/11979, example URL:
|
# https://github.com/rg3/youtube-dl/issues/11979, example URL:
|
||||||
# http://www.porntrex.com/maps/videositemap.xml).
|
# http://www.porntrex.com/maps/videositemap.xml).
|
||||||
r'(?s)(<(?P<tag>video|audio)(?:\s+[^>]*)?>)(.*?)</(?P=tag)>', webpage))
|
r'(?s)(<(?P<tag>(?:amp-)?(?:video|audio))(?:\s+[^>]*)?>)(.*?)</(?P=tag)>', webpage))
|
||||||
for media_tag, media_type, media_content in media_tags:
|
for media_tag, media_type, media_content in media_tags:
|
||||||
media_info = {
|
media_info = {
|
||||||
'formats': [],
|
'formats': [],
|
||||||
@ -2158,9 +2193,15 @@ class InfoExtractor(object):
|
|||||||
src = source_attributes.get('src')
|
src = source_attributes.get('src')
|
||||||
if not src:
|
if not src:
|
||||||
continue
|
continue
|
||||||
is_plain_url, formats = _media_formats(src, media_type)
|
|
||||||
if is_plain_url:
|
|
||||||
f = parse_content_type(source_attributes.get('type'))
|
f = parse_content_type(source_attributes.get('type'))
|
||||||
|
is_plain_url, formats = _media_formats(src, media_type, f)
|
||||||
|
if is_plain_url:
|
||||||
|
# res attribute is not standard but seen several times
|
||||||
|
# in the wild
|
||||||
|
f.update({
|
||||||
|
'height': int_or_none(source_attributes.get('res')),
|
||||||
|
'format_id': source_attributes.get('label'),
|
||||||
|
})
|
||||||
f.update(formats[0])
|
f.update(formats[0])
|
||||||
media_info['formats'].append(f)
|
media_info['formats'].append(f)
|
||||||
else:
|
else:
|
||||||
@ -2204,27 +2245,35 @@ class InfoExtractor(object):
|
|||||||
return formats
|
return formats
|
||||||
|
|
||||||
def _extract_wowza_formats(self, url, video_id, m3u8_entry_protocol='m3u8_native', skip_protocols=[]):
|
def _extract_wowza_formats(self, url, video_id, m3u8_entry_protocol='m3u8_native', skip_protocols=[]):
|
||||||
|
query = compat_urlparse.urlparse(url).query
|
||||||
url = re.sub(r'/(?:manifest|playlist|jwplayer)\.(?:m3u8|f4m|mpd|smil)', '', url)
|
url = re.sub(r'/(?:manifest|playlist|jwplayer)\.(?:m3u8|f4m|mpd|smil)', '', url)
|
||||||
url_base = self._search_regex(
|
url_base = self._search_regex(
|
||||||
r'(?:(?:https?|rtmp|rtsp):)?(//[^?]+)', url, 'format url')
|
r'(?:(?:https?|rtmp|rtsp):)?(//[^?]+)', url, 'format url')
|
||||||
http_base_url = '%s:%s' % ('http', url_base)
|
http_base_url = '%s:%s' % ('http', url_base)
|
||||||
formats = []
|
formats = []
|
||||||
|
|
||||||
|
def manifest_url(manifest):
|
||||||
|
m_url = '%s/%s' % (http_base_url, manifest)
|
||||||
|
if query:
|
||||||
|
m_url += '?%s' % query
|
||||||
|
return m_url
|
||||||
|
|
||||||
if 'm3u8' not in skip_protocols:
|
if 'm3u8' not in skip_protocols:
|
||||||
formats.extend(self._extract_m3u8_formats(
|
formats.extend(self._extract_m3u8_formats(
|
||||||
http_base_url + '/playlist.m3u8', video_id, 'mp4',
|
manifest_url('playlist.m3u8'), video_id, 'mp4',
|
||||||
m3u8_entry_protocol, m3u8_id='hls', fatal=False))
|
m3u8_entry_protocol, m3u8_id='hls', fatal=False))
|
||||||
if 'f4m' not in skip_protocols:
|
if 'f4m' not in skip_protocols:
|
||||||
formats.extend(self._extract_f4m_formats(
|
formats.extend(self._extract_f4m_formats(
|
||||||
http_base_url + '/manifest.f4m',
|
manifest_url('manifest.f4m'),
|
||||||
video_id, f4m_id='hds', fatal=False))
|
video_id, f4m_id='hds', fatal=False))
|
||||||
if 'dash' not in skip_protocols:
|
if 'dash' not in skip_protocols:
|
||||||
formats.extend(self._extract_mpd_formats(
|
formats.extend(self._extract_mpd_formats(
|
||||||
http_base_url + '/manifest.mpd',
|
manifest_url('manifest.mpd'),
|
||||||
video_id, mpd_id='dash', fatal=False))
|
video_id, mpd_id='dash', fatal=False))
|
||||||
if re.search(r'(?:/smil:|\.smil)', url_base):
|
if re.search(r'(?:/smil:|\.smil)', url_base):
|
||||||
if 'smil' not in skip_protocols:
|
if 'smil' not in skip_protocols:
|
||||||
rtmp_formats = self._extract_smil_formats(
|
rtmp_formats = self._extract_smil_formats(
|
||||||
http_base_url + '/jwplayer.smil',
|
manifest_url('jwplayer.smil'),
|
||||||
video_id, fatal=False)
|
video_id, fatal=False)
|
||||||
for rtmp_format in rtmp_formats:
|
for rtmp_format in rtmp_formats:
|
||||||
rtsp_format = rtmp_format.copy()
|
rtsp_format = rtmp_format.copy()
|
||||||
@ -2293,7 +2342,6 @@ class InfoExtractor(object):
|
|||||||
formats = self._parse_jwplayer_formats(
|
formats = self._parse_jwplayer_formats(
|
||||||
video_data['sources'], video_id=this_video_id, m3u8_id=m3u8_id,
|
video_data['sources'], video_id=this_video_id, m3u8_id=m3u8_id,
|
||||||
mpd_id=mpd_id, rtmp_params=rtmp_params, base_url=base_url)
|
mpd_id=mpd_id, rtmp_params=rtmp_params, base_url=base_url)
|
||||||
self._sort_formats(formats)
|
|
||||||
|
|
||||||
subtitles = {}
|
subtitles = {}
|
||||||
tracks = video_data.get('tracks')
|
tracks = video_data.get('tracks')
|
||||||
@ -2310,16 +2358,25 @@ class InfoExtractor(object):
|
|||||||
'url': self._proto_relative_url(track_url)
|
'url': self._proto_relative_url(track_url)
|
||||||
})
|
})
|
||||||
|
|
||||||
entries.append({
|
entry = {
|
||||||
'id': this_video_id,
|
'id': this_video_id,
|
||||||
'title': video_data['title'] if require_title else video_data.get('title'),
|
'title': unescapeHTML(video_data['title'] if require_title else video_data.get('title')),
|
||||||
'description': video_data.get('description'),
|
'description': video_data.get('description'),
|
||||||
'thumbnail': self._proto_relative_url(video_data.get('image')),
|
'thumbnail': self._proto_relative_url(video_data.get('image')),
|
||||||
'timestamp': int_or_none(video_data.get('pubdate')),
|
'timestamp': int_or_none(video_data.get('pubdate')),
|
||||||
'duration': float_or_none(jwplayer_data.get('duration') or video_data.get('duration')),
|
'duration': float_or_none(jwplayer_data.get('duration') or video_data.get('duration')),
|
||||||
'subtitles': subtitles,
|
'subtitles': subtitles,
|
||||||
'formats': formats,
|
}
|
||||||
|
# https://github.com/jwplayer/jwplayer/blob/master/src/js/utils/validator.js#L32
|
||||||
|
if len(formats) == 1 and re.search(r'^(?:http|//).*(?:youtube\.com|youtu\.be)/.+', formats[0]['url']):
|
||||||
|
entry.update({
|
||||||
|
'_type': 'url_transparent',
|
||||||
|
'url': formats[0]['url'],
|
||||||
})
|
})
|
||||||
|
else:
|
||||||
|
self._sort_formats(formats)
|
||||||
|
entry['formats'] = formats
|
||||||
|
entries.append(entry)
|
||||||
if len(entries) == 1:
|
if len(entries) == 1:
|
||||||
return entries[0]
|
return entries[0]
|
||||||
else:
|
else:
|
||||||
@ -2420,10 +2477,12 @@ class InfoExtractor(object):
|
|||||||
self._downloader.report_warning(msg)
|
self._downloader.report_warning(msg)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def _set_cookie(self, domain, name, value, expire_time=None):
|
def _set_cookie(self, domain, name, value, expire_time=None, port=None,
|
||||||
|
path='/', secure=False, discard=False, rest={}, **kwargs):
|
||||||
cookie = compat_cookiejar.Cookie(
|
cookie = compat_cookiejar.Cookie(
|
||||||
0, name, value, None, None, domain, None,
|
0, name, value, port, port is not None, domain, True,
|
||||||
None, '/', True, False, expire_time, '', None, None, None)
|
domain.startswith('.'), path, True, secure, expire_time,
|
||||||
|
discard, None, None, rest)
|
||||||
self._downloader.cookiejar.set_cookie(cookie)
|
self._downloader.cookiejar.set_cookie(cookie)
|
||||||
|
|
||||||
def _get_cookies(self, url):
|
def _get_cookies(self, url):
|
||||||
|
@ -116,16 +116,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_params(self, webpage):
|
def _extract_video_params(self, webpage, display_id):
|
||||||
query = {}
|
query = self._parse_json(
|
||||||
params = self._search_regex(
|
self._search_regex(
|
||||||
r'(?s)var params = {(.+?)}[;,]', webpage, 'player params', default=None)
|
r'(?s)var\s+params\s*=\s*({.+?})[;,]', webpage, 'player params',
|
||||||
if params:
|
default='{}'),
|
||||||
query.update({
|
display_id, transform_source=js_to_json, fatal=False)
|
||||||
'videoId': self._search_regex(r'videoId: [\'"](.+?)[\'"]', params, 'video id'),
|
if query:
|
||||||
'playerId': self._search_regex(r'playerId: [\'"](.+?)[\'"]', params, 'player id'),
|
query['videoId'] = self._search_regex(
|
||||||
'target': self._search_regex(r'target: [\'"](.+?)[\'"]', params, 'target'),
|
r'(?:data-video-id=|currentVideoId\s*=\s*)["\']([\da-f]+)',
|
||||||
})
|
webpage, 'video id', default=None)
|
||||||
else:
|
else:
|
||||||
params = extract_attributes(self._search_regex(
|
params = extract_attributes(self._search_regex(
|
||||||
r'(<[^>]+data-js="video-player"[^>]+>)',
|
r'(<[^>]+data-js="video-player"[^>]+>)',
|
||||||
@ -141,17 +141,27 @@ class CondeNastIE(InfoExtractor):
|
|||||||
video_id = params['videoId']
|
video_id = params['videoId']
|
||||||
|
|
||||||
video_info = None
|
video_info = None
|
||||||
|
|
||||||
|
# New API path
|
||||||
|
query = params.copy()
|
||||||
|
query['embedType'] = 'inline'
|
||||||
|
info_page = self._download_json(
|
||||||
|
'http://player.cnevids.com/embed-api.json', video_id,
|
||||||
|
'Downloading embed info', fatal=False, query=query)
|
||||||
|
|
||||||
|
# Old fallbacks
|
||||||
|
if not info_page:
|
||||||
if params.get('playerId'):
|
if params.get('playerId'):
|
||||||
info_page = self._download_json(
|
info_page = self._download_json(
|
||||||
'http://player.cnevids.com/player/video.js',
|
'http://player.cnevids.com/player/video.js', video_id,
|
||||||
video_id, 'Downloading video info', fatal=False, query=params)
|
'Downloading video info', fatal=False, query=params)
|
||||||
if info_page:
|
if info_page:
|
||||||
video_info = info_page.get('video')
|
video_info = info_page.get('video')
|
||||||
if not video_info:
|
if not video_info:
|
||||||
info_page = self._download_webpage(
|
info_page = self._download_webpage(
|
||||||
'http://player.cnevids.com/player/loader.js',
|
'http://player.cnevids.com/player/loader.js',
|
||||||
video_id, 'Downloading loader info', query=params)
|
video_id, 'Downloading loader info', query=params)
|
||||||
else:
|
if not video_info:
|
||||||
info_page = self._download_webpage(
|
info_page = self._download_webpage(
|
||||||
'https://player.cnevids.com/inline/video/%s.js' % video_id,
|
'https://player.cnevids.com/inline/video/%s.js' % video_id,
|
||||||
video_id, 'Downloading inline info', query={
|
video_id, 'Downloading inline info', query={
|
||||||
@ -215,7 +225,7 @@ class CondeNastIE(InfoExtractor):
|
|||||||
if url_type == 'series':
|
if url_type == 'series':
|
||||||
return self._extract_series(url, webpage)
|
return self._extract_series(url, webpage)
|
||||||
else:
|
else:
|
||||||
params = self._extract_video_params(webpage)
|
params = self._extract_video_params(webpage, display_id)
|
||||||
info = self._search_json_ld(
|
info = self._search_json_ld(
|
||||||
webpage, display_id, fatal=False)
|
webpage, display_id, fatal=False)
|
||||||
info.update(self._extract_video(params))
|
info.update(self._extract_video(params))
|
||||||
|
@ -3,6 +3,7 @@ from __future__ import unicode_literals
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from .youtube import YoutubeIE
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
parse_iso8601,
|
parse_iso8601,
|
||||||
str_to_int,
|
str_to_int,
|
||||||
@ -41,11 +42,9 @@ class CrackedIE(InfoExtractor):
|
|||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
youtube_url = self._search_regex(
|
youtube_url = YoutubeIE._extract_url(webpage)
|
||||||
r'<iframe[^>]+src="((?:https?:)?//www\.youtube\.com/embed/[^"]+)"',
|
|
||||||
webpage, 'youtube url', default=None)
|
|
||||||
if youtube_url:
|
if youtube_url:
|
||||||
return self.url_result(youtube_url, 'Youtube')
|
return self.url_result(youtube_url, ie=YoutubeIE.ie_key())
|
||||||
|
|
||||||
video_url = self._html_search_regex(
|
video_url = self._html_search_regex(
|
||||||
[r'var\s+CK_vidSrc\s*=\s*"([^"]+)"', r'<video\s+src="([^"]+)"'],
|
[r'var\s+CK_vidSrc\s*=\s*"([^"]+)"', r'<video\s+src="([^"]+)"'],
|
||||||
|
@ -38,11 +38,32 @@ class CrunchyrollBaseIE(InfoExtractor):
|
|||||||
_LOGIN_FORM = 'login_form'
|
_LOGIN_FORM = 'login_form'
|
||||||
_NETRC_MACHINE = 'crunchyroll'
|
_NETRC_MACHINE = 'crunchyroll'
|
||||||
|
|
||||||
|
def _call_rpc_api(self, method, video_id, note=None, data=None):
|
||||||
|
data = data or {}
|
||||||
|
data['req'] = 'RpcApi' + method
|
||||||
|
data = compat_urllib_parse_urlencode(data).encode('utf-8')
|
||||||
|
return self._download_xml(
|
||||||
|
'http://www.crunchyroll.com/xml/',
|
||||||
|
video_id, note, fatal=False, data=data, headers={
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
})
|
||||||
|
|
||||||
def _login(self):
|
def _login(self):
|
||||||
(username, password) = self._get_login_info()
|
(username, password) = self._get_login_info()
|
||||||
if username is None:
|
if username is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
self._download_webpage(
|
||||||
|
'https://www.crunchyroll.com/?a=formhandler',
|
||||||
|
None, 'Logging in', 'Wrong login info',
|
||||||
|
data=urlencode_postdata({
|
||||||
|
'formname': 'RpcApiUser_Login',
|
||||||
|
'next_url': 'https://www.crunchyroll.com/acct/membership',
|
||||||
|
'name': username,
|
||||||
|
'password': password,
|
||||||
|
}))
|
||||||
|
|
||||||
|
'''
|
||||||
login_page = self._download_webpage(
|
login_page = self._download_webpage(
|
||||||
self._LOGIN_URL, None, 'Downloading login page')
|
self._LOGIN_URL, None, 'Downloading login page')
|
||||||
|
|
||||||
@ -86,6 +107,7 @@ class CrunchyrollBaseIE(InfoExtractor):
|
|||||||
raise ExtractorError('Unable to login: %s' % error, expected=True)
|
raise ExtractorError('Unable to login: %s' % error, expected=True)
|
||||||
|
|
||||||
raise ExtractorError('Unable to log in')
|
raise ExtractorError('Unable to log in')
|
||||||
|
'''
|
||||||
|
|
||||||
def _real_initialize(self):
|
def _real_initialize(self):
|
||||||
self._login()
|
self._login()
|
||||||
@ -365,15 +387,19 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
|||||||
def _get_subtitles(self, video_id, webpage):
|
def _get_subtitles(self, video_id, webpage):
|
||||||
subtitles = {}
|
subtitles = {}
|
||||||
for sub_id, sub_name in re.findall(r'\bssid=([0-9]+)"[^>]+?\btitle="([^"]+)', webpage):
|
for sub_id, sub_name in re.findall(r'\bssid=([0-9]+)"[^>]+?\btitle="([^"]+)', webpage):
|
||||||
sub_page = self._download_webpage(
|
sub_doc = self._call_rpc_api(
|
||||||
'http://www.crunchyroll.com/xml/?req=RpcApiSubtitle_GetXml&subtitle_script_id=' + sub_id,
|
'Subtitle_GetXml', video_id,
|
||||||
video_id, note='Downloading subtitles for ' + sub_name)
|
'Downloading subtitles for ' + sub_name, data={
|
||||||
id = self._search_regex(r'id=\'([0-9]+)', sub_page, 'subtitle_id', fatal=False)
|
'subtitle_script_id': sub_id,
|
||||||
iv = self._search_regex(r'<iv>([^<]+)', sub_page, 'subtitle_iv', fatal=False)
|
})
|
||||||
data = self._search_regex(r'<data>([^<]+)', sub_page, 'subtitle_data', fatal=False)
|
if not sub_doc:
|
||||||
if not id or not iv or not data:
|
|
||||||
continue
|
continue
|
||||||
subtitle = self._decrypt_subtitles(data, iv, id).decode('utf-8')
|
sid = sub_doc.get('id')
|
||||||
|
iv = xpath_text(sub_doc, 'iv', 'subtitle iv')
|
||||||
|
data = xpath_text(sub_doc, 'data', 'subtitle data')
|
||||||
|
if not sid or not iv or not data:
|
||||||
|
continue
|
||||||
|
subtitle = self._decrypt_subtitles(data, iv, sid).decode('utf-8')
|
||||||
lang_code = self._search_regex(r'lang_code=["\']([^"\']+)', subtitle, 'subtitle_lang_code', fatal=False)
|
lang_code = self._search_regex(r'lang_code=["\']([^"\']+)', subtitle, 'subtitle_lang_code', fatal=False)
|
||||||
if not lang_code:
|
if not lang_code:
|
||||||
continue
|
continue
|
||||||
@ -444,15 +470,29 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
|||||||
for fmt in available_fmts:
|
for fmt in available_fmts:
|
||||||
stream_quality, stream_format = self._FORMAT_IDS[fmt]
|
stream_quality, stream_format = self._FORMAT_IDS[fmt]
|
||||||
video_format = fmt + 'p'
|
video_format = fmt + 'p'
|
||||||
streamdata_req = sanitized_Request(
|
stream_infos = []
|
||||||
'http://www.crunchyroll.com/xml/?req=RpcApiVideoPlayer_GetStandardConfig&media_id=%s&video_format=%s&video_quality=%s'
|
streamdata = self._call_rpc_api(
|
||||||
% (video_id, stream_format, stream_quality),
|
'VideoPlayer_GetStandardConfig', video_id,
|
||||||
compat_urllib_parse_urlencode({'current_page': url}).encode('utf-8'))
|
'Downloading media info for %s' % video_format, data={
|
||||||
streamdata_req.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
'media_id': video_id,
|
||||||
streamdata = self._download_xml(
|
'video_format': stream_format,
|
||||||
streamdata_req, video_id,
|
'video_quality': stream_quality,
|
||||||
note='Downloading media info for %s' % video_format)
|
'current_page': url,
|
||||||
|
})
|
||||||
|
if streamdata:
|
||||||
stream_info = streamdata.find('./{default}preload/stream_info')
|
stream_info = streamdata.find('./{default}preload/stream_info')
|
||||||
|
if stream_info:
|
||||||
|
stream_infos.append(stream_info)
|
||||||
|
stream_info = self._call_rpc_api(
|
||||||
|
'VideoEncode_GetStreamInfo', video_id,
|
||||||
|
'Downloading stream info for %s' % video_format, data={
|
||||||
|
'media_id': video_id,
|
||||||
|
'video_format': stream_format,
|
||||||
|
'video_encode_quality': stream_quality,
|
||||||
|
})
|
||||||
|
if stream_info:
|
||||||
|
stream_infos.append(stream_info)
|
||||||
|
for stream_info in stream_infos:
|
||||||
video_encode_id = xpath_text(stream_info, './video_encode_id')
|
video_encode_id = xpath_text(stream_info, './video_encode_id')
|
||||||
if video_encode_id in video_encode_ids:
|
if video_encode_id in video_encode_ids:
|
||||||
continue
|
continue
|
||||||
@ -473,7 +513,6 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
|||||||
metadata = stream_info.find('./metadata')
|
metadata = stream_info.find('./metadata')
|
||||||
format_info = {
|
format_info = {
|
||||||
'format': video_format,
|
'format': video_format,
|
||||||
'format_id': video_format,
|
|
||||||
'height': int_or_none(xpath_text(metadata, './height')),
|
'height': int_or_none(xpath_text(metadata, './height')),
|
||||||
'width': int_or_none(xpath_text(metadata, './width')),
|
'width': int_or_none(xpath_text(metadata, './width')),
|
||||||
}
|
}
|
||||||
@ -486,23 +525,24 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
|||||||
path='%s/%s' % (remove_end(parsed_video_url.path, '/'), video_file.split(':')[-1])))
|
path='%s/%s' % (remove_end(parsed_video_url.path, '/'), video_file.split(':')[-1])))
|
||||||
if self._is_valid_url(direct_video_url, video_id, video_format):
|
if self._is_valid_url(direct_video_url, video_id, video_format):
|
||||||
format_info.update({
|
format_info.update({
|
||||||
|
'format_id': 'http-' + video_format,
|
||||||
'url': direct_video_url,
|
'url': direct_video_url,
|
||||||
})
|
})
|
||||||
formats.append(format_info)
|
formats.append(format_info)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
format_info.update({
|
format_info.update({
|
||||||
|
'format_id': 'rtmp-' + video_format,
|
||||||
'url': video_url,
|
'url': video_url,
|
||||||
'play_path': video_file,
|
'play_path': video_file,
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
})
|
})
|
||||||
formats.append(format_info)
|
formats.append(format_info)
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats, ('height', 'width', 'tbr', 'fps'))
|
||||||
|
|
||||||
metadata = self._download_xml(
|
metadata = self._call_rpc_api(
|
||||||
'http://www.crunchyroll.com/xml', video_id,
|
'VideoPlayer_GetMediaMetadata', video_id,
|
||||||
note='Downloading media info', query={
|
note='Downloading media info', data={
|
||||||
'req': 'RpcApiVideoPlayer_GetMediaMetadata',
|
|
||||||
'media_id': video_id,
|
'media_id': video_id,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -510,7 +550,7 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
|||||||
|
|
||||||
# webpage provide more accurate data than series_title from XML
|
# webpage provide more accurate data than series_title from XML
|
||||||
series = self._html_search_regex(
|
series = self._html_search_regex(
|
||||||
r'id=["\']showmedia_about_episode_num[^>]+>\s*<a[^>]+>([^<]+)',
|
r'(?s)<h\d[^>]+\bid=["\']showmedia_about_episode_num[^>]+>(.+?)</h\d',
|
||||||
webpage, 'series', fatal=False)
|
webpage, 'series', fatal=False)
|
||||||
season = xpath_text(metadata, 'series_title')
|
season = xpath_text(metadata, 'series_title')
|
||||||
|
|
||||||
@ -518,7 +558,7 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
|||||||
episode_number = int_or_none(xpath_text(metadata, 'episode_number'))
|
episode_number = int_or_none(xpath_text(metadata, 'episode_number'))
|
||||||
|
|
||||||
season_number = int_or_none(self._search_regex(
|
season_number = int_or_none(self._search_regex(
|
||||||
r'(?s)<h4[^>]+id=["\']showmedia_about_episode_num[^>]+>.+?</h4>\s*<h4>\s*Season (\d+)',
|
r'(?s)<h\d[^>]+id=["\']showmedia_about_episode_num[^>]+>.+?</h\d>\s*<h4>\s*Season (\d+)',
|
||||||
webpage, 'season number', default=None))
|
webpage, 'season number', default=None))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import compat_str
|
from ..compat import compat_str
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
@ -12,8 +14,8 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class DailyMailIE(InfoExtractor):
|
class DailyMailIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?dailymail\.co\.uk/video/[^/]+/video-(?P<id>[0-9]+)'
|
_VALID_URL = r'https?://(?:www\.)?dailymail\.co\.uk/(?:video/[^/]+/video-|embed/video/)(?P<id>[0-9]+)'
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
'url': 'http://www.dailymail.co.uk/video/tvshowbiz/video-1295863/The-Mountain-appears-sparkling-water-ad-Heavy-Bubbles.html',
|
'url': 'http://www.dailymail.co.uk/video/tvshowbiz/video-1295863/The-Mountain-appears-sparkling-water-ad-Heavy-Bubbles.html',
|
||||||
'md5': 'f6129624562251f628296c3a9ffde124',
|
'md5': 'f6129624562251f628296c3a9ffde124',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@ -22,7 +24,16 @@ class DailyMailIE(InfoExtractor):
|
|||||||
'title': 'The Mountain appears in sparkling water ad for \'Heavy Bubbles\'',
|
'title': 'The Mountain appears in sparkling water ad for \'Heavy Bubbles\'',
|
||||||
'description': 'md5:a93d74b6da172dd5dc4d973e0b766a84',
|
'description': 'md5:a93d74b6da172dd5dc4d973e0b766a84',
|
||||||
}
|
}
|
||||||
}
|
}, {
|
||||||
|
'url': 'http://www.dailymail.co.uk/embed/video/1295863.html',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _extract_urls(webpage):
|
||||||
|
return re.findall(
|
||||||
|
r'<iframe\b[^>]+\bsrc=["\'](?P<url>(?:https?:)?//(?:www\.)?dailymail\.co\.uk/embed/video/\d+\.html)',
|
||||||
|
webpage)
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
|
@ -147,7 +147,7 @@ class DailymotionIE(DailymotionBaseInfoExtractor):
|
|||||||
view_count_str = self._search_regex(
|
view_count_str = self._search_regex(
|
||||||
(r'<meta[^>]+itemprop="interactionCount"[^>]+content="UserPlays:([\s\d,.]+)"',
|
(r'<meta[^>]+itemprop="interactionCount"[^>]+content="UserPlays:([\s\d,.]+)"',
|
||||||
r'video_views_count[^>]+>\s+([\s\d\,.]+)'),
|
r'video_views_count[^>]+>\s+([\s\d\,.]+)'),
|
||||||
webpage, 'view count', fatal=False)
|
webpage, 'view count', default=None)
|
||||||
if view_count_str:
|
if view_count_str:
|
||||||
view_count_str = re.sub(r'\s', '', view_count_str)
|
view_count_str = re.sub(r'\s', '', view_count_str)
|
||||||
view_count = str_to_int(view_count_str)
|
view_count = str_to_int(view_count_str)
|
||||||
@ -159,7 +159,9 @@ class DailymotionIE(DailymotionBaseInfoExtractor):
|
|||||||
[r'buildPlayer\(({.+?})\);\n', # See https://github.com/rg3/youtube-dl/issues/7826
|
[r'buildPlayer\(({.+?})\);\n', # See https://github.com/rg3/youtube-dl/issues/7826
|
||||||
r'playerV5\s*=\s*dmp\.create\([^,]+?,\s*({.+?})\);',
|
r'playerV5\s*=\s*dmp\.create\([^,]+?,\s*({.+?})\);',
|
||||||
r'buildPlayer\(({.+?})\);',
|
r'buildPlayer\(({.+?})\);',
|
||||||
r'var\s+config\s*=\s*({.+?});'],
|
r'var\s+config\s*=\s*({.+?});',
|
||||||
|
# New layout regex (see https://github.com/rg3/youtube-dl/issues/13580)
|
||||||
|
r'__PLAYER_CONFIG__\s*=\s*({.+?});'],
|
||||||
webpage, 'player v5', default=None)
|
webpage, 'player v5', default=None)
|
||||||
if player_v5:
|
if player_v5:
|
||||||
player = self._parse_json(player_v5, video_id)
|
player = self._parse_json(player_v5, video_id)
|
||||||
@ -233,7 +235,7 @@ class DailymotionIE(DailymotionBaseInfoExtractor):
|
|||||||
|
|
||||||
# vevo embed
|
# vevo embed
|
||||||
vevo_id = self._search_regex(
|
vevo_id = self._search_regex(
|
||||||
r'<link rel="video_src" href="[^"]*?vevo.com[^"]*?video=(?P<id>[\w]*)',
|
r'<link rel="video_src" href="[^"]*?vevo\.com[^"]*?video=(?P<id>[\w]*)',
|
||||||
webpage, 'vevo embed', default=None)
|
webpage, 'vevo embed', default=None)
|
||||||
if vevo_id:
|
if vevo_id:
|
||||||
return self.url_result('vevo:%s' % vevo_id, 'Vevo')
|
return self.url_result('vevo:%s' % vevo_id, 'Vevo')
|
||||||
@ -323,7 +325,7 @@ class DailymotionIE(DailymotionBaseInfoExtractor):
|
|||||||
|
|
||||||
class DailymotionPlaylistIE(DailymotionBaseInfoExtractor):
|
class DailymotionPlaylistIE(DailymotionBaseInfoExtractor):
|
||||||
IE_NAME = 'dailymotion:playlist'
|
IE_NAME = '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'(?s)<div class="pages[^"]*">.*?<a\s+class="[^"]*?icon-arrow_right[^"]*?"'
|
_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'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
|
@ -13,33 +13,30 @@ from ..aes import (
|
|||||||
from ..utils import (
|
from ..utils import (
|
||||||
bytes_to_intlist,
|
bytes_to_intlist,
|
||||||
bytes_to_long,
|
bytes_to_long,
|
||||||
clean_html,
|
extract_attributes,
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
intlist_to_bytes,
|
intlist_to_bytes,
|
||||||
get_element_by_id,
|
|
||||||
js_to_json,
|
js_to_json,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
long_to_bytes,
|
long_to_bytes,
|
||||||
pkcs1pad,
|
pkcs1pad,
|
||||||
remove_end,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class DaisukiIE(InfoExtractor):
|
class DaisukiMottoIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?daisuki\.net/[^/]+/[^/]+/[^/]+/watch\.[^.]+\.(?P<id>\d+)\.html'
|
_VALID_URL = r'https?://motto\.daisuki\.net/framewatch/embed/[^/]+/(?P<id>[0-9a-zA-Z]{3})'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.daisuki.net/tw/en/anime/watch.TheIdolMasterCG.11213.html',
|
'url': 'http://motto.daisuki.net/framewatch/embed/embedDRAGONBALLSUPERUniverseSurvivalsaga/V2e/760/428',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '11213',
|
'id': 'V2e',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': '#01 Who is in the pumpkin carriage? - THE IDOLM@STER CINDERELLA GIRLS',
|
'title': '#117 SHOWDOWN OF LOVE! ANDROIDS VS UNIVERSE 2!!',
|
||||||
'subtitles': {
|
'subtitles': {
|
||||||
'mul': [{
|
'mul': [{
|
||||||
'ext': 'ttml',
|
'ext': 'ttml',
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
'creator': 'BANDAI NAMCO Entertainment',
|
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
'skip_download': True, # AES-encrypted HLS stream
|
'skip_download': True, # AES-encrypted HLS stream
|
||||||
@ -73,7 +70,9 @@ class DaisukiIE(InfoExtractor):
|
|||||||
|
|
||||||
n, e = self._RSA_KEY
|
n, e = self._RSA_KEY
|
||||||
encrypted_aeskey = long_to_bytes(pow(bytes_to_long(padded_aeskey), e, n))
|
encrypted_aeskey = long_to_bytes(pow(bytes_to_long(padded_aeskey), e, n))
|
||||||
init_data = self._download_json('http://www.daisuki.net/bin/bgn/init', video_id, query={
|
init_data = self._download_json(
|
||||||
|
'http://motto.daisuki.net/fastAPI/bgn/init/',
|
||||||
|
video_id, query={
|
||||||
's': flashvars.get('s', ''),
|
's': flashvars.get('s', ''),
|
||||||
'c': flashvars.get('ss3_prm', ''),
|
'c': flashvars.get('ss3_prm', ''),
|
||||||
'e': url,
|
'e': url,
|
||||||
@ -98,14 +97,11 @@ class DaisukiIE(InfoExtractor):
|
|||||||
aes_key, iv)).decode('utf-8').rstrip('\0'),
|
aes_key, iv)).decode('utf-8').rstrip('\0'),
|
||||||
video_id)
|
video_id)
|
||||||
|
|
||||||
|
title = rtn['title_str']
|
||||||
|
|
||||||
formats = self._extract_m3u8_formats(
|
formats = self._extract_m3u8_formats(
|
||||||
rtn['play_url'], video_id, ext='mp4', entry_protocol='m3u8_native')
|
rtn['play_url'], video_id, ext='mp4', entry_protocol='m3u8_native')
|
||||||
|
|
||||||
title = remove_end(self._og_search_title(webpage), ' - DAISUKI')
|
|
||||||
|
|
||||||
creator = self._html_search_regex(
|
|
||||||
r'Creator\s*:\s*([^<]+)', webpage, 'creator', fatal=False)
|
|
||||||
|
|
||||||
subtitles = {}
|
subtitles = {}
|
||||||
caption_url = rtn.get('caption_url')
|
caption_url = rtn.get('caption_url')
|
||||||
if caption_url:
|
if caption_url:
|
||||||
@ -120,21 +116,18 @@ class DaisukiIE(InfoExtractor):
|
|||||||
'title': title,
|
'title': title,
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
'subtitles': subtitles,
|
'subtitles': subtitles,
|
||||||
'creator': creator,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class DaisukiPlaylistIE(InfoExtractor):
|
class DaisukiMottoPlaylistIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)daisuki\.net/[^/]+/[^/]+/[^/]+/detail\.(?P<id>[a-zA-Z0-9]+)\.html'
|
_VALID_URL = r'https?://motto\.daisuki\.net/(?P<id>information)/'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.daisuki.net/tw/en/anime/detail.TheIdolMasterCG.html',
|
'url': 'http://motto.daisuki.net/information/',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'TheIdolMasterCG',
|
'title': 'DRAGON BALL SUPER',
|
||||||
'title': 'THE IDOLM@STER CINDERELLA GIRLS',
|
|
||||||
'description': 'md5:0f2c028a9339f7a2c7fbf839edc5c5d8',
|
|
||||||
},
|
},
|
||||||
'playlist_count': 26,
|
'playlist_mincount': 117,
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
@ -142,18 +135,19 @@ class DaisukiPlaylistIE(InfoExtractor):
|
|||||||
|
|
||||||
webpage = self._download_webpage(url, playlist_id)
|
webpage = self._download_webpage(url, playlist_id)
|
||||||
|
|
||||||
episode_pattern = r'''(?sx)
|
entries = []
|
||||||
<img[^>]+delay="[^"]+/(\d+)/movie\.jpg".+?
|
for li in re.findall(r'(<li[^>]+?data-product_id="[a-zA-Z0-9]{3}"[^>]+>)', webpage):
|
||||||
<p[^>]+class=".*?\bepisodeNumber\b.*?">(?:<a[^>]+>)?([^<]+)'''
|
attr = extract_attributes(li)
|
||||||
entries = [{
|
ad_id = attr.get('data-ad_id')
|
||||||
|
product_id = attr.get('data-product_id')
|
||||||
|
if ad_id and product_id:
|
||||||
|
episode_id = attr.get('data-chapter')
|
||||||
|
entries.append({
|
||||||
'_type': 'url_transparent',
|
'_type': 'url_transparent',
|
||||||
'url': url.replace('detail', 'watch').replace('.html', '.' + movie_id + '.html'),
|
'url': 'http://motto.daisuki.net/framewatch/embed/%s/%s/760/428' % (ad_id, product_id),
|
||||||
'episode_id': episode_id,
|
'episode_id': episode_id,
|
||||||
'episode_number': int_or_none(episode_id),
|
'episode_number': int_or_none(episode_id),
|
||||||
} for movie_id, episode_id in re.findall(episode_pattern, webpage)]
|
'ie_key': 'DaisukiMotto',
|
||||||
|
})
|
||||||
|
|
||||||
playlist_title = remove_end(
|
return self.playlist_result(entries, playlist_title='DRAGON BALL SUPER')
|
||||||
self._og_search_title(webpage, fatal=False), ' - Anime - DAISUKI')
|
|
||||||
playlist_description = clean_html(get_element_by_id('synopsisTxt', webpage))
|
|
||||||
|
|
||||||
return self.playlist_result(entries, playlist_id, playlist_title, playlist_description)
|
|
||||||
|
@ -2,53 +2,85 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import unified_strdate
|
from ..compat import compat_str
|
||||||
|
from ..utils import (
|
||||||
|
float_or_none,
|
||||||
|
unified_strdate,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class DctpTvIE(InfoExtractor):
|
class DctpTvIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?dctp\.tv/(#/)?filme/(?P<id>.+?)/$'
|
_VALID_URL = r'https?://(?:www\.)?dctp\.tv/(?:#/)?filme/(?P<id>[^/?#&]+)'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.dctp.tv/filme/videoinstallation-fuer-eine-kaufhausfassade/',
|
'url': 'http://www.dctp.tv/filme/videoinstallation-fuer-eine-kaufhausfassade/',
|
||||||
'md5': '174dd4a8a6225cf5655952f969cfbe24',
|
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '95eaa4f33dad413aa17b4ee613cccc6c',
|
'id': '95eaa4f33dad413aa17b4ee613cccc6c',
|
||||||
'display_id': 'videoinstallation-fuer-eine-kaufhausfassade',
|
'display_id': 'videoinstallation-fuer-eine-kaufhausfassade',
|
||||||
'ext': 'mp4',
|
'ext': 'flv',
|
||||||
'title': 'Videoinstallation für eine Kaufhausfassade',
|
'title': 'Videoinstallation für eine Kaufhausfassade',
|
||||||
'description': 'Kurzfilm',
|
'description': 'Kurzfilm',
|
||||||
'upload_date': '20110407',
|
'upload_date': '20110407',
|
||||||
'thumbnail': r're:^https?://.*\.jpg$',
|
'thumbnail': r're:^https?://.*\.jpg$',
|
||||||
|
'duration': 71.24,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# rtmp download
|
||||||
|
'skip_download': True,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
display_id = self._match_id(url)
|
||||||
webpage = self._download_webpage(url, video_id)
|
|
||||||
|
|
||||||
object_id = self._html_search_meta('DC.identifier', webpage)
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
|
||||||
servers_json = self._download_json(
|
video_id = self._html_search_meta(
|
||||||
'http://www.dctp.tv/elastic_streaming_client/get_streaming_server/',
|
'DC.identifier', webpage, 'video id',
|
||||||
video_id, note='Downloading server list')
|
default=None) or self._search_regex(
|
||||||
server = servers_json[0]['server']
|
r'id=["\']uuid[^>]+>([^<]+)<', webpage, 'video id')
|
||||||
m3u8_path = self._search_regex(
|
|
||||||
r'\'([^\'"]+/playlist\.m3u8)"', webpage, 'm3u8 path')
|
|
||||||
formats = self._extract_m3u8_formats(
|
|
||||||
'http://%s%s' % (server, m3u8_path), video_id, ext='mp4',
|
|
||||||
entry_protocol='m3u8_native')
|
|
||||||
|
|
||||||
title = self._og_search_title(webpage)
|
title = self._og_search_title(webpage)
|
||||||
|
|
||||||
|
servers = self._download_json(
|
||||||
|
'http://www.dctp.tv/streaming_servers/', display_id,
|
||||||
|
note='Downloading server list', fatal=False)
|
||||||
|
|
||||||
|
if servers:
|
||||||
|
endpoint = next(
|
||||||
|
server['endpoint']
|
||||||
|
for server in servers
|
||||||
|
if isinstance(server.get('endpoint'), compat_str) and
|
||||||
|
'cloudfront' in server['endpoint'])
|
||||||
|
else:
|
||||||
|
endpoint = 'rtmpe://s2pqqn4u96e4j8.cloudfront.net/cfx/st/'
|
||||||
|
|
||||||
|
app = self._search_regex(
|
||||||
|
r'^rtmpe?://[^/]+/(?P<app>.*)$', endpoint, 'app')
|
||||||
|
|
||||||
|
formats = [{
|
||||||
|
'url': endpoint,
|
||||||
|
'app': app,
|
||||||
|
'play_path': 'mp4:%s_dctp_0500_4x3.m4v' % video_id,
|
||||||
|
'page_url': url,
|
||||||
|
'player_url': 'http://svm-prod-dctptv-static.s3.amazonaws.com/dctptv-relaunch2012-109.swf',
|
||||||
|
'ext': 'flv',
|
||||||
|
}]
|
||||||
|
|
||||||
description = self._html_search_meta('DC.description', webpage)
|
description = self._html_search_meta('DC.description', webpage)
|
||||||
upload_date = unified_strdate(
|
upload_date = unified_strdate(
|
||||||
self._html_search_meta('DC.date.created', webpage))
|
self._html_search_meta('DC.date.created', webpage))
|
||||||
thumbnail = self._og_search_thumbnail(webpage)
|
thumbnail = self._og_search_thumbnail(webpage)
|
||||||
|
duration = float_or_none(self._search_regex(
|
||||||
|
r'id=["\']duration_in_ms[^+]>(\d+)', webpage, 'duration',
|
||||||
|
default=None), scale=1000)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': object_id,
|
'id': video_id,
|
||||||
'title': title,
|
'title': title,
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
'display_id': video_id,
|
'display_id': display_id,
|
||||||
'description': description,
|
'description': description,
|
||||||
'upload_date': upload_date,
|
'upload_date': upload_date,
|
||||||
'thumbnail': thumbnail,
|
'thumbnail': thumbnail,
|
||||||
|
'duration': duration,
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ class DeezerPlaylistIE(InfoExtractor):
|
|||||||
'id': '176747451',
|
'id': '176747451',
|
||||||
'title': 'Best!',
|
'title': 'Best!',
|
||||||
'uploader': 'Anonymous',
|
'uploader': 'Anonymous',
|
||||||
'thumbnail': r're:^https?://cdn-images.deezer.com/images/cover/.*\.jpg$',
|
'thumbnail': r're:^https?://cdn-images\.deezer\.com/images/cover/.*\.jpg$',
|
||||||
},
|
},
|
||||||
'playlist_count': 30,
|
'playlist_count': 30,
|
||||||
'skip': 'Only available in .de',
|
'skip': 'Only available in .de',
|
||||||
|
@ -13,7 +13,7 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class DigitallySpeakingIE(InfoExtractor):
|
class DigitallySpeakingIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:evt\.dispeak|events\.digitallyspeaking)\.com/(?:[^/]+/)+xml/(?P<id>[^.]+)\.xml'
|
_VALID_URL = r'https?://(?:s?evt\.dispeak|events\.digitallyspeaking)\.com/(?:[^/]+/)+xml/(?P<id>[^.]+)\.xml'
|
||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
# From http://gdcvault.com/play/1023460/Tenacious-Design-and-The-Interface
|
# From http://gdcvault.com/play/1023460/Tenacious-Design-and-The-Interface
|
||||||
@ -28,6 +28,10 @@ class DigitallySpeakingIE(InfoExtractor):
|
|||||||
# From http://www.gdcvault.com/play/1014631/Classic-Game-Postmortem-PAC
|
# From http://www.gdcvault.com/play/1014631/Classic-Game-Postmortem-PAC
|
||||||
'url': 'http://events.digitallyspeaking.com/gdc/sf11/xml/12396_1299111843500GMPX.xml',
|
'url': 'http://events.digitallyspeaking.com/gdc/sf11/xml/12396_1299111843500GMPX.xml',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
# From http://www.gdcvault.com/play/1013700/Advanced-Material
|
||||||
|
'url': 'http://sevt.dispeak.com/ubm/gdc/eur10/xml/11256_1282118587281VNIT.xml',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _parse_mp4(self, metadata):
|
def _parse_mp4(self, metadata):
|
||||||
|
@ -7,16 +7,18 @@ import time
|
|||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import (
|
||||||
compat_urlparse,
|
|
||||||
compat_HTTPError,
|
compat_HTTPError,
|
||||||
|
compat_str,
|
||||||
|
compat_urlparse,
|
||||||
)
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
USER_AGENTS,
|
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
unified_strdate,
|
|
||||||
remove_end,
|
remove_end,
|
||||||
|
try_get,
|
||||||
|
unified_strdate,
|
||||||
update_url_query,
|
update_url_query,
|
||||||
|
USER_AGENTS,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -183,12 +185,28 @@ class DPlayItIE(InfoExtractor):
|
|||||||
|
|
||||||
webpage = self._download_webpage(url, display_id)
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
|
||||||
info_url = self._search_regex(
|
|
||||||
r'url\s*:\s*["\']((?:https?:)?//[^/]+/playback/videoPlaybackInfo/\d+)',
|
|
||||||
webpage, 'video id')
|
|
||||||
|
|
||||||
title = remove_end(self._og_search_title(webpage), ' | Dplay')
|
title = remove_end(self._og_search_title(webpage), ' | Dplay')
|
||||||
|
|
||||||
|
video_id = None
|
||||||
|
|
||||||
|
info = self._search_regex(
|
||||||
|
r'playback_json\s*:\s*JSON\.parse\s*\(\s*("(?:\\.|[^"\\])+?")',
|
||||||
|
webpage, 'playback JSON', default=None)
|
||||||
|
if info:
|
||||||
|
for _ in range(2):
|
||||||
|
info = self._parse_json(info, display_id, fatal=False)
|
||||||
|
if not info:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
video_id = try_get(info, lambda x: x['data']['id'])
|
||||||
|
|
||||||
|
if not info:
|
||||||
|
info_url = self._search_regex(
|
||||||
|
r'url\s*[:=]\s*["\']((?:https?:)?//[^/]+/playback/videoPlaybackInfo/\d+)',
|
||||||
|
webpage, 'info url')
|
||||||
|
|
||||||
|
video_id = info_url.rpartition('/')[-1]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
info = self._download_json(
|
info = self._download_json(
|
||||||
info_url, display_id, headers={
|
info_url, display_id, headers={
|
||||||
@ -230,7 +248,7 @@ class DPlayItIE(InfoExtractor):
|
|||||||
season_number = episode_number = upload_date = None
|
season_number = episode_number = upload_date = None
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': info_url.rpartition('/')[-1],
|
'id': compat_str(video_id or display_id),
|
||||||
'display_id': display_id,
|
'display_id': display_id,
|
||||||
'title': title,
|
'title': title,
|
||||||
'description': self._og_search_description(webpage),
|
'description': self._og_search_description(webpage),
|
||||||
|
@ -12,6 +12,7 @@ from ..utils import (
|
|||||||
ExtractorError,
|
ExtractorError,
|
||||||
clean_html,
|
clean_html,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
|
remove_end,
|
||||||
sanitized_Request,
|
sanitized_Request,
|
||||||
urlencode_postdata
|
urlencode_postdata
|
||||||
)
|
)
|
||||||
@ -53,12 +54,12 @@ class DramaFeverBaseIE(AMPIE):
|
|||||||
request = sanitized_Request(
|
request = sanitized_Request(
|
||||||
self._LOGIN_URL, urlencode_postdata(login_form))
|
self._LOGIN_URL, urlencode_postdata(login_form))
|
||||||
response = self._download_webpage(
|
response = self._download_webpage(
|
||||||
request, None, 'Logging in as %s' % username)
|
request, None, 'Logging in')
|
||||||
|
|
||||||
if all(logout_pattern not in response
|
if all(logout_pattern not in response
|
||||||
for logout_pattern in ['href="/accounts/logout/"', '>Log out<']):
|
for logout_pattern in ['href="/accounts/logout/"', '>Log out<']):
|
||||||
error = self._html_search_regex(
|
error = self._html_search_regex(
|
||||||
r'(?s)class="hidden-xs prompt"[^>]*>(.+?)<',
|
r'(?s)<h\d[^>]+\bclass="hidden-xs prompt"[^>]*>(.+?)</h\d',
|
||||||
response, 'error message', default=None)
|
response, 'error message', default=None)
|
||||||
if error:
|
if error:
|
||||||
raise ExtractorError('Unable to login: %s' % error, expected=True)
|
raise ExtractorError('Unable to login: %s' % error, expected=True)
|
||||||
@ -72,15 +73,15 @@ class DramaFeverIE(DramaFeverBaseIE):
|
|||||||
'url': 'http://www.dramafever.com/drama/4512/1/Cooking_with_Shin/',
|
'url': 'http://www.dramafever.com/drama/4512/1/Cooking_with_Shin/',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '4512.1',
|
'id': '4512.1',
|
||||||
'ext': 'mp4',
|
'ext': 'flv',
|
||||||
'title': 'Cooking with Shin 4512.1',
|
'title': 'Cooking with Shin',
|
||||||
'description': 'md5:a8eec7942e1664a6896fcd5e1287bfd0',
|
'description': 'md5:a8eec7942e1664a6896fcd5e1287bfd0',
|
||||||
'episode': 'Episode 1',
|
'episode': 'Episode 1',
|
||||||
'episode_number': 1,
|
'episode_number': 1,
|
||||||
'thumbnail': r're:^https?://.*\.jpg',
|
'thumbnail': r're:^https?://.*\.jpg',
|
||||||
'timestamp': 1404336058,
|
'timestamp': 1404336058,
|
||||||
'upload_date': '20140702',
|
'upload_date': '20140702',
|
||||||
'duration': 343,
|
'duration': 344,
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
# m3u8 download
|
# m3u8 download
|
||||||
@ -90,15 +91,15 @@ class DramaFeverIE(DramaFeverBaseIE):
|
|||||||
'url': 'http://www.dramafever.com/drama/4826/4/Mnet_Asian_Music_Awards_2015/?ap=1',
|
'url': 'http://www.dramafever.com/drama/4826/4/Mnet_Asian_Music_Awards_2015/?ap=1',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '4826.4',
|
'id': '4826.4',
|
||||||
'ext': 'mp4',
|
'ext': 'flv',
|
||||||
'title': 'Mnet Asian Music Awards 2015 4826.4',
|
'title': 'Mnet Asian Music Awards 2015',
|
||||||
'description': 'md5:3ff2ee8fedaef86e076791c909cf2e91',
|
'description': 'md5:3ff2ee8fedaef86e076791c909cf2e91',
|
||||||
'episode': 'Mnet Asian Music Awards 2015 - Part 3',
|
'episode': 'Mnet Asian Music Awards 2015 - Part 3',
|
||||||
'episode_number': 4,
|
'episode_number': 4,
|
||||||
'thumbnail': r're:^https?://.*\.jpg',
|
'thumbnail': r're:^https?://.*\.jpg',
|
||||||
'timestamp': 1450213200,
|
'timestamp': 1450213200,
|
||||||
'upload_date': '20151215',
|
'upload_date': '20151215',
|
||||||
'duration': 5602,
|
'duration': 5359,
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
# m3u8 download
|
# m3u8 download
|
||||||
@ -122,6 +123,10 @@ class DramaFeverIE(DramaFeverBaseIE):
|
|||||||
countries=self._GEO_COUNTRIES)
|
countries=self._GEO_COUNTRIES)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
# title is postfixed with video id for some reason, removing
|
||||||
|
if info.get('title'):
|
||||||
|
info['title'] = remove_end(info['title'], video_id).strip()
|
||||||
|
|
||||||
series_id, episode_number = video_id.split('.')
|
series_id, episode_number = video_id.split('.')
|
||||||
episode_info = self._download_json(
|
episode_info = self._download_json(
|
||||||
# We only need a single episode info, so restricting page size to one episode
|
# We only need a single episode info, so restricting page size to one episode
|
||||||
|
@ -10,7 +10,7 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class DrTuberIE(InfoExtractor):
|
class DrTuberIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?drtuber\.com/(?:video|embed)/(?P<id>\d+)(?:/(?P<display_id>[\w-]+))?'
|
_VALID_URL = r'https?://(?:(?:www|m)\.)?drtuber\.com/(?:video|embed)/(?P<id>\d+)(?:/(?P<display_id>[\w-]+))?'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.drtuber.com/video/1740434/hot-perky-blonde-naked-golf',
|
'url': 'http://www.drtuber.com/video/1740434/hot-perky-blonde-naked-golf',
|
||||||
'md5': '93e680cf2536ad0dfb7e74d94a89facd',
|
'md5': '93e680cf2536ad0dfb7e74d94a89facd',
|
||||||
@ -28,6 +28,9 @@ class DrTuberIE(InfoExtractor):
|
|||||||
}, {
|
}, {
|
||||||
'url': 'http://www.drtuber.com/embed/489939',
|
'url': 'http://www.drtuber.com/embed/489939',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://m.drtuber.com/video/3893529/lingerie-blowjob-from-beautiful-teen',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -118,7 +118,7 @@ class DRTVIE(InfoExtractor):
|
|||||||
if target == 'HDS':
|
if target == 'HDS':
|
||||||
f4m_formats = self._extract_f4m_formats(
|
f4m_formats = self._extract_f4m_formats(
|
||||||
uri + '?hdcore=3.3.0&plugin=aasp-3.3.0.99.43',
|
uri + '?hdcore=3.3.0&plugin=aasp-3.3.0.99.43',
|
||||||
video_id, preference, f4m_id=format_id)
|
video_id, preference, f4m_id=format_id, fatal=False)
|
||||||
if kind == 'AudioResource':
|
if kind == 'AudioResource':
|
||||||
for f in f4m_formats:
|
for f in f4m_formats:
|
||||||
f['vcodec'] = 'none'
|
f['vcodec'] = 'none'
|
||||||
@ -126,7 +126,8 @@ class DRTVIE(InfoExtractor):
|
|||||||
elif target == 'HLS':
|
elif target == 'HLS':
|
||||||
formats.extend(self._extract_m3u8_formats(
|
formats.extend(self._extract_m3u8_formats(
|
||||||
uri, video_id, 'mp4', entry_protocol='m3u8_native',
|
uri, video_id, 'mp4', entry_protocol='m3u8_native',
|
||||||
preference=preference, m3u8_id=format_id))
|
preference=preference, m3u8_id=format_id,
|
||||||
|
fatal=False))
|
||||||
else:
|
else:
|
||||||
bitrate = link.get('Bitrate')
|
bitrate = link.get('Bitrate')
|
||||||
if bitrate:
|
if bitrate:
|
||||||
@ -137,6 +138,7 @@ class DRTVIE(InfoExtractor):
|
|||||||
'tbr': int_or_none(bitrate),
|
'tbr': int_or_none(bitrate),
|
||||||
'ext': link.get('FileFormat'),
|
'ext': link.get('FileFormat'),
|
||||||
'vcodec': 'none' if kind == 'AudioResource' else None,
|
'vcodec': 'none' if kind == 'AudioResource' else None,
|
||||||
|
'preference': preference,
|
||||||
})
|
})
|
||||||
subtitles_list = asset.get('SubtitlesList')
|
subtitles_list = asset.get('SubtitlesList')
|
||||||
if isinstance(subtitles_list, list):
|
if isinstance(subtitles_list, list):
|
||||||
|
@ -11,6 +11,7 @@ from ..compat import (
|
|||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
|
unsmuggle_url,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -50,6 +51,10 @@ class EaglePlatformIE(InfoExtractor):
|
|||||||
'view_count': int,
|
'view_count': int,
|
||||||
},
|
},
|
||||||
'skip': 'Georestricted',
|
'skip': 'Georestricted',
|
||||||
|
}, {
|
||||||
|
# referrer protected video (https://tvrain.ru/lite/teleshow/kak_vse_nachinalos/namin-418921/)
|
||||||
|
'url': 'eagleplatform:tvrainru.media.eagleplatform.com:582306',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -60,16 +65,40 @@ class EaglePlatformIE(InfoExtractor):
|
|||||||
webpage)
|
webpage)
|
||||||
if mobj is not None:
|
if mobj is not None:
|
||||||
return mobj.group('url')
|
return mobj.group('url')
|
||||||
# Basic usage embedding (see http://dultonmedia.github.io/eplayer/)
|
PLAYER_JS_RE = r'''
|
||||||
|
<script[^>]+
|
||||||
|
src=(?P<qjs>["\'])(?:https?:)?//(?P<host>(?:(?!(?P=qjs)).)+\.media\.eagleplatform\.com)/player/player\.js(?P=qjs)
|
||||||
|
.+?
|
||||||
|
'''
|
||||||
|
# "Basic usage" embedding (see http://dultonmedia.github.io/eplayer/)
|
||||||
mobj = re.search(
|
mobj = re.search(
|
||||||
r'''(?xs)
|
r'''(?xs)
|
||||||
<script[^>]+
|
%s
|
||||||
src=(?P<q1>["\'])(?:https?:)?//(?P<host>.+?\.media\.eagleplatform\.com)/player/player\.js(?P=q1)
|
|
||||||
.+?
|
|
||||||
<div[^>]+
|
<div[^>]+
|
||||||
class=(?P<q2>["\'])eagleplayer(?P=q2)[^>]+
|
class=(?P<qclass>["\'])eagleplayer(?P=qclass)[^>]+
|
||||||
data-id=["\'](?P<id>\d+)
|
data-id=["\'](?P<id>\d+)
|
||||||
''', webpage)
|
''' % PLAYER_JS_RE, webpage)
|
||||||
|
if mobj is not None:
|
||||||
|
return 'eagleplatform:%(host)s:%(id)s' % mobj.groupdict()
|
||||||
|
# Generalization of "Javascript code usage", "Combined usage" and
|
||||||
|
# "Usage without attaching to DOM" embeddings (see
|
||||||
|
# http://dultonmedia.github.io/eplayer/)
|
||||||
|
mobj = re.search(
|
||||||
|
r'''(?xs)
|
||||||
|
%s
|
||||||
|
<script>
|
||||||
|
.+?
|
||||||
|
new\s+EaglePlayer\(
|
||||||
|
(?:[^,]+\s*,\s*)?
|
||||||
|
{
|
||||||
|
.+?
|
||||||
|
\bid\s*:\s*["\']?(?P<id>\d+)
|
||||||
|
.+?
|
||||||
|
}
|
||||||
|
\s*\)
|
||||||
|
.+?
|
||||||
|
</script>
|
||||||
|
''' % PLAYER_JS_RE, webpage)
|
||||||
if mobj is not None:
|
if mobj is not None:
|
||||||
return 'eagleplatform:%(host)s:%(id)s' % mobj.groupdict()
|
return 'eagleplatform:%(host)s:%(id)s' % mobj.groupdict()
|
||||||
|
|
||||||
@ -79,9 +108,10 @@ class EaglePlatformIE(InfoExtractor):
|
|||||||
if status != 200:
|
if status != 200:
|
||||||
raise ExtractorError(' '.join(response['errors']), expected=True)
|
raise ExtractorError(' '.join(response['errors']), expected=True)
|
||||||
|
|
||||||
def _download_json(self, url_or_request, video_id, note='Downloading JSON metadata', *args, **kwargs):
|
def _download_json(self, url_or_request, video_id, *args, **kwargs):
|
||||||
try:
|
try:
|
||||||
response = super(EaglePlatformIE, self)._download_json(url_or_request, video_id, note)
|
response = super(EaglePlatformIE, self)._download_json(
|
||||||
|
url_or_request, video_id, *args, **kwargs)
|
||||||
except ExtractorError as ee:
|
except ExtractorError as ee:
|
||||||
if isinstance(ee.cause, compat_HTTPError):
|
if isinstance(ee.cause, compat_HTTPError):
|
||||||
response = self._parse_json(ee.cause.read().decode('utf-8'), video_id)
|
response = self._parse_json(ee.cause.read().decode('utf-8'), video_id)
|
||||||
@ -93,11 +123,24 @@ class EaglePlatformIE(InfoExtractor):
|
|||||||
return self._download_json(url_or_request, video_id, note)['data'][0]
|
return self._download_json(url_or_request, video_id, note)['data'][0]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
|
url, smuggled_data = unsmuggle_url(url, {})
|
||||||
|
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
host, video_id = mobj.group('custom_host') or mobj.group('host'), mobj.group('id')
|
host, video_id = mobj.group('custom_host') or mobj.group('host'), mobj.group('id')
|
||||||
|
|
||||||
|
headers = {}
|
||||||
|
query = {
|
||||||
|
'id': video_id,
|
||||||
|
}
|
||||||
|
|
||||||
|
referrer = smuggled_data.get('referrer')
|
||||||
|
if referrer:
|
||||||
|
headers['Referer'] = referrer
|
||||||
|
query['referrer'] = referrer
|
||||||
|
|
||||||
player_data = self._download_json(
|
player_data = self._download_json(
|
||||||
'http://%s/api/player_data?id=%s' % (host, video_id), video_id)
|
'http://%s/api/player_data' % host, video_id,
|
||||||
|
headers=headers, query=query)
|
||||||
|
|
||||||
media = player_data['data']['playlist']['viewports'][0]['medialist'][0]
|
media = player_data['data']['playlist']['viewports'][0]['medialist'][0]
|
||||||
|
|
||||||
|
@ -1,20 +1,25 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from ..compat import compat_str
|
||||||
|
from ..utils import (
|
||||||
|
determine_ext,
|
||||||
|
int_or_none,
|
||||||
|
try_get,
|
||||||
|
unified_timestamp,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class EggheadCourseIE(InfoExtractor):
|
class EggheadCourseIE(InfoExtractor):
|
||||||
IE_DESC = 'egghead.io course'
|
IE_DESC = 'egghead.io course'
|
||||||
IE_NAME = 'egghead:course'
|
IE_NAME = 'egghead:course'
|
||||||
_VALID_URL = r'https://egghead\.io/courses/(?P<id>[a-zA-Z_0-9-]+)'
|
_VALID_URL = r'https://egghead\.io/courses/(?P<id>[^/?#&]+)'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'https://egghead.io/courses/professor-frisby-introduces-composable-functional-javascript',
|
'url': 'https://egghead.io/courses/professor-frisby-introduces-composable-functional-javascript',
|
||||||
'playlist_count': 29,
|
'playlist_count': 29,
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'professor-frisby-introduces-composable-functional-javascript',
|
'id': '72',
|
||||||
'title': 'Professor Frisby Introduces Composable Functional JavaScript',
|
'title': 'Professor Frisby Introduces Composable Functional JavaScript',
|
||||||
'description': 're:(?s)^This course teaches the ubiquitous.*You\'ll start composing functionality before you know it.$',
|
'description': 're:(?s)^This course teaches the ubiquitous.*You\'ll start composing functionality before you know it.$',
|
||||||
},
|
},
|
||||||
@ -22,18 +27,101 @@ class EggheadCourseIE(InfoExtractor):
|
|||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
playlist_id = self._match_id(url)
|
playlist_id = self._match_id(url)
|
||||||
webpage = self._download_webpage(url, playlist_id)
|
|
||||||
|
|
||||||
title = self._html_search_regex(r'<h1 class="title">([^<]+)</h1>', webpage, 'title')
|
lessons = self._download_json(
|
||||||
ul = self._search_regex(r'(?s)<ul class="series-lessons-list">(.*?)</ul>', webpage, 'session list')
|
'https://egghead.io/api/v1/series/%s/lessons' % playlist_id,
|
||||||
|
playlist_id, 'Downloading course lessons JSON')
|
||||||
|
|
||||||
found = re.findall(r'(?s)<a class="[^"]*"\s*href="([^"]+)">\s*<li class="item', ul)
|
entries = []
|
||||||
entries = [self.url_result(m) for m in found]
|
for lesson in lessons:
|
||||||
|
lesson_url = lesson.get('http_url')
|
||||||
|
if not lesson_url or not isinstance(lesson_url, compat_str):
|
||||||
|
continue
|
||||||
|
lesson_id = lesson.get('id')
|
||||||
|
if lesson_id:
|
||||||
|
lesson_id = compat_str(lesson_id)
|
||||||
|
entries.append(self.url_result(
|
||||||
|
lesson_url, ie=EggheadLessonIE.ie_key(), video_id=lesson_id))
|
||||||
|
|
||||||
|
course = self._download_json(
|
||||||
|
'https://egghead.io/api/v1/series/%s' % playlist_id,
|
||||||
|
playlist_id, 'Downloading course JSON', fatal=False) or {}
|
||||||
|
|
||||||
|
playlist_id = course.get('id')
|
||||||
|
if playlist_id:
|
||||||
|
playlist_id = compat_str(playlist_id)
|
||||||
|
|
||||||
|
return self.playlist_result(
|
||||||
|
entries, playlist_id, course.get('title'),
|
||||||
|
course.get('description'))
|
||||||
|
|
||||||
|
|
||||||
|
class EggheadLessonIE(InfoExtractor):
|
||||||
|
IE_DESC = 'egghead.io lesson'
|
||||||
|
IE_NAME = 'egghead:lesson'
|
||||||
|
_VALID_URL = r'https://egghead\.io/(?:api/v1/)?lessons/(?P<id>[^/?#&]+)'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://egghead.io/lessons/javascript-linear-data-flow-with-container-style-types-box',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '1196',
|
||||||
|
'display_id': 'javascript-linear-data-flow-with-container-style-types-box',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Create linear data flow with container style types (Box)',
|
||||||
|
'description': 'md5:9aa2cdb6f9878ed4c39ec09e85a8150e',
|
||||||
|
'thumbnail': r're:^https?:.*\.jpg$',
|
||||||
|
'timestamp': 1481296768,
|
||||||
|
'upload_date': '20161209',
|
||||||
|
'duration': 304,
|
||||||
|
'view_count': 0,
|
||||||
|
'tags': ['javascript', 'free'],
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True,
|
||||||
|
'format': 'bestvideo',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'https://egghead.io/api/v1/lessons/react-add-redux-to-a-react-application',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
display_id = self._match_id(url)
|
||||||
|
|
||||||
|
lesson = self._download_json(
|
||||||
|
'https://egghead.io/api/v1/lessons/%s' % display_id, display_id)
|
||||||
|
|
||||||
|
lesson_id = compat_str(lesson['id'])
|
||||||
|
title = lesson['title']
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
for _, format_url in lesson['media_urls'].items():
|
||||||
|
if not format_url or not isinstance(format_url, compat_str):
|
||||||
|
continue
|
||||||
|
ext = determine_ext(format_url)
|
||||||
|
if ext == 'm3u8':
|
||||||
|
formats.extend(self._extract_m3u8_formats(
|
||||||
|
format_url, lesson_id, 'mp4', entry_protocol='m3u8',
|
||||||
|
m3u8_id='hls', fatal=False))
|
||||||
|
elif ext == 'mpd':
|
||||||
|
formats.extend(self._extract_mpd_formats(
|
||||||
|
format_url, lesson_id, mpd_id='dash', fatal=False))
|
||||||
|
else:
|
||||||
|
formats.append({
|
||||||
|
'url': format_url,
|
||||||
|
})
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'_type': 'playlist',
|
'id': lesson_id,
|
||||||
'id': playlist_id,
|
'display_id': display_id,
|
||||||
'title': title,
|
'title': title,
|
||||||
'description': self._og_search_description(webpage),
|
'description': lesson.get('summary'),
|
||||||
'entries': entries,
|
'thumbnail': lesson.get('thumb_nail'),
|
||||||
|
'timestamp': unified_timestamp(lesson.get('published_at')),
|
||||||
|
'duration': int_or_none(lesson.get('duration')),
|
||||||
|
'view_count': int_or_none(lesson.get('plays_count')),
|
||||||
|
'tags': try_get(lesson, lambda x: x['tag_list'], list),
|
||||||
|
'series': try_get(
|
||||||
|
lesson, lambda x: x['series']['title'], compat_str),
|
||||||
|
'formats': formats,
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class EpornerIE(InfoExtractor):
|
class EpornerIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?eporner\.com/hd-porn/(?P<id>\w+)(?:/(?P<display_id>[\w-]+))?'
|
_VALID_URL = r'https?://(?:www\.)?eporner\.com/(?:hd-porn|embed)/(?P<id>\w+)(?:/(?P<display_id>[\w-]+))?'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.eporner.com/hd-porn/95008/Infamous-Tiffany-Teen-Strip-Tease-Video/',
|
'url': 'http://www.eporner.com/hd-porn/95008/Infamous-Tiffany-Teen-Strip-Tease-Video/',
|
||||||
'md5': '39d486f046212d8e1b911c52ab4691f8',
|
'md5': '39d486f046212d8e1b911c52ab4691f8',
|
||||||
@ -35,6 +35,9 @@ class EpornerIE(InfoExtractor):
|
|||||||
}, {
|
}, {
|
||||||
'url': 'http://www.eporner.com/hd-porn/3YRUtzMcWn0',
|
'url': 'http://www.eporner.com/hd-porn/3YRUtzMcWn0',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.eporner.com/hd-porn/3YRUtzMcWn0',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
|
@ -10,7 +10,25 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class ESPNIE(InfoExtractor):
|
class ESPNIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:espn\.go|(?:www\.)?espn)\.com/video/clip(?:\?.*?\bid=|/_/id/)(?P<id>\d+)'
|
_VALID_URL = r'''(?x)
|
||||||
|
https?://
|
||||||
|
(?:
|
||||||
|
(?:(?:\w+\.)+)?espn\.go|
|
||||||
|
(?:www\.)?espn
|
||||||
|
)\.com/
|
||||||
|
(?:
|
||||||
|
(?:
|
||||||
|
video/clip|
|
||||||
|
watch/player
|
||||||
|
)
|
||||||
|
(?:
|
||||||
|
\?.*?\bid=|
|
||||||
|
/_/id/
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(?P<id>\d+)
|
||||||
|
'''
|
||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://espn.go.com/video/clip?id=10365079',
|
'url': 'http://espn.go.com/video/clip?id=10365079',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@ -25,20 +43,34 @@ class ESPNIE(InfoExtractor):
|
|||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
# intl video, from http://www.espnfc.us/video/mls-highlights/150/video/2743663/must-see-moments-best-of-the-mls-season
|
'url': 'https://broadband.espn.go.com/video/clip?id=18910086',
|
||||||
'url': 'http://espn.go.com/video/clip?id=2743663',
|
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '2743663',
|
'id': '18910086',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Must-See Moments: Best of the MLS season',
|
'title': 'Kyrie spins around defender for two',
|
||||||
'description': 'md5:4c2d7232beaea572632bec41004f0aeb',
|
'description': 'md5:2b0f5bae9616d26fba8808350f0d2b9b',
|
||||||
'timestamp': 1449446454,
|
'timestamp': 1489539155,
|
||||||
'upload_date': '20151207',
|
'upload_date': '20170315',
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
},
|
},
|
||||||
'expected_warnings': ['Unable to download f4m manifest'],
|
'expected_warnings': ['Unable to download f4m manifest'],
|
||||||
|
}, {
|
||||||
|
'url': 'http://nonredline.sports.espn.go.com/video/clip?id=19744672',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://cdn.espn.go.com/video/clip/_/id/19771774',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.espn.com/watch/player?id=19141491',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.espn.com/watch/player?bucketId=257&id=19505875',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.espn.com/watch/player/_/id/19141491',
|
||||||
|
'only_matching': True,
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.espn.com/video/clip?id=10365079',
|
'url': 'http://www.espn.com/video/clip?id=10365079',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
@ -31,20 +31,19 @@ from .aenetworks import (
|
|||||||
AENetworksIE,
|
AENetworksIE,
|
||||||
HistoryTopicIE,
|
HistoryTopicIE,
|
||||||
)
|
)
|
||||||
from .afreecatv import (
|
from .afreecatv import AfreecaTVIE
|
||||||
AfreecaTVIE,
|
|
||||||
AfreecaTVGlobalIE,
|
|
||||||
)
|
|
||||||
from .airmozilla import AirMozillaIE
|
from .airmozilla import AirMozillaIE
|
||||||
from .aljazeera import AlJazeeraIE
|
from .aljazeera import AlJazeeraIE
|
||||||
from .alphaporno import AlphaPornoIE
|
from .alphaporno import AlphaPornoIE
|
||||||
from .amcnetworks import AMCNetworksIE
|
from .amcnetworks import AMCNetworksIE
|
||||||
|
from .americastestkitchen import AmericasTestKitchenIE
|
||||||
from .animeondemand import AnimeOnDemandIE
|
from .animeondemand import AnimeOnDemandIE
|
||||||
from .anitube import AnitubeIE
|
from .anitube import AnitubeIE
|
||||||
from .anvato import AnvatoIE
|
from .anvato import AnvatoIE
|
||||||
from .anysex import AnySexIE
|
from .anysex import AnySexIE
|
||||||
from .aol import AolIE
|
from .aol import AolIE
|
||||||
from .allocine import AllocineIE
|
from .allocine import AllocineIE
|
||||||
|
from .aliexpress import AliExpressLiveIE
|
||||||
from .aparat import AparatIE
|
from .aparat import AparatIE
|
||||||
from .appleconnect import AppleConnectIE
|
from .appleconnect import AppleConnectIE
|
||||||
from .appletrailers import (
|
from .appletrailers import (
|
||||||
@ -128,7 +127,10 @@ from .bloomberg import BloombergIE
|
|||||||
from .bokecc import BokeCCIE
|
from .bokecc import BokeCCIE
|
||||||
from .bostonglobe import BostonGlobeIE
|
from .bostonglobe import BostonGlobeIE
|
||||||
from .bpb import BpbIE
|
from .bpb import BpbIE
|
||||||
from .br import BRIE
|
from .br import (
|
||||||
|
BRIE,
|
||||||
|
BRMediathekIE,
|
||||||
|
)
|
||||||
from .bravotv import BravoTVIE
|
from .bravotv import BravoTVIE
|
||||||
from .breakcom import BreakIE
|
from .breakcom import BreakIE
|
||||||
from .brightcove import (
|
from .brightcove import (
|
||||||
@ -148,7 +150,11 @@ from .camdemy import (
|
|||||||
from .camwithher import CamWithHerIE
|
from .camwithher import CamWithHerIE
|
||||||
from .canalplus import CanalplusIE
|
from .canalplus import CanalplusIE
|
||||||
from .canalc2 import Canalc2IE
|
from .canalc2 import Canalc2IE
|
||||||
from .canvas import CanvasIE
|
from .canvas import (
|
||||||
|
CanvasIE,
|
||||||
|
CanvasEenIE,
|
||||||
|
VrtNUIE,
|
||||||
|
)
|
||||||
from .carambatv import (
|
from .carambatv import (
|
||||||
CarambaTVIE,
|
CarambaTVIE,
|
||||||
CarambaTVPageIE,
|
CarambaTVPageIE,
|
||||||
@ -185,8 +191,9 @@ from .chirbit import (
|
|||||||
ChirbitProfileIE,
|
ChirbitProfileIE,
|
||||||
)
|
)
|
||||||
from .cinchcast import CinchcastIE
|
from .cinchcast import CinchcastIE
|
||||||
from .clipfish import ClipfishIE
|
from .cjsw import CJSWIE
|
||||||
from .cliphunter import CliphunterIE
|
from .cliphunter import CliphunterIE
|
||||||
|
from .clippit import ClippitIE
|
||||||
from .cliprs import ClipRsIE
|
from .cliprs import ClipRsIE
|
||||||
from .clipsyndicate import ClipsyndicateIE
|
from .clipsyndicate import ClipsyndicateIE
|
||||||
from .closertotruth import CloserToTruthIE
|
from .closertotruth import CloserToTruthIE
|
||||||
@ -242,8 +249,8 @@ from .dailymotion import (
|
|||||||
DailymotionCloudIE,
|
DailymotionCloudIE,
|
||||||
)
|
)
|
||||||
from .daisuki import (
|
from .daisuki import (
|
||||||
DaisukiIE,
|
DaisukiMottoIE,
|
||||||
DaisukiPlaylistIE,
|
DaisukiMottoPlaylistIE,
|
||||||
)
|
)
|
||||||
from .daum import (
|
from .daum import (
|
||||||
DaumIE,
|
DaumIE,
|
||||||
@ -297,7 +304,10 @@ from .dw import (
|
|||||||
from .eagleplatform import EaglePlatformIE
|
from .eagleplatform import EaglePlatformIE
|
||||||
from .ebaumsworld import EbaumsWorldIE
|
from .ebaumsworld import EbaumsWorldIE
|
||||||
from .echomsk import EchoMskIE
|
from .echomsk import EchoMskIE
|
||||||
from .egghead import EggheadCourseIE
|
from .egghead import (
|
||||||
|
EggheadCourseIE,
|
||||||
|
EggheadLessonIE,
|
||||||
|
)
|
||||||
from .ehow import EHowIE
|
from .ehow import EHowIE
|
||||||
from .eighttracks import EightTracksIE
|
from .eighttracks import EightTracksIE
|
||||||
from .einthusan import EinthusanIE
|
from .einthusan import EinthusanIE
|
||||||
@ -337,17 +347,20 @@ from .filmon import (
|
|||||||
FilmOnIE,
|
FilmOnIE,
|
||||||
FilmOnChannelIE,
|
FilmOnChannelIE,
|
||||||
)
|
)
|
||||||
from .firstpost import FirstpostIE
|
|
||||||
from .firsttv import FirstTVIE
|
from .firsttv import FirstTVIE
|
||||||
from .fivemin import FiveMinIE
|
from .fivemin import FiveMinIE
|
||||||
from .fivetv import FiveTVIE
|
from .fivetv import FiveTVIE
|
||||||
from .fktv import FKTVIE
|
|
||||||
from .flickr import FlickrIE
|
from .flickr import FlickrIE
|
||||||
from .flipagram import FlipagramIE
|
from .flipagram import FlipagramIE
|
||||||
from .folketinget import FolketingetIE
|
from .folketinget import FolketingetIE
|
||||||
from .footyroom import FootyRoomIE
|
from .footyroom import FootyRoomIE
|
||||||
from .formula1 import Formula1IE
|
from .formula1 import Formula1IE
|
||||||
from .fourtube import FourTubeIE
|
from .fourtube import (
|
||||||
|
FourTubeIE,
|
||||||
|
PornTubeIE,
|
||||||
|
PornerBrosIE,
|
||||||
|
FuxIE,
|
||||||
|
)
|
||||||
from .fox import FOXIE
|
from .fox import FOXIE
|
||||||
from .fox9 import FOX9IE
|
from .fox9 import FOX9IE
|
||||||
from .foxgay import FoxgayIE
|
from .foxgay import FoxgayIE
|
||||||
@ -363,13 +376,14 @@ from .francetv import (
|
|||||||
FranceTVIE,
|
FranceTVIE,
|
||||||
FranceTVEmbedIE,
|
FranceTVEmbedIE,
|
||||||
FranceTVInfoIE,
|
FranceTVInfoIE,
|
||||||
GenerationQuoiIE,
|
GenerationWhatIE,
|
||||||
CultureboxIE,
|
CultureboxIE,
|
||||||
)
|
)
|
||||||
from .freesound import FreesoundIE
|
from .freesound import FreesoundIE
|
||||||
from .freespeech import FreespeechIE
|
from .freespeech import FreespeechIE
|
||||||
from .freshlive import FreshLiveIE
|
from .freshlive import FreshLiveIE
|
||||||
from .funimation import FunimationIE
|
from .funimation import FunimationIE
|
||||||
|
from .funk import FunkIE
|
||||||
from .funnyordie import FunnyOrDieIE
|
from .funnyordie import FunnyOrDieIE
|
||||||
from .fusion import FusionIE
|
from .fusion import FusionIE
|
||||||
from .fxnetworks import FXNetworksIE
|
from .fxnetworks import FXNetworksIE
|
||||||
@ -378,7 +392,6 @@ from .gameone import (
|
|||||||
GameOneIE,
|
GameOneIE,
|
||||||
GameOnePlaylistIE,
|
GameOnePlaylistIE,
|
||||||
)
|
)
|
||||||
from .gamersyde import GamersydeIE
|
|
||||||
from .gamespot import GameSpotIE
|
from .gamespot import GameSpotIE
|
||||||
from .gamestar import GameStarIE
|
from .gamestar import GameStarIE
|
||||||
from .gaskrank import GaskrankIE
|
from .gaskrank import GaskrankIE
|
||||||
@ -419,7 +432,10 @@ from .hitbox import HitboxIE, HitboxLiveIE
|
|||||||
from .hitrecord import HitRecordIE
|
from .hitrecord import HitRecordIE
|
||||||
from .hornbunny import HornBunnyIE
|
from .hornbunny import HornBunnyIE
|
||||||
from .hotnewhiphop import HotNewHipHopIE
|
from .hotnewhiphop import HotNewHipHopIE
|
||||||
from .hotstar import HotStarIE
|
from .hotstar import (
|
||||||
|
HotStarIE,
|
||||||
|
HotStarPlaylistIE,
|
||||||
|
)
|
||||||
from .howcast import HowcastIE
|
from .howcast import HowcastIE
|
||||||
from .howstuffworks import HowStuffWorksIE
|
from .howstuffworks import HowStuffWorksIE
|
||||||
from .hrti import (
|
from .hrti import (
|
||||||
@ -469,8 +485,10 @@ from .jamendo import (
|
|||||||
)
|
)
|
||||||
from .jeuxvideo import JeuxVideoIE
|
from .jeuxvideo import JeuxVideoIE
|
||||||
from .jove import JoveIE
|
from .jove import JoveIE
|
||||||
|
from .joj import JojIE
|
||||||
from .jwplatform import JWPlatformIE
|
from .jwplatform import JWPlatformIE
|
||||||
from .jpopsukitv import JpopsukiIE
|
from .jpopsukitv import JpopsukiIE
|
||||||
|
from .kakao import KakaoIE
|
||||||
from .kaltura import KalturaIE
|
from .kaltura import KalturaIE
|
||||||
from .kamcord import KamcordIE
|
from .kamcord import KamcordIE
|
||||||
from .kanalplay import KanalPlayIE
|
from .kanalplay import KanalPlayIE
|
||||||
@ -499,6 +517,7 @@ from .la7 import LA7IE
|
|||||||
from .laola1tv import (
|
from .laola1tv import (
|
||||||
Laola1TvEmbedIE,
|
Laola1TvEmbedIE,
|
||||||
Laola1TvIE,
|
Laola1TvIE,
|
||||||
|
ITTFIE,
|
||||||
)
|
)
|
||||||
from .lci import LCIIE
|
from .lci import LCIIE
|
||||||
from .lcp import (
|
from .lcp import (
|
||||||
@ -526,7 +545,10 @@ from .limelight import (
|
|||||||
LimelightChannelListIE,
|
LimelightChannelListIE,
|
||||||
)
|
)
|
||||||
from .litv import LiTVIE
|
from .litv import LiTVIE
|
||||||
from .liveleak import LiveLeakIE
|
from .liveleak import (
|
||||||
|
LiveLeakIE,
|
||||||
|
LiveLeakEmbedIE,
|
||||||
|
)
|
||||||
from .livestream import (
|
from .livestream import (
|
||||||
LivestreamIE,
|
LivestreamIE,
|
||||||
LivestreamOriginalIE,
|
LivestreamOriginalIE,
|
||||||
@ -549,10 +571,13 @@ from .mangomolo import (
|
|||||||
MangomoloVideoIE,
|
MangomoloVideoIE,
|
||||||
MangomoloLiveIE,
|
MangomoloLiveIE,
|
||||||
)
|
)
|
||||||
|
from .manyvids import ManyVidsIE
|
||||||
|
from .massengeschmacktv import MassengeschmackTVIE
|
||||||
from .matchtv import MatchTVIE
|
from .matchtv import MatchTVIE
|
||||||
from .mdr import MDRIE
|
from .mdr import MDRIE
|
||||||
from .mediaset import MediasetIE
|
from .mediaset import MediasetIE
|
||||||
from .medici import MediciIE
|
from .medici import MediciIE
|
||||||
|
from .megaphone import MegaphoneIE
|
||||||
from .meipai import MeipaiIE
|
from .meipai import MeipaiIE
|
||||||
from .melonvod import MelonVODIE
|
from .melonvod import MelonVODIE
|
||||||
from .meta import METAIE
|
from .meta import METAIE
|
||||||
@ -579,7 +604,6 @@ from .mixcloud import (
|
|||||||
)
|
)
|
||||||
from .mlb import MLBIE
|
from .mlb import MLBIE
|
||||||
from .mnet import MnetIE
|
from .mnet import MnetIE
|
||||||
from .mpora import MporaIE
|
|
||||||
from .moevideo import MoeVideoIE
|
from .moevideo import MoeVideoIE
|
||||||
from .mofosex import MofosexIE
|
from .mofosex import MofosexIE
|
||||||
from .mojvideo import MojvideoIE
|
from .mojvideo import MojvideoIE
|
||||||
@ -604,7 +628,6 @@ from .mwave import MwaveIE, MwaveMeetGreetIE
|
|||||||
from .myspace import MySpaceIE, MySpaceAlbumIE
|
from .myspace import MySpaceIE, MySpaceAlbumIE
|
||||||
from .myspass import MySpassIE
|
from .myspass import MySpassIE
|
||||||
from .myvi import MyviIE
|
from .myvi import MyviIE
|
||||||
from .myvideo import MyVideoIE
|
|
||||||
from .myvidster import MyVidsterIE
|
from .myvidster import MyVidsterIE
|
||||||
from .nationalgeographic import (
|
from .nationalgeographic import (
|
||||||
NationalGeographicVideoIE,
|
NationalGeographicVideoIE,
|
||||||
@ -651,6 +674,10 @@ from .nextmedia import (
|
|||||||
AppleDailyIE,
|
AppleDailyIE,
|
||||||
NextTVIE,
|
NextTVIE,
|
||||||
)
|
)
|
||||||
|
from .nexx import (
|
||||||
|
NexxIE,
|
||||||
|
NexxEmbedIE,
|
||||||
|
)
|
||||||
from .nfb import NFBIE
|
from .nfb import NFBIE
|
||||||
from .nfl import NFLIE
|
from .nfl import NFLIE
|
||||||
from .nhk import NhkVodIE
|
from .nhk import NhkVodIE
|
||||||
@ -664,6 +691,7 @@ from .nick import (
|
|||||||
NickIE,
|
NickIE,
|
||||||
NickDeIE,
|
NickDeIE,
|
||||||
NickNightIE,
|
NickNightIE,
|
||||||
|
NickRuIE,
|
||||||
)
|
)
|
||||||
from .niconico import NiconicoIE, NiconicoPlaylistIE
|
from .niconico import NiconicoIE, NiconicoPlaylistIE
|
||||||
from .ninecninemedia import (
|
from .ninecninemedia import (
|
||||||
@ -747,6 +775,7 @@ from .ora import OraTVIE
|
|||||||
from .orf import (
|
from .orf import (
|
||||||
ORFTVthekIE,
|
ORFTVthekIE,
|
||||||
ORFFM4IE,
|
ORFFM4IE,
|
||||||
|
ORFFM4StoryIE,
|
||||||
ORFOE1IE,
|
ORFOE1IE,
|
||||||
ORFIPTVIE,
|
ORFIPTVIE,
|
||||||
)
|
)
|
||||||
@ -759,7 +788,9 @@ from .pandoratv import PandoraTVIE
|
|||||||
from .parliamentliveuk import ParliamentLiveUKIE
|
from .parliamentliveuk import ParliamentLiveUKIE
|
||||||
from .patreon import PatreonIE
|
from .patreon import PatreonIE
|
||||||
from .pbs import PBSIE
|
from .pbs import PBSIE
|
||||||
|
from .pearvideo import PearVideoIE
|
||||||
from .people import PeopleIE
|
from .people import PeopleIE
|
||||||
|
from .performgroup import PerformGroupIE
|
||||||
from .periscope import (
|
from .periscope import (
|
||||||
PeriscopeIE,
|
PeriscopeIE,
|
||||||
PeriscopeUserIE,
|
PeriscopeUserIE,
|
||||||
@ -785,6 +816,7 @@ from .polskieradio import (
|
|||||||
PolskieRadioIE,
|
PolskieRadioIE,
|
||||||
PolskieRadioCategoryIE,
|
PolskieRadioCategoryIE,
|
||||||
)
|
)
|
||||||
|
from .popcorntv import PopcornTVIE
|
||||||
from .porn91 import Porn91IE
|
from .porn91 import Porn91IE
|
||||||
from .porncom import PornComIE
|
from .porncom import PornComIE
|
||||||
from .pornflip import PornFlipIE
|
from .pornflip import PornFlipIE
|
||||||
@ -824,11 +856,16 @@ from .radiobremen import RadioBremenIE
|
|||||||
from .radiofrance import RadioFranceIE
|
from .radiofrance import RadioFranceIE
|
||||||
from .rai import (
|
from .rai import (
|
||||||
RaiPlayIE,
|
RaiPlayIE,
|
||||||
|
RaiPlayLiveIE,
|
||||||
RaiIE,
|
RaiIE,
|
||||||
)
|
)
|
||||||
from .rbmaradio import RBMARadioIE
|
from .rbmaradio import RBMARadioIE
|
||||||
from .rds import RDSIE
|
from .rds import RDSIE
|
||||||
from .redbulltv import RedBullTVIE
|
from .redbulltv import RedBullTVIE
|
||||||
|
from .reddit import (
|
||||||
|
RedditIE,
|
||||||
|
RedditRIE,
|
||||||
|
)
|
||||||
from .redtube import RedTubeIE
|
from .redtube import RedTubeIE
|
||||||
from .regiotv import RegioTVIE
|
from .regiotv import RegioTVIE
|
||||||
from .rentv import (
|
from .rentv import (
|
||||||
@ -872,6 +909,7 @@ from .rutube import (
|
|||||||
RutubeEmbedIE,
|
RutubeEmbedIE,
|
||||||
RutubeMovieIE,
|
RutubeMovieIE,
|
||||||
RutubePersonIE,
|
RutubePersonIE,
|
||||||
|
RutubePlaylistIE,
|
||||||
)
|
)
|
||||||
from .rutv import RUTVIE
|
from .rutv import RUTVIE
|
||||||
from .ruutu import RuutuIE
|
from .ruutu import RuutuIE
|
||||||
@ -892,6 +930,7 @@ from .seeker import SeekerIE
|
|||||||
from .senateisvp import SenateISVPIE
|
from .senateisvp import SenateISVPIE
|
||||||
from .sendtonews import SendtoNewsIE
|
from .sendtonews import SendtoNewsIE
|
||||||
from .servingsys import ServingSysIE
|
from .servingsys import ServingSysIE
|
||||||
|
from .servus import ServusIE
|
||||||
from .sexu import SexuIE
|
from .sexu import SexuIE
|
||||||
from .shahid import ShahidIE
|
from .shahid import ShahidIE
|
||||||
from .shared import (
|
from .shared import (
|
||||||
@ -908,6 +947,7 @@ from .skynewsarabia import (
|
|||||||
)
|
)
|
||||||
from .skysports import SkySportsIE
|
from .skysports import SkySportsIE
|
||||||
from .slideshare import SlideshareIE
|
from .slideshare import SlideshareIE
|
||||||
|
from .slideslive import SlidesLiveIE
|
||||||
from .slutload import SlutloadIE
|
from .slutload import SlutloadIE
|
||||||
from .smotri import (
|
from .smotri import (
|
||||||
SmotriIE,
|
SmotriIE,
|
||||||
@ -922,8 +962,9 @@ from .soundcloud import (
|
|||||||
SoundcloudIE,
|
SoundcloudIE,
|
||||||
SoundcloudSetIE,
|
SoundcloudSetIE,
|
||||||
SoundcloudUserIE,
|
SoundcloudUserIE,
|
||||||
|
SoundcloudTrackStationIE,
|
||||||
SoundcloudPlaylistIE,
|
SoundcloudPlaylistIE,
|
||||||
SoundcloudSearchIE
|
SoundcloudSearchIE,
|
||||||
)
|
)
|
||||||
from .soundgasm import (
|
from .soundgasm import (
|
||||||
SoundgasmIE,
|
SoundgasmIE,
|
||||||
@ -972,6 +1013,7 @@ from .tagesschau import (
|
|||||||
TagesschauIE,
|
TagesschauIE,
|
||||||
)
|
)
|
||||||
from .tass import TassIE
|
from .tass import TassIE
|
||||||
|
from .tastytrade import TastyTradeIE
|
||||||
from .tbs import TBSIE
|
from .tbs import TBSIE
|
||||||
from .tdslifeway import TDSLifewayIE
|
from .tdslifeway import TDSLifewayIE
|
||||||
from .teachertube import (
|
from .teachertube import (
|
||||||
@ -980,7 +1022,6 @@ from .teachertube import (
|
|||||||
)
|
)
|
||||||
from .teachingchannel import TeachingChannelIE
|
from .teachingchannel import TeachingChannelIE
|
||||||
from .teamcoco import TeamcocoIE
|
from .teamcoco import TeamcocoIE
|
||||||
from .teamfourstar import TeamFourStarIE
|
|
||||||
from .techtalks import TechTalksIE
|
from .techtalks import TechTalksIE
|
||||||
from .ted import TEDIE
|
from .ted import TEDIE
|
||||||
from .tele13 import Tele13IE
|
from .tele13 import Tele13IE
|
||||||
@ -1074,10 +1115,6 @@ from .tvplayer import TVPlayerIE
|
|||||||
from .tweakers import TweakersIE
|
from .tweakers import TweakersIE
|
||||||
from .twentyfourvideo import TwentyFourVideoIE
|
from .twentyfourvideo import TwentyFourVideoIE
|
||||||
from .twentymin import TwentyMinutenIE
|
from .twentymin import TwentyMinutenIE
|
||||||
from .twentytwotracks import (
|
|
||||||
TwentyTwoTracksIE,
|
|
||||||
TwentyTwoTracksGenreIE
|
|
||||||
)
|
|
||||||
from .twitch import (
|
from .twitch import (
|
||||||
TwitchVideoIE,
|
TwitchVideoIE,
|
||||||
TwitchChapterIE,
|
TwitchChapterIE,
|
||||||
@ -1103,6 +1140,7 @@ from .udn import UDNEmbedIE
|
|||||||
from .uktvplay import UKTVPlayIE
|
from .uktvplay import UKTVPlayIE
|
||||||
from .digiteka import DigitekaIE
|
from .digiteka import DigitekaIE
|
||||||
from .unistra import UnistraIE
|
from .unistra import UnistraIE
|
||||||
|
from .unity import UnityIE
|
||||||
from .uol import UOLIE
|
from .uol import UOLIE
|
||||||
from .uplynk import (
|
from .uplynk import (
|
||||||
UplynkIE,
|
UplynkIE,
|
||||||
@ -1202,13 +1240,18 @@ from .vk import (
|
|||||||
)
|
)
|
||||||
from .vlive import (
|
from .vlive import (
|
||||||
VLiveIE,
|
VLiveIE,
|
||||||
VLiveChannelIE
|
VLiveChannelIE,
|
||||||
|
VLivePlaylistIE
|
||||||
)
|
)
|
||||||
from .vodlocker import VodlockerIE
|
from .vodlocker import VodlockerIE
|
||||||
from .vodpl import VODPlIE
|
from .vodpl import VODPlIE
|
||||||
from .vodplatform import VODPlatformIE
|
from .vodplatform import VODPlatformIE
|
||||||
from .voicerepublic import VoiceRepublicIE
|
from .voicerepublic import VoiceRepublicIE
|
||||||
from .voxmedia import VoxMediaIE
|
from .voot import VootIE
|
||||||
|
from .voxmedia import (
|
||||||
|
VoxMediaVolumeIE,
|
||||||
|
VoxMediaIE,
|
||||||
|
)
|
||||||
from .vporn import VpornIE
|
from .vporn import VpornIE
|
||||||
from .vrt import VRTIE
|
from .vrt import VRTIE
|
||||||
from .vrak import VrakIE
|
from .vrak import VrakIE
|
||||||
@ -1229,6 +1272,7 @@ from .washingtonpost import (
|
|||||||
WashingtonPostArticleIE,
|
WashingtonPostArticleIE,
|
||||||
)
|
)
|
||||||
from .wat import WatIE
|
from .wat import WatIE
|
||||||
|
from .watchbox import WatchBoxIE
|
||||||
from .watchindianporn import WatchIndianPornIE
|
from .watchindianporn import WatchIndianPornIE
|
||||||
from .wdr import (
|
from .wdr import (
|
||||||
WDRIE,
|
WDRIE,
|
||||||
@ -1278,12 +1322,12 @@ from .yahoo import (
|
|||||||
YahooIE,
|
YahooIE,
|
||||||
YahooSearchIE,
|
YahooSearchIE,
|
||||||
)
|
)
|
||||||
from .yam import YamIE
|
|
||||||
from .yandexmusic import (
|
from .yandexmusic import (
|
||||||
YandexMusicTrackIE,
|
YandexMusicTrackIE,
|
||||||
YandexMusicAlbumIE,
|
YandexMusicAlbumIE,
|
||||||
YandexMusicPlaylistIE,
|
YandexMusicPlaylistIE,
|
||||||
)
|
)
|
||||||
|
from .yandexdisk import YandexDiskIE
|
||||||
from .yesjapan import YesJapanIE
|
from .yesjapan import YesJapanIE
|
||||||
from .yinyuetai import YinYueTaiIE
|
from .yinyuetai import YinYueTaiIE
|
||||||
from .ynet import YnetIE
|
from .ynet import YnetIE
|
||||||
@ -1292,6 +1336,11 @@ from .youku import (
|
|||||||
YoukuIE,
|
YoukuIE,
|
||||||
YoukuShowIE,
|
YoukuShowIE,
|
||||||
)
|
)
|
||||||
|
from .younow import (
|
||||||
|
YouNowLiveIE,
|
||||||
|
YouNowChannelIE,
|
||||||
|
YouNowMomentIE,
|
||||||
|
)
|
||||||
from .youporn import YouPornIE
|
from .youporn import YouPornIE
|
||||||
from .yourupload import YourUploadIE
|
from .yourupload import YourUploadIE
|
||||||
from .youtube import (
|
from .youtube import (
|
||||||
@ -1306,7 +1355,6 @@ from .youtube import (
|
|||||||
YoutubeSearchDateIE,
|
YoutubeSearchDateIE,
|
||||||
YoutubeSearchIE,
|
YoutubeSearchIE,
|
||||||
YoutubeSearchURLIE,
|
YoutubeSearchURLIE,
|
||||||
YoutubeSharedVideoIE,
|
|
||||||
YoutubeShowIE,
|
YoutubeShowIE,
|
||||||
YoutubeSubscriptionsIE,
|
YoutubeSubscriptionsIE,
|
||||||
YoutubeTruncatedIDIE,
|
YoutubeTruncatedIDIE,
|
||||||
|
@ -67,9 +67,9 @@ class FacebookIE(InfoExtractor):
|
|||||||
'uploader': 'Tennis on Facebook',
|
'uploader': 'Tennis on Facebook',
|
||||||
'upload_date': '20140908',
|
'upload_date': '20140908',
|
||||||
'timestamp': 1410199200,
|
'timestamp': 1410199200,
|
||||||
}
|
},
|
||||||
|
'skip': 'Requires logging in',
|
||||||
}, {
|
}, {
|
||||||
'note': 'Video without discernible title',
|
|
||||||
'url': 'https://www.facebook.com/video.php?v=274175099429670',
|
'url': 'https://www.facebook.com/video.php?v=274175099429670',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '274175099429670',
|
'id': '274175099429670',
|
||||||
@ -78,6 +78,7 @@ class FacebookIE(InfoExtractor):
|
|||||||
'uploader': 'Asif Nawab Butt',
|
'uploader': 'Asif Nawab Butt',
|
||||||
'upload_date': '20140506',
|
'upload_date': '20140506',
|
||||||
'timestamp': 1399398998,
|
'timestamp': 1399398998,
|
||||||
|
'thumbnail': r're:^https?://.*',
|
||||||
},
|
},
|
||||||
'expected_warnings': [
|
'expected_warnings': [
|
||||||
'title'
|
'title'
|
||||||
@ -94,6 +95,7 @@ class FacebookIE(InfoExtractor):
|
|||||||
'upload_date': '20160110',
|
'upload_date': '20160110',
|
||||||
'timestamp': 1452431627,
|
'timestamp': 1452431627,
|
||||||
},
|
},
|
||||||
|
'skip': 'Requires logging in',
|
||||||
}, {
|
}, {
|
||||||
'url': 'https://www.facebook.com/maxlayn/posts/10153807558977570',
|
'url': 'https://www.facebook.com/maxlayn/posts/10153807558977570',
|
||||||
'md5': '037b1fa7f3c2d02b7a0d7bc16031ecc6',
|
'md5': '037b1fa7f3c2d02b7a0d7bc16031ecc6',
|
||||||
@ -121,7 +123,11 @@ class FacebookIE(InfoExtractor):
|
|||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '10153664894881749',
|
'id': '10153664894881749',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Facebook video #10153664894881749',
|
'title': 'Average time to confirm recent Supreme Court nominees: 67 days Longest it\'s t...',
|
||||||
|
'thumbnail': r're:^https?://.*',
|
||||||
|
'timestamp': 1456259628,
|
||||||
|
'upload_date': '20160223',
|
||||||
|
'uploader': 'Barack Obama',
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
# have 1080P, but only up to 720p in swf params
|
# have 1080P, but only up to 720p in swf params
|
||||||
@ -130,10 +136,11 @@ class FacebookIE(InfoExtractor):
|
|||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '10155529876156509',
|
'id': '10155529876156509',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Holocaust survivor becomes US citizen',
|
'title': 'She survived the holocaust — and years later, she’s getting her citizenship s...',
|
||||||
'timestamp': 1477818095,
|
'timestamp': 1477818095,
|
||||||
'upload_date': '20161030',
|
'upload_date': '20161030',
|
||||||
'uploader': 'CNN',
|
'uploader': 'CNN',
|
||||||
|
'thumbnail': r're:^https?://.*',
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
# bigPipe.onPageletArrive ... onPageletArrive pagelet_group_mall
|
# bigPipe.onPageletArrive ... onPageletArrive pagelet_group_mall
|
||||||
@ -158,6 +165,7 @@ class FacebookIE(InfoExtractor):
|
|||||||
'timestamp': 1477305000,
|
'timestamp': 1477305000,
|
||||||
'upload_date': '20161024',
|
'upload_date': '20161024',
|
||||||
'uploader': 'La Guía Del Varón',
|
'uploader': 'La Guía Del Varón',
|
||||||
|
'thumbnail': r're:^https?://.*',
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
@ -203,19 +211,19 @@ class FacebookIE(InfoExtractor):
|
|||||||
}]
|
}]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _extract_url(webpage):
|
def _extract_urls(webpage):
|
||||||
mobj = re.search(
|
urls = []
|
||||||
r'<iframe[^>]+?src=(["\'])(?P<url>https://www\.facebook\.com/video/embed.+?)\1', webpage)
|
for mobj in re.finditer(
|
||||||
if mobj is not None:
|
r'<iframe[^>]+?src=(["\'])(?P<url>https?://www\.facebook\.com/(?:video/embed|plugins/video\.php).+?)\1',
|
||||||
return mobj.group('url')
|
webpage):
|
||||||
|
urls.append(mobj.group('url'))
|
||||||
# Facebook API embed
|
# Facebook API embed
|
||||||
# see https://developers.facebook.com/docs/plugins/embedded-video-player
|
# see https://developers.facebook.com/docs/plugins/embedded-video-player
|
||||||
mobj = re.search(r'''(?x)<div[^>]+
|
for mobj in re.finditer(r'''(?x)<div[^>]+
|
||||||
class=(?P<q1>[\'"])[^\'"]*\bfb-(?:video|post)\b[^\'"]*(?P=q1)[^>]+
|
class=(?P<q1>[\'"])[^\'"]*\bfb-(?:video|post)\b[^\'"]*(?P=q1)[^>]+
|
||||||
data-href=(?P<q2>[\'"])(?P<url>(?:https?:)?//(?:www\.)?facebook.com/.+?)(?P=q2)''', webpage)
|
data-href=(?P<q2>[\'"])(?P<url>(?:https?:)?//(?:www\.)?facebook.com/.+?)(?P=q2)''', webpage):
|
||||||
if mobj is not None:
|
urls.append(mobj.group('url'))
|
||||||
return mobj.group('url')
|
return urls
|
||||||
|
|
||||||
def _login(self):
|
def _login(self):
|
||||||
(useremail, password) = self._get_login_info()
|
(useremail, password) = self._get_login_info()
|
||||||
@ -376,6 +384,7 @@ class FacebookIE(InfoExtractor):
|
|||||||
timestamp = int_or_none(self._search_regex(
|
timestamp = int_or_none(self._search_regex(
|
||||||
r'<abbr[^>]+data-utime=["\'](\d+)', webpage,
|
r'<abbr[^>]+data-utime=["\'](\d+)', webpage,
|
||||||
'timestamp', default=None))
|
'timestamp', default=None))
|
||||||
|
thumbnail = self._og_search_thumbnail(webpage)
|
||||||
|
|
||||||
info_dict = {
|
info_dict = {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
@ -383,6 +392,7 @@ class FacebookIE(InfoExtractor):
|
|||||||
'formats': formats,
|
'formats': formats,
|
||||||
'uploader': uploader,
|
'uploader': uploader,
|
||||||
'timestamp': timestamp,
|
'timestamp': timestamp,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
}
|
}
|
||||||
|
|
||||||
return webpage, info_dict
|
return webpage, info_dict
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from ..compat import compat_etree_fromstring
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
xpath_element,
|
xpath_element,
|
||||||
xpath_text,
|
xpath_text,
|
||||||
@ -43,10 +46,15 @@ class FazIE(InfoExtractor):
|
|||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
description = self._og_search_description(webpage)
|
description = self._og_search_description(webpage)
|
||||||
config_xml_url = self._search_regex(
|
media = self._html_search_regex(
|
||||||
r'videoXMLURL\s*=\s*"([^"]+)', webpage, 'config xml url')
|
r"data-videojs-media='([^']+)",
|
||||||
config = self._download_xml(
|
webpage, 'media')
|
||||||
config_xml_url, video_id, 'Downloading config xml')
|
if media == 'extern':
|
||||||
|
perform_url = self._search_regex(
|
||||||
|
r"<iframe[^>]+?src='((?:http:)?//player\.performgroup\.com/eplayer/eplayer\.html#/?[0-9a-f]{26}\.[0-9a-z]{26})",
|
||||||
|
webpage, 'perform url')
|
||||||
|
return self.url_result(perform_url)
|
||||||
|
config = compat_etree_fromstring(media)
|
||||||
|
|
||||||
encodings = xpath_element(config, 'ENCODINGS', 'encodings', True)
|
encodings = xpath_element(config, 'ENCODINGS', 'encodings', True)
|
||||||
formats = []
|
formats = []
|
||||||
@ -55,12 +63,24 @@ class FazIE(InfoExtractor):
|
|||||||
if encoding is not None:
|
if encoding is not None:
|
||||||
encoding_url = xpath_text(encoding, 'FILENAME')
|
encoding_url = xpath_text(encoding, 'FILENAME')
|
||||||
if encoding_url:
|
if encoding_url:
|
||||||
formats.append({
|
tbr = xpath_text(encoding, 'AVERAGEBITRATE', 1000)
|
||||||
|
if tbr:
|
||||||
|
tbr = int_or_none(tbr.replace(',', '.'))
|
||||||
|
f = {
|
||||||
'url': encoding_url,
|
'url': encoding_url,
|
||||||
'format_id': code.lower(),
|
'format_id': code.lower(),
|
||||||
'quality': pref,
|
'quality': pref,
|
||||||
'tbr': int_or_none(xpath_text(encoding, 'AVERAGEBITRATE')),
|
'tbr': tbr,
|
||||||
|
'vcodec': xpath_text(encoding, 'CODEC'),
|
||||||
|
}
|
||||||
|
mobj = re.search(r'(\d+)x(\d+)_(\d+)\.mp4', encoding_url)
|
||||||
|
if mobj:
|
||||||
|
f.update({
|
||||||
|
'width': int(mobj.group(1)),
|
||||||
|
'height': int(mobj.group(2)),
|
||||||
|
'tbr': tbr or int(mobj.group(3)),
|
||||||
})
|
})
|
||||||
|
formats.append(f)
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -2,7 +2,10 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import compat_urlparse
|
from ..utils import (
|
||||||
|
int_or_none,
|
||||||
|
float_or_none,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class FczenitIE(InfoExtractor):
|
class FczenitIE(InfoExtractor):
|
||||||
@ -14,6 +17,8 @@ class FczenitIE(InfoExtractor):
|
|||||||
'id': '41044',
|
'id': '41044',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Так пишется история: казанский разгром ЦСКА на «Зенит-ТВ»',
|
'title': 'Так пишется история: казанский разгром ЦСКА на «Зенит-ТВ»',
|
||||||
|
'timestamp': 1462283735,
|
||||||
|
'upload_date': '20160503',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -21,28 +26,31 @@ class FczenitIE(InfoExtractor):
|
|||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
video_title = self._html_search_regex(
|
msi_id = self._search_regex(
|
||||||
r'<[^>]+class=\"photoalbum__title\">([^<]+)', webpage, 'title')
|
r"(?s)config\s*=\s*{.+?video_id\s*:\s*'([^']+)'", webpage, 'msi id')
|
||||||
|
|
||||||
video_items = self._parse_json(self._search_regex(
|
msi_data = self._download_json(
|
||||||
r'arrPath\s*=\s*JSON\.parse\(\'(.+)\'\)', webpage, 'video items'),
|
'http://player.fc-zenit.ru/msi/video', msi_id, query={
|
||||||
video_id)
|
'video': msi_id,
|
||||||
|
})['data']
|
||||||
def merge_dicts(*dicts):
|
title = msi_data['name']
|
||||||
ret = {}
|
|
||||||
for a_dict in dicts:
|
|
||||||
ret.update(a_dict)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
formats = [{
|
formats = [{
|
||||||
'url': compat_urlparse.urljoin(url, video_url),
|
'format_id': q.get('label'),
|
||||||
'tbr': int(tbr),
|
'url': q['url'],
|
||||||
} for tbr, video_url in merge_dicts(*video_items).items()]
|
'height': int_or_none(q.get('label')),
|
||||||
|
} for q in msi_data['qualities'] if q.get('url')]
|
||||||
|
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
tags = [tag['label'] for tag in msi_data.get('tags', []) if tag.get('label')]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': video_title,
|
'title': title,
|
||||||
|
'thumbnail': msi_data.get('preview'),
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
|
'duration': float_or_none(msi_data.get('duration')),
|
||||||
|
'timestamp': int_or_none(msi_data.get('date')),
|
||||||
|
'tags': tags,
|
||||||
}
|
}
|
||||||
|
@ -1,50 +0,0 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
|
||||||
|
|
||||||
|
|
||||||
class FirstpostIE(InfoExtractor):
|
|
||||||
_VALID_URL = r'https?://(?:www\.)?firstpost\.com/[^/]+/.*-(?P<id>[0-9]+)\.html'
|
|
||||||
|
|
||||||
_TEST = {
|
|
||||||
'url': 'http://www.firstpost.com/india/india-to-launch-indigenous-aircraft-carrier-monday-1025403.html',
|
|
||||||
'md5': 'ee9114957692f01fb1263ed87039112a',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '1025403',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': 'India to launch indigenous aircraft carrier INS Vikrant today',
|
|
||||||
'description': 'md5:feef3041cb09724e0bdc02843348f5f4',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
|
||||||
video_id = self._match_id(url)
|
|
||||||
page = self._download_webpage(url, video_id)
|
|
||||||
|
|
||||||
title = self._html_search_meta('twitter:title', page, 'title', fatal=True)
|
|
||||||
description = self._html_search_meta('twitter:description', page, 'title')
|
|
||||||
|
|
||||||
data = self._download_xml(
|
|
||||||
'http://www.firstpost.com/getvideoxml-%s.xml' % video_id, video_id,
|
|
||||||
'Downloading video XML')
|
|
||||||
|
|
||||||
item = data.find('./playlist/item')
|
|
||||||
thumbnail = item.find('./image').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
|
|
||||||
]
|
|
||||||
self._sort_formats(formats)
|
|
||||||
|
|
||||||
return {
|
|
||||||
'id': video_id,
|
|
||||||
'title': title,
|
|
||||||
'description': description,
|
|
||||||
'thumbnail': thumbnail,
|
|
||||||
'formats': formats,
|
|
||||||
}
|
|
@ -43,7 +43,7 @@ class FiveTVIE(InfoExtractor):
|
|||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'glavnoe',
|
'id': 'glavnoe',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Итоги недели с 8 по 14 июня 2015 года',
|
'title': r're:^Итоги недели с \d+ по \d+ \w+ \d{4} года$',
|
||||||
'thumbnail': r're:^https?://.*\.jpg$',
|
'thumbnail': r're:^https?://.*\.jpg$',
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
@ -70,7 +70,8 @@ class FiveTVIE(InfoExtractor):
|
|||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
video_url = self._search_regex(
|
video_url = self._search_regex(
|
||||||
r'<a[^>]+?href="([^"]+)"[^>]+?class="videoplayer"',
|
[r'<div[^>]+?class="flowplayer[^>]+?data-href="([^"]+)"',
|
||||||
|
r'<a[^>]+?href="([^"]+)"[^>]+?class="videoplayer"'],
|
||||||
webpage, 'video url')
|
webpage, 'video url')
|
||||||
|
|
||||||
title = self._og_search_title(webpage, default=None) or self._search_regex(
|
title = self._og_search_title(webpage, default=None) or self._search_regex(
|
||||||
|
@ -1,51 +0,0 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
|
||||||
from ..utils import (
|
|
||||||
clean_html,
|
|
||||||
determine_ext,
|
|
||||||
js_to_json,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class FKTVIE(InfoExtractor):
|
|
||||||
IE_NAME = 'fernsehkritik.tv'
|
|
||||||
_VALID_URL = r'https?://(?:www\.)?fernsehkritik\.tv/folge-(?P<id>[0-9]+)(?:/.*)?'
|
|
||||||
|
|
||||||
_TEST = {
|
|
||||||
'url': 'http://fernsehkritik.tv/folge-1',
|
|
||||||
'md5': '21f0b0c99bce7d5b524eb1b17b1c6d79',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '1',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': 'Folge 1 vom 10. April 2007',
|
|
||||||
'thumbnail': r're:^https?://.*\.jpg$',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
|
||||||
episode = self._match_id(url)
|
|
||||||
|
|
||||||
webpage = self._download_webpage(
|
|
||||||
'http://fernsehkritik.tv/folge-%s/play' % episode, episode)
|
|
||||||
title = clean_html(self._html_search_regex(
|
|
||||||
'<h3>([^<]+)</h3>', webpage, 'title'))
|
|
||||||
thumbnail = self._search_regex(r'POSTER\s*=\s*"([^"]+)', webpage, 'thumbnail', fatal=False)
|
|
||||||
sources = self._parse_json(self._search_regex(r'(?s)MEDIA\s*=\s*(\[.+?\]);', webpage, 'media'), episode, js_to_json)
|
|
||||||
|
|
||||||
formats = []
|
|
||||||
for source in sources:
|
|
||||||
furl = source.get('src')
|
|
||||||
if furl:
|
|
||||||
formats.append({
|
|
||||||
'url': furl,
|
|
||||||
'format_id': determine_ext(furl),
|
|
||||||
})
|
|
||||||
self._sort_formats(formats)
|
|
||||||
|
|
||||||
return {
|
|
||||||
'id': episode,
|
|
||||||
'title': title,
|
|
||||||
'formats': formats,
|
|
||||||
'thumbnail': thumbnail,
|
|
||||||
}
|
|
@ -3,39 +3,22 @@ from __future__ import unicode_literals
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from ..compat import compat_urlparse
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
parse_duration,
|
parse_duration,
|
||||||
parse_iso8601,
|
parse_iso8601,
|
||||||
sanitized_Request,
|
|
||||||
str_to_int,
|
str_to_int,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class FourTubeIE(InfoExtractor):
|
class FourTubeBaseIE(InfoExtractor):
|
||||||
IE_NAME = '4tube'
|
|
||||||
_VALID_URL = r'https?://(?:www\.)?4tube\.com/videos/(?P<id>\d+)'
|
|
||||||
|
|
||||||
_TEST = {
|
|
||||||
'url': 'http://www.4tube.com/videos/209733/hot-babe-holly-michaels-gets-her-ass-stuffed-by-black',
|
|
||||||
'md5': '6516c8ac63b03de06bc8eac14362db4f',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '209733',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': 'Hot Babe Holly Michaels gets her ass stuffed by black',
|
|
||||||
'uploader': 'WCP Club',
|
|
||||||
'uploader_id': 'wcp-club',
|
|
||||||
'upload_date': '20131031',
|
|
||||||
'timestamp': 1383263892,
|
|
||||||
'duration': 583,
|
|
||||||
'view_count': int,
|
|
||||||
'like_count': int,
|
|
||||||
'categories': list,
|
|
||||||
'age_limit': 18,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
kind, video_id, display_id = mobj.group('kind', 'id', 'display_id')
|
||||||
|
|
||||||
|
if kind == 'm' or not display_id:
|
||||||
|
url = self._URL_TEMPLATE % video_id
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
title = self._html_search_meta('name', webpage)
|
title = self._html_search_meta('name', webpage)
|
||||||
@ -43,10 +26,10 @@ class FourTubeIE(InfoExtractor):
|
|||||||
'uploadDate', webpage))
|
'uploadDate', webpage))
|
||||||
thumbnail = self._html_search_meta('thumbnailUrl', webpage)
|
thumbnail = self._html_search_meta('thumbnailUrl', webpage)
|
||||||
uploader_id = self._html_search_regex(
|
uploader_id = self._html_search_regex(
|
||||||
r'<a class="item-to-subscribe" href="[^"]+/channels/([^/"]+)" title="Go to [^"]+ page">',
|
r'<a class="item-to-subscribe" href="[^"]+/(?:channel|user)s?/([^/"]+)" title="Go to [^"]+ page">',
|
||||||
webpage, 'uploader id', fatal=False)
|
webpage, 'uploader id', fatal=False)
|
||||||
uploader = self._html_search_regex(
|
uploader = self._html_search_regex(
|
||||||
r'<a class="item-to-subscribe" href="[^"]+/channels/[^/"]+" title="Go to ([^"]+) page">',
|
r'<a class="item-to-subscribe" href="[^"]+/(?:channel|user)s?/[^/"]+" title="Go to ([^"]+) page">',
|
||||||
webpage, 'uploader', fatal=False)
|
webpage, 'uploader', fatal=False)
|
||||||
|
|
||||||
categories_html = self._search_regex(
|
categories_html = self._search_regex(
|
||||||
@ -60,10 +43,10 @@ class FourTubeIE(InfoExtractor):
|
|||||||
|
|
||||||
view_count = str_to_int(self._search_regex(
|
view_count = str_to_int(self._search_regex(
|
||||||
r'<meta[^>]+itemprop="interactionCount"[^>]+content="UserPlays:([0-9,]+)">',
|
r'<meta[^>]+itemprop="interactionCount"[^>]+content="UserPlays:([0-9,]+)">',
|
||||||
webpage, 'view count', fatal=False))
|
webpage, 'view count', default=None))
|
||||||
like_count = str_to_int(self._search_regex(
|
like_count = str_to_int(self._search_regex(
|
||||||
r'<meta[^>]+itemprop="interactionCount"[^>]+content="UserLikes:([0-9,]+)">',
|
r'<meta[^>]+itemprop="interactionCount"[^>]+content="UserLikes:([0-9,]+)">',
|
||||||
webpage, 'like count', fatal=False))
|
webpage, 'like count', default=None))
|
||||||
duration = parse_duration(self._html_search_meta('duration', webpage))
|
duration = parse_duration(self._html_search_meta('duration', webpage))
|
||||||
|
|
||||||
media_id = self._search_regex(
|
media_id = self._search_regex(
|
||||||
@ -87,12 +70,12 @@ class FourTubeIE(InfoExtractor):
|
|||||||
|
|
||||||
token_url = 'https://tkn.kodicdn.com/{0}/desktop/{1}'.format(
|
token_url = 'https://tkn.kodicdn.com/{0}/desktop/{1}'.format(
|
||||||
media_id, '+'.join(sources))
|
media_id, '+'.join(sources))
|
||||||
headers = {
|
|
||||||
b'Content-Type': b'application/x-www-form-urlencoded',
|
parsed_url = compat_urlparse.urlparse(url)
|
||||||
b'Origin': b'https://www.4tube.com',
|
tokens = self._download_json(token_url, video_id, data=b'', headers={
|
||||||
}
|
'Origin': '%s://%s' % (parsed_url.scheme, parsed_url.hostname),
|
||||||
token_req = sanitized_Request(token_url, b'{}', headers)
|
'Referer': url,
|
||||||
tokens = self._download_json(token_req, video_id)
|
})
|
||||||
formats = [{
|
formats = [{
|
||||||
'url': tokens[format]['token'],
|
'url': tokens[format]['token'],
|
||||||
'format_id': format + 'p',
|
'format_id': format + 'p',
|
||||||
@ -115,3 +98,126 @@ class FourTubeIE(InfoExtractor):
|
|||||||
'duration': duration,
|
'duration': duration,
|
||||||
'age_limit': 18,
|
'age_limit': 18,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class FourTubeIE(FourTubeBaseIE):
|
||||||
|
IE_NAME = '4tube'
|
||||||
|
_VALID_URL = r'https?://(?:(?P<kind>www|m)\.)?4tube\.com/(?:videos|embed)/(?P<id>\d+)(?:/(?P<display_id>[^/?#&]+))?'
|
||||||
|
_URL_TEMPLATE = 'https://www.4tube.com/videos/%s/video'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://www.4tube.com/videos/209733/hot-babe-holly-michaels-gets-her-ass-stuffed-by-black',
|
||||||
|
'md5': '6516c8ac63b03de06bc8eac14362db4f',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '209733',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Hot Babe Holly Michaels gets her ass stuffed by black',
|
||||||
|
'uploader': 'WCP Club',
|
||||||
|
'uploader_id': 'wcp-club',
|
||||||
|
'upload_date': '20131031',
|
||||||
|
'timestamp': 1383263892,
|
||||||
|
'duration': 583,
|
||||||
|
'view_count': int,
|
||||||
|
'like_count': int,
|
||||||
|
'categories': list,
|
||||||
|
'age_limit': 18,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.4tube.com/embed/209733',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://m.4tube.com/videos/209733/hot-babe-holly-michaels-gets-her-ass-stuffed-by-black',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
|
||||||
|
class FuxIE(FourTubeBaseIE):
|
||||||
|
_VALID_URL = r'https?://(?:(?P<kind>www|m)\.)?fux\.com/(?:video|embed)/(?P<id>\d+)(?:/(?P<display_id>[^/?#&]+))?'
|
||||||
|
_URL_TEMPLATE = 'https://www.fux.com/video/%s/video'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://www.fux.com/video/195359/awesome-fucking-kitchen-ends-cum-swallow',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '195359',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Awesome fucking in the kitchen ends with cum swallow',
|
||||||
|
'uploader': 'alenci2342',
|
||||||
|
'uploader_id': 'alenci2342',
|
||||||
|
'upload_date': '20131230',
|
||||||
|
'timestamp': 1388361660,
|
||||||
|
'duration': 289,
|
||||||
|
'view_count': int,
|
||||||
|
'like_count': int,
|
||||||
|
'categories': list,
|
||||||
|
'age_limit': 18,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.fux.com/embed/195359',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.fux.com/video/195359/awesome-fucking-kitchen-ends-cum-swallow',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
|
||||||
|
class PornTubeIE(FourTubeBaseIE):
|
||||||
|
_VALID_URL = r'https?://(?:(?P<kind>www|m)\.)?porntube\.com/(?:videos/(?P<display_id>[^/]+)_|embed/)(?P<id>\d+)'
|
||||||
|
_URL_TEMPLATE = 'https://www.porntube.com/videos/video_%s'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://www.porntube.com/videos/teen-couple-doing-anal_7089759',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '7089759',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Teen couple doing anal',
|
||||||
|
'uploader': 'Alexy',
|
||||||
|
'uploader_id': 'Alexy',
|
||||||
|
'upload_date': '20150606',
|
||||||
|
'timestamp': 1433595647,
|
||||||
|
'duration': 5052,
|
||||||
|
'view_count': int,
|
||||||
|
'like_count': int,
|
||||||
|
'categories': list,
|
||||||
|
'age_limit': 18,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.porntube.com/embed/7089759',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://m.porntube.com/videos/teen-couple-doing-anal_7089759',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
|
||||||
|
class PornerBrosIE(FourTubeBaseIE):
|
||||||
|
_VALID_URL = r'https?://(?:(?P<kind>www|m)\.)?pornerbros\.com/(?:videos/(?P<display_id>[^/]+)_|embed/)(?P<id>\d+)'
|
||||||
|
_URL_TEMPLATE = 'https://www.pornerbros.com/videos/video_%s'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://www.pornerbros.com/videos/skinny-brunette-takes-big-cock-down-her-anal-hole_181369',
|
||||||
|
'md5': '6516c8ac63b03de06bc8eac14362db4f',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '181369',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Skinny brunette takes big cock down her anal hole',
|
||||||
|
'uploader': 'PornerBros HD',
|
||||||
|
'uploader_id': 'pornerbros-hd',
|
||||||
|
'upload_date': '20130130',
|
||||||
|
'timestamp': 1359527401,
|
||||||
|
'duration': 1224,
|
||||||
|
'view_count': int,
|
||||||
|
'categories': list,
|
||||||
|
'age_limit': 18,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.pornerbros.com/embed/181369',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://m.pornerbros.com/videos/skinny-brunette-takes-big-cock-down-her-anal-hole_181369',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
@ -2,57 +2,113 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .adobepass import AdobePassIE
|
from .adobepass import AdobePassIE
|
||||||
|
from .uplynk import UplynkPreplayIE
|
||||||
|
from ..compat import compat_str
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
smuggle_url,
|
HEADRequest,
|
||||||
update_url_query,
|
int_or_none,
|
||||||
|
parse_age_limit,
|
||||||
|
parse_duration,
|
||||||
|
try_get,
|
||||||
|
unified_timestamp,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class FOXIE(AdobePassIE):
|
class FOXIE(AdobePassIE):
|
||||||
_VALID_URL = r'https?://(?:www\.)?fox\.com/watch/(?P<id>[0-9]+)'
|
_VALID_URL = r'https?://(?:www\.)?fox\.com/watch/(?P<id>[\da-fA-F]+)'
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
'url': 'http://www.fox.com/watch/255180355939/7684182528',
|
# clip
|
||||||
|
'url': 'https://www.fox.com/watch/4b765a60490325103ea69888fb2bd4e8/',
|
||||||
'md5': 'ebd296fcc41dd4b19f8115d8461a3165',
|
'md5': 'ebd296fcc41dd4b19f8115d8461a3165',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '255180355939',
|
'id': '4b765a60490325103ea69888fb2bd4e8',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Official Trailer: Gotham',
|
'title': 'Aftermath: Bruce Wayne Develops Into The Dark Knight',
|
||||||
'description': 'Tracing the rise of the great DC Comics Super-Villains and vigilantes, Gotham reveals an entirely new chapter that has never been told.',
|
'description': 'md5:549cd9c70d413adb32ce2a779b53b486',
|
||||||
'duration': 129,
|
'duration': 102,
|
||||||
'timestamp': 1400020798,
|
'timestamp': 1504291893,
|
||||||
'upload_date': '20140513',
|
'upload_date': '20170901',
|
||||||
'uploader': 'NEWA-FNG-FOXCOM',
|
'creator': 'FOX',
|
||||||
|
'series': 'Gotham',
|
||||||
},
|
},
|
||||||
'add_ie': ['ThePlatform'],
|
'params': {
|
||||||
}
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
# episode, geo-restricted
|
||||||
|
'url': 'https://www.fox.com/watch/087036ca7f33c8eb79b08152b4dd75c1/',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
# episode, geo-restricted, tv provided required
|
||||||
|
'url': 'https://www.fox.com/watch/30056b295fb57f7452aeeb4920bc3024/',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
webpage = self._download_webpage(url, video_id)
|
|
||||||
|
|
||||||
settings = self._parse_json(self._search_regex(
|
video = self._download_json(
|
||||||
r'jQuery\.extend\(Drupal\.settings\s*,\s*({.+?})\);',
|
'https://api.fox.com/fbc-content/v1_4/video/%s' % video_id,
|
||||||
webpage, 'drupal settings'), video_id)
|
video_id, headers={
|
||||||
fox_pdk_player = settings['fox_pdk_player']
|
'apikey': 'abdcbed02c124d393b39e818a4312055',
|
||||||
release_url = fox_pdk_player['release_url']
|
'Content-Type': 'application/json',
|
||||||
query = {
|
'Referer': url,
|
||||||
'mbr': 'true',
|
|
||||||
'switch': 'http'
|
|
||||||
}
|
|
||||||
if fox_pdk_player.get('access') == 'locked':
|
|
||||||
ap_p = settings['foxAdobePassProvider']
|
|
||||||
rating = ap_p.get('videoRating')
|
|
||||||
if rating == 'n/a':
|
|
||||||
rating = None
|
|
||||||
resource = self._get_mvpd_resource('fbc-fox', None, ap_p['videoGUID'], rating)
|
|
||||||
query['auth'] = self._extract_mvpd_auth(url, video_id, 'fbc-fox', resource)
|
|
||||||
|
|
||||||
info = self._search_json_ld(webpage, video_id, fatal=False)
|
|
||||||
info.update({
|
|
||||||
'_type': 'url_transparent',
|
|
||||||
'ie_key': 'ThePlatform',
|
|
||||||
'url': smuggle_url(update_url_query(release_url, query), {'force_smil_url': True}),
|
|
||||||
'id': video_id,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
title = video['name']
|
||||||
|
release_url = video['videoRelease']['url']
|
||||||
|
|
||||||
|
description = video.get('description')
|
||||||
|
duration = int_or_none(video.get('durationInSeconds')) or int_or_none(
|
||||||
|
video.get('duration')) or parse_duration(video.get('duration'))
|
||||||
|
timestamp = unified_timestamp(video.get('datePublished'))
|
||||||
|
age_limit = parse_age_limit(video.get('contentRating'))
|
||||||
|
|
||||||
|
data = try_get(
|
||||||
|
video, lambda x: x['trackingData']['properties'], dict) or {}
|
||||||
|
|
||||||
|
creator = data.get('brand') or data.get('network') or video.get('network')
|
||||||
|
|
||||||
|
series = video.get('seriesName') or data.get(
|
||||||
|
'seriesName') or data.get('show')
|
||||||
|
season_number = int_or_none(video.get('seasonNumber'))
|
||||||
|
episode = video.get('name')
|
||||||
|
episode_number = int_or_none(video.get('episodeNumber'))
|
||||||
|
release_year = int_or_none(video.get('releaseYear'))
|
||||||
|
|
||||||
|
if data.get('authRequired'):
|
||||||
|
# TODO: AP
|
||||||
|
pass
|
||||||
|
|
||||||
|
info = {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'duration': duration,
|
||||||
|
'timestamp': timestamp,
|
||||||
|
'age_limit': age_limit,
|
||||||
|
'creator': creator,
|
||||||
|
'series': series,
|
||||||
|
'season_number': season_number,
|
||||||
|
'episode': episode,
|
||||||
|
'episode_number': episode_number,
|
||||||
|
'release_year': release_year,
|
||||||
|
}
|
||||||
|
|
||||||
|
urlh = self._request_webpage(HEADRequest(release_url), video_id)
|
||||||
|
video_url = compat_str(urlh.geturl())
|
||||||
|
|
||||||
|
if UplynkPreplayIE.suitable(video_url):
|
||||||
|
info.update({
|
||||||
|
'_type': 'url_transparent',
|
||||||
|
'url': video_url,
|
||||||
|
'ie_key': UplynkPreplayIE.ie_key(),
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
m3u8_url = self._download_json(release_url, video_id)['playURL']
|
||||||
|
formats = self._extract_m3u8_formats(
|
||||||
|
m3u8_url, video_id, 'mp4',
|
||||||
|
entry_protocol='m3u8_native', m3u8_id='hls')
|
||||||
|
self._sort_formats(formats)
|
||||||
|
info['formats'] = formats
|
||||||
return info
|
return info
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .anvato import AnvatoIE
|
from .anvato import AnvatoIE
|
||||||
from ..utils import js_to_json
|
|
||||||
|
|
||||||
|
|
||||||
class FOX9IE(AnvatoIE):
|
class FOX9IE(AnvatoIE):
|
||||||
@ -34,9 +33,9 @@ class FOX9IE(AnvatoIE):
|
|||||||
|
|
||||||
video_id = self._parse_json(
|
video_id = self._parse_json(
|
||||||
self._search_regex(
|
self._search_regex(
|
||||||
r'AnvatoPlaylist\s*\(\s*(\[.+?\])\s*\)\s*;',
|
r"this\.videosJson\s*=\s*'(\[.+?\])';",
|
||||||
webpage, 'anvato playlist'),
|
webpage, 'anvato playlist'),
|
||||||
video_id, transform_source=js_to_json)[0]['video']
|
video_id)[0]['video']
|
||||||
|
|
||||||
return self._get_anvato_videos(
|
return self._get_anvato_videos(
|
||||||
'anvato_epfox_app_web_prod_b3373168e12f423f41504f207000188daf88251b',
|
'anvato_epfox_app_web_prod_b3373168e12f423f41504f207000188daf88251b',
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
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 ..compat import compat_urlparse
|
from ..compat import compat_urlparse
|
||||||
@ -308,31 +307,32 @@ class FranceTVInfoIE(FranceTVBaseInfoExtractor):
|
|||||||
return self._extract_video(video_id, catalogue)
|
return self._extract_video(video_id, catalogue)
|
||||||
|
|
||||||
|
|
||||||
class GenerationQuoiIE(InfoExtractor):
|
class GenerationWhatIE(InfoExtractor):
|
||||||
IE_NAME = 'france2.fr:generation-quoi'
|
IE_NAME = 'france2.fr:generation-what'
|
||||||
_VALID_URL = r'https?://generation-quoi\.france2\.fr/portrait/(?P<id>[^/?#]+)'
|
_VALID_URL = r'https?://generation-what\.francetv\.fr/[^/]+/video/(?P<id>[^/?#]+)'
|
||||||
|
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
'url': 'http://generation-quoi.france2.fr/portrait/garde-a-vous',
|
'url': 'http://generation-what.francetv.fr/portrait/video/present-arms',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'k7FJX8VBcvvLmX4wA5Q',
|
'id': 'wtvKYUG45iw',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Génération Quoi - Garde à Vous',
|
'title': 'Generation What - Garde à vous - FRA',
|
||||||
'uploader': 'Génération Quoi',
|
'uploader': 'Generation What',
|
||||||
|
'uploader_id': 'UCHH9p1eetWCgt4kXBYCb3_w',
|
||||||
|
'upload_date': '20160411',
|
||||||
},
|
},
|
||||||
'params': {
|
}, {
|
||||||
# It uses Dailymotion
|
'url': 'http://generation-what.francetv.fr/europe/video/present-arms',
|
||||||
'skip_download': True,
|
'only_matching': True,
|
||||||
},
|
}]
|
||||||
}
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
display_id = self._match_id(url)
|
display_id = self._match_id(url)
|
||||||
info_url = compat_urlparse.urljoin(url, '/medias/video/%s.json' % display_id)
|
webpage = self._download_webpage(url, display_id)
|
||||||
info_json = self._download_webpage(info_url, display_id)
|
youtube_id = self._search_regex(
|
||||||
info = json.loads(info_json)
|
r"window\.videoURL\s*=\s*'([0-9A-Za-z_-]{11})';",
|
||||||
return self.url_result('http://www.dailymotion.com/video/%s' % info['id'],
|
webpage, 'youtube id')
|
||||||
ie='Dailymotion')
|
return self.url_result(youtube_id, 'Youtube', youtube_id)
|
||||||
|
|
||||||
|
|
||||||
class CultureboxIE(FranceTVBaseInfoExtractor):
|
class CultureboxIE(FranceTVBaseInfoExtractor):
|
||||||
@ -363,6 +363,6 @@ class CultureboxIE(FranceTVBaseInfoExtractor):
|
|||||||
raise ExtractorError('Video %s is not available' % name, expected=True)
|
raise ExtractorError('Video %s is not available' % name, expected=True)
|
||||||
|
|
||||||
video_id, catalogue = self._search_regex(
|
video_id, catalogue = self._search_regex(
|
||||||
r'"http://videos\.francetv\.fr/video/([^@]+@[^"]+)"', webpage, 'video id').split('@')
|
r'"https?://videos\.francetv\.fr/video/([^@]+@[^"]+)"', webpage, 'video id').split('@')
|
||||||
|
|
||||||
return self._extract_video(video_id, catalogue)
|
return self._extract_video(video_id, catalogue)
|
||||||
|
@ -1,37 +1,34 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
|
||||||
import json
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
class FreespeechIE(InfoExtractor):
|
class FreespeechIE(InfoExtractor):
|
||||||
IE_NAME = 'freespeech.org'
|
IE_NAME = 'freespeech.org'
|
||||||
_VALID_URL = r'https?://(?:www\.)?freespeech\.org/video/(?P<title>.+)'
|
_VALID_URL = r'https?://(?:www\.)?freespeech\.org/stories/(?P<id>.+)'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'add_ie': ['Youtube'],
|
'add_ie': ['Youtube'],
|
||||||
'url': 'https://www.freespeech.org/video/obama-romney-campaign-colorado-ahead-debate-0',
|
'url': 'http://www.freespeech.org/stories/fcc-announces-net-neutrality-rollback-whats-stake/',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'poKsVCZ64uU',
|
'id': 'waRk6IPqyWM',
|
||||||
'ext': 'webm',
|
'ext': 'mp4',
|
||||||
'title': 'Obama, Romney Campaign in Colorado Ahead of Debate',
|
'title': 'What\'s At Stake - Net Neutrality Special',
|
||||||
'description': 'Obama, Romney Campaign in Colorado Ahead of Debate',
|
'description': 'Presented by MNN and FSTV',
|
||||||
'uploader': 'freespeechtv',
|
'upload_date': '20170728',
|
||||||
'uploader_id': 'freespeechtv',
|
'uploader_id': 'freespeechtv',
|
||||||
'upload_date': '20121002',
|
'uploader': 'freespeechtv',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
display_id = self._match_id(url)
|
||||||
title = mobj.group('title')
|
webpage = self._download_webpage(url, display_id)
|
||||||
webpage = self._download_webpage(url, title)
|
youtube_url = self._search_regex(
|
||||||
info_json = self._search_regex(r'jQuery.extend\(Drupal.settings, ({.*?})\);', webpage, 'info')
|
r'data-video-url="([^"]+)"',
|
||||||
info = json.loads(info_json)
|
webpage, 'youtube url')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'_type': 'url',
|
'_type': 'url',
|
||||||
'url': info['jw_player']['basic_video_node_player']['file'],
|
'url': youtube_url,
|
||||||
'ie_key': 'Youtube',
|
'ie_key': 'Youtube',
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user