mirror of
https://github.com/ytdl-org/youtube-dl.git
synced 2025-12-08 15:12:43 +01:00
Compare commits
511 Commits
2016.07.02
...
2016.08.24
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c86f51ee38 | ||
|
|
6e52bbb413 | ||
|
|
05bddcc512 | ||
|
|
1212e9972f | ||
|
|
ccb6570e9e | ||
|
|
18b6216150 | ||
|
|
fb009b7f53 | ||
|
|
3083e4dc07 | ||
|
|
7367bdef23 | ||
|
|
ad31642584 | ||
|
|
c7c43a93ba | ||
|
|
96229e5f95 | ||
|
|
55d119e2a1 | ||
|
|
6d2679ee26 | ||
|
|
afbab5688e | ||
|
|
3d897cc791 | ||
|
|
cf143c4d97 | ||
|
|
ad120ae1c5 | ||
|
|
d0fa172e5f | ||
|
|
f97f9f71e5 | ||
|
|
526656726b | ||
|
|
9b8c554ea7 | ||
|
|
d13bfc07b7 | ||
|
|
efe470e261 | ||
|
|
e3f6b56909 | ||
|
|
b1e676fde8 | ||
|
|
92d4cfa358 | ||
|
|
3d47ee0a9e | ||
|
|
d164a0d41b | ||
|
|
db29af6d36 | ||
|
|
2c6acdfd2d | ||
|
|
fddaa76a59 | ||
|
|
a809446750 | ||
|
|
d8f30a7e66 | ||
|
|
5b1d85754e | ||
|
|
e25586e471 | ||
|
|
292a2301bf | ||
|
|
dabe15701b | ||
|
|
4245f55880 | ||
|
|
5b9d187cc6 | ||
|
|
39e1c4f08c | ||
|
|
19f35402c5 | ||
|
|
70852b47ca | ||
|
|
a9a3b4a081 | ||
|
|
ecc90093f9 | ||
|
|
520251c093 | ||
|
|
55af45fcab | ||
|
|
b82232036a | ||
|
|
e4659b4547 | ||
|
|
9e5751b9fe | ||
|
|
bd1bcd3ea0 | ||
|
|
93a63b36f1 | ||
|
|
8b2dc4c328 | ||
|
|
850837b67a | ||
|
|
13585d7682 | ||
|
|
fd3ec986a4 | ||
|
|
b0d578ff7b | ||
|
|
b0c8f2e9c8 | ||
|
|
51815886a9 | ||
|
|
08a42f9c74 | ||
|
|
e15ad9ef09 | ||
|
|
4e9fee1015 | ||
|
|
7273e5849b | ||
|
|
b505e98784 | ||
|
|
92cd9fd565 | ||
|
|
b3d7dce429 | ||
|
|
a44694ab4e | ||
|
|
ab19b46b88 | ||
|
|
8804f10e6b | ||
|
|
6be17c0870 | ||
|
|
8652770bd2 | ||
|
|
2a1321a272 | ||
|
|
9c0fa60bf3 | ||
|
|
502d87c546 | ||
|
|
b35b0d73d8 | ||
|
|
6e7e4a6edf | ||
|
|
53fef319f1 | ||
|
|
2cabee2a7d | ||
|
|
11f502fac1 | ||
|
|
98affc1a48 | ||
|
|
70a2829fee | ||
|
|
837e56c8ee | ||
|
|
b5ddee8c77 | ||
|
|
fb64adcbd3 | ||
|
|
4f640f2890 | ||
|
|
254e64a20a | ||
|
|
818ac213eb | ||
|
|
cbef4d5c9f | ||
|
|
bf90c46790 | ||
|
|
69eb4d699f | ||
|
|
6d8ec8c3b7 | ||
|
|
760845ce99 | ||
|
|
5c2d087221 | ||
|
|
b6c4e36728 | ||
|
|
1a57b8c18c | ||
|
|
24eb13b1c6 | ||
|
|
525e0316c0 | ||
|
|
7e60ce9cf7 | ||
|
|
e811bcf8f8 | ||
|
|
6103f59095 | ||
|
|
9fa5789279 | ||
|
|
d2ac04674d | ||
|
|
1fd6e30988 | ||
|
|
884cdb6cd9 | ||
|
|
9771b1f901 | ||
|
|
2118fdd1a9 | ||
|
|
320d597c21 | ||
|
|
aaf44a2f47 | ||
|
|
fafabc0712 | ||
|
|
409760a932 | ||
|
|
097eba019d | ||
|
|
73a85620ee | ||
|
|
a560f28c98 | ||
|
|
5ec5461e1a | ||
|
|
542130a5d9 | ||
|
|
82997dad57 | ||
|
|
647a7bf5e8 | ||
|
|
77afa008dd | ||
|
|
db535435b3 | ||
|
|
c2a453b461 | ||
|
|
cd29eaab95 | ||
|
|
52aa7e7476 | ||
|
|
e97c55ee6a | ||
|
|
acfccacad5 | ||
|
|
5f2c2b7936 | ||
|
|
cb55908e51 | ||
|
|
e581224843 | ||
|
|
f50365e91c | ||
|
|
c366f8d30a | ||
|
|
6a26c5f9d5 | ||
|
|
bd6fb007de | ||
|
|
b69b2ff736 | ||
|
|
794e5dcd7e | ||
|
|
f0d3669437 | ||
|
|
98e698f1ff | ||
|
|
3cddb8d6a7 | ||
|
|
990d533ee4 | ||
|
|
b0081562d2 | ||
|
|
fff37cfd4f | ||
|
|
a3be69b7f0 | ||
|
|
0fd1b1624c | ||
|
|
367976d49f | ||
|
|
0aef0771f8 | ||
|
|
0c070681c5 | ||
|
|
30b25d382d | ||
|
|
e5f878c205 | ||
|
|
e2e84aed7e | ||
|
|
b1927f4e8a | ||
|
|
3b9323d96e | ||
|
|
7f832413d6 | ||
|
|
7f2ed47595 | ||
|
|
c3fa77bdef | ||
|
|
57ce8a6d08 | ||
|
|
69d8eeeec5 | ||
|
|
81c13222c6 | ||
|
|
b1ce2ba197 | ||
|
|
5c8411e968 | ||
|
|
cc9c8ce5df | ||
|
|
20ef4123b9 | ||
|
|
4e62d26aa2 | ||
|
|
b657816684 | ||
|
|
9778b3e7ee | ||
|
|
25dd58ca6a | ||
|
|
5e42f8a0ad | ||
|
|
1ad6b891b2 | ||
|
|
7aa589a5e1 | ||
|
|
065bc35489 | ||
|
|
3a380766d1 | ||
|
|
affaea0688 | ||
|
|
77426a087b | ||
|
|
8991844ea2 | ||
|
|
082395d0a0 | ||
|
|
e8ed7354e6 | ||
|
|
1e7f602e2a | ||
|
|
522f6c066d | ||
|
|
321b5e082a | ||
|
|
3711fa1eb2 | ||
|
|
395c74615c | ||
|
|
3dc240e8c6 | ||
|
|
a41a6c5094 | ||
|
|
d71207121d | ||
|
|
b1c6f21c74 | ||
|
|
412abb8760 | ||
|
|
f17d5f6d14 | ||
|
|
6bb801cfaf | ||
|
|
de02d1f4e9 | ||
|
|
e1f93a0a76 | ||
|
|
d21a661bb4 | ||
|
|
b2bd968f4b | ||
|
|
4a01befb34 | ||
|
|
845dfcdc40 | ||
|
|
d92cb46305 | ||
|
|
a8795327ca | ||
|
|
d34995a9e3 | ||
|
|
958849275f | ||
|
|
998f094452 | ||
|
|
aaa42cf0cf | ||
|
|
9fb64c04cd | ||
|
|
f9622868e7 | ||
|
|
37768f9242 | ||
|
|
a1aadd09a4 | ||
|
|
b47a75017b | ||
|
|
e37b54b140 | ||
|
|
c1decda58c | ||
|
|
d3f8e038fe | ||
|
|
ad152e2d95 | ||
|
|
b0af12154e | ||
|
|
d16b3c6677 | ||
|
|
c57244cdb1 | ||
|
|
a7e5f27412 | ||
|
|
089a40955c | ||
|
|
d73ebac100 | ||
|
|
e563c0d73b | ||
|
|
491c42e690 | ||
|
|
7f2339c617 | ||
|
|
8122e79fef | ||
|
|
fe3ad1d456 | ||
|
|
038a5e1a65 | ||
|
|
84bc23b41b | ||
|
|
46933a15d6 | ||
|
|
3859ebeee6 | ||
|
|
d50aca41f8 | ||
|
|
0ca057b965 | ||
|
|
5ca968d0a6 | ||
|
|
f0d31c624e | ||
|
|
08c655906c | ||
|
|
5a993e1692 | ||
|
|
a7d2953073 | ||
|
|
fdd0b8f8e0 | ||
|
|
f65dc41b72 | ||
|
|
962250f7ea | ||
|
|
7dc2a74e0a | ||
|
|
b02b960c6b | ||
|
|
4f427c4be8 | ||
|
|
8a00ea567b | ||
|
|
8895be01fc | ||
|
|
52e7fcfeb7 | ||
|
|
2396062c74 | ||
|
|
14704aeff6 | ||
|
|
3c2c3af059 | ||
|
|
1891ea2d76 | ||
|
|
1094074c04 | ||
|
|
217d5ae013 | ||
|
|
8b40854529 | ||
|
|
6bb0fbf9fb | ||
|
|
8d3b226b83 | ||
|
|
42b7a5afe0 | ||
|
|
899d2bea63 | ||
|
|
9cb0e65d7e | ||
|
|
b070564efb | ||
|
|
ce28252c48 | ||
|
|
3aa9a73554 | ||
|
|
6a9b3b61ea | ||
|
|
45408eb075 | ||
|
|
eafc66855d | ||
|
|
e03d3e6453 | ||
|
|
a70e45f80a | ||
|
|
697655a7c0 | ||
|
|
e382b953f0 | ||
|
|
116e7e0d04 | ||
|
|
cf03e34ad3 | ||
|
|
2903137292 | ||
|
|
9361f2169c | ||
|
|
35aa6c538f | ||
|
|
fa9f1d16b8 | ||
|
|
485fedf6fd | ||
|
|
da0baba5c8 | ||
|
|
bb9f3bfedf | ||
|
|
dbc0b39b91 | ||
|
|
481c5c5137 | ||
|
|
0cacae2807 | ||
|
|
d9d56deadf | ||
|
|
74ba450a81 | ||
|
|
db19df6ca0 | ||
|
|
fbdf8d15d1 | ||
|
|
94aae01548 | ||
|
|
39eef54cf0 | ||
|
|
05c8268c81 | ||
|
|
289a16b4f3 | ||
|
|
7935926baa | ||
|
|
dcbb07c35a | ||
|
|
40090e8d51 | ||
|
|
3e050d51d4 | ||
|
|
ced70c8640 | ||
|
|
9a700deea4 | ||
|
|
dc35ba0eba | ||
|
|
88bd486b9a | ||
|
|
7f8b92e3cf | ||
|
|
35f6e0ff36 | ||
|
|
326fa4e6e5 | ||
|
|
c74299a72c | ||
|
|
10a1bb3a78 | ||
|
|
4d3e543c73 | ||
|
|
05d1e7aaa9 | ||
|
|
a3aa814b77 | ||
|
|
5c32a77cad | ||
|
|
14a28e705b | ||
|
|
cc99d4f826 | ||
|
|
712c7530ff | ||
|
|
0a147785e8 | ||
|
|
59eaf69e33 | ||
|
|
e8be2943a7 | ||
|
|
8fdc538b46 | ||
|
|
9513c1eb17 | ||
|
|
ae6fff4e64 | ||
|
|
5a65668e25 | ||
|
|
f75e6890db | ||
|
|
d9cb92c840 | ||
|
|
94c04a3c79 | ||
|
|
f094834857 | ||
|
|
111de00289 | ||
|
|
b4a131e1a5 | ||
|
|
f1991ce928 | ||
|
|
6548030a17 | ||
|
|
3a8947650b | ||
|
|
1979969f91 | ||
|
|
0673741af3 | ||
|
|
c8e170b209 | ||
|
|
bbe1f3634a | ||
|
|
4671dd41b2 | ||
|
|
f164b97123 | ||
|
|
5275efe30d | ||
|
|
b13647cf3c | ||
|
|
add7d2a0e2 | ||
|
|
e298d3a08c | ||
|
|
fd8c8c7dcd | ||
|
|
9158af16cc | ||
|
|
c6668e4ad1 | ||
|
|
84e8cca48b | ||
|
|
790b06b7d4 | ||
|
|
740d7c49c2 | ||
|
|
4e51ec5f57 | ||
|
|
05087d1b4c | ||
|
|
a66a73ee90 | ||
|
|
8188b923db | ||
|
|
d993a1354d | ||
|
|
e8882e7043 | ||
|
|
1056821799 | ||
|
|
890e6d3309 | ||
|
|
246080d378 | ||
|
|
b1ea680270 | ||
|
|
45550d1039 | ||
|
|
7cdfc4c90f | ||
|
|
af21f56f98 | ||
|
|
1a8f0773b6 | ||
|
|
59cc5bd8bf | ||
|
|
49bc16b95e | ||
|
|
a2f9ca1e67 | ||
|
|
371ddb14fe | ||
|
|
998895dffa | ||
|
|
aadd3ce21f | ||
|
|
ae7b846203 | ||
|
|
21ba7d0981 | ||
|
|
691fbe7f98 | ||
|
|
2e221ca3a8 | ||
|
|
317f7ab634 | ||
|
|
23495d6a39 | ||
|
|
224db034ab | ||
|
|
ad27649be3 | ||
|
|
84571be645 | ||
|
|
7b0d333a7e | ||
|
|
342f0c3682 | ||
|
|
38e0f16a94 | ||
|
|
e910fe2fe4 | ||
|
|
233b58dec7 | ||
|
|
c39b2ed990 | ||
|
|
35ec86689c | ||
|
|
c485959034 | ||
|
|
a0560d8ab8 | ||
|
|
0385aa6199 | ||
|
|
00f4764cb7 | ||
|
|
51c2cd0b83 | ||
|
|
5f5a9d6158 | ||
|
|
2d19fb5072 | ||
|
|
9d865a1af6 | ||
|
|
41aa44259d | ||
|
|
381ff44756 | ||
|
|
7f29cf545a | ||
|
|
7d1219f3e0 | ||
|
|
f1b4af7d79 | ||
|
|
8a8590a617 | ||
|
|
4a7a5e41f7 | ||
|
|
2a49d01600 | ||
|
|
b99af8a51c | ||
|
|
8e7020daef | ||
|
|
a26bcc61c1 | ||
|
|
5c4dcf8172 | ||
|
|
e9fb6a4bbe | ||
|
|
e2dbcaa1bf | ||
|
|
ae01850165 | ||
|
|
c3baaedfc8 | ||
|
|
0b68de3cc1 | ||
|
|
39e9d524e5 | ||
|
|
865b087224 | ||
|
|
3121b25639 | ||
|
|
0286b85c79 | ||
|
|
ab52bb5137 | ||
|
|
61a98b8623 | ||
|
|
6daf34a045 | ||
|
|
c03adf90bd | ||
|
|
0ece114b7b | ||
|
|
5b6a74856b | ||
|
|
ce43100a01 | ||
|
|
8cc9b4016d | ||
|
|
31eeab9f41 | ||
|
|
9558dcec9c | ||
|
|
6e6b70d65f | ||
|
|
d417fd88d0 | ||
|
|
9e4f5dc1e9 | ||
|
|
1251565ee0 | ||
|
|
1f7258a367 | ||
|
|
0af985069b | ||
|
|
0de168f7ed | ||
|
|
95b31e266b | ||
|
|
6b3a3098b5 | ||
|
|
2de624fdd5 | ||
|
|
3fee7f636c | ||
|
|
89e2fff2b7 | ||
|
|
cedc70b292 | ||
|
|
07d7689f2e | ||
|
|
ae8cb5328d | ||
|
|
2e32ac0b9a | ||
|
|
672f01c370 | ||
|
|
e2d616dd30 | ||
|
|
0ab7f4fe2b | ||
|
|
29c4a07776 | ||
|
|
826e911e41 | ||
|
|
30d22dae8e | ||
|
|
ec3518725b | ||
|
|
5f87d845eb | ||
|
|
571808a7aa | ||
|
|
dfe5fa49ae | ||
|
|
01a0c511eb | ||
|
|
b3d30315ce | ||
|
|
882af14d7d | ||
|
|
47335a0efa | ||
|
|
34bc2d9dfd | ||
|
|
08c7af4afa | ||
|
|
f7291a0b7c | ||
|
|
c65aa4e9e1 | ||
|
|
ad213a1d74 | ||
|
|
43f1e4e41e | ||
|
|
54b0e909d5 | ||
|
|
f8752b86ac | ||
|
|
84c237fb8a | ||
|
|
ab49d7a9fa | ||
|
|
b4173f1551 | ||
|
|
2817b99cf2 | ||
|
|
001fffd004 | ||
|
|
0e94b4713d | ||
|
|
a6d3b89feb | ||
|
|
6c26815d63 | ||
|
|
73c4ac2c95 | ||
|
|
84f214d840 | ||
|
|
e3f88be7a9 | ||
|
|
31af3e35e0 | ||
|
|
94a5cff91d | ||
|
|
77082c7b9e | ||
|
|
252a1f75d2 | ||
|
|
5abf513cf8 | ||
|
|
c6054e3201 | ||
|
|
4080530624 | ||
|
|
c25f1a9b63 | ||
|
|
dfaa86b75e | ||
|
|
d9163ae3b6 | ||
|
|
dafafe7cf1 | ||
|
|
81953d1ae5 | ||
|
|
3a212ed62e | ||
|
|
195f084542 | ||
|
|
aa7a455b2e | ||
|
|
6a4e659c93 | ||
|
|
40f3666f6b | ||
|
|
dd801bbe18 | ||
|
|
38cce791c7 | ||
|
|
bf3ae6a543 | ||
|
|
bff98341d5 | ||
|
|
2644e911be | ||
|
|
a5f67895d3 | ||
|
|
15e4b6b758 | ||
|
|
2b28b892d8 | ||
|
|
7507fc98cb | ||
|
|
477b7a8474 | ||
|
|
034a884957 | ||
|
|
64436cb1a4 | ||
|
|
f138873900 | ||
|
|
e793338c88 | ||
|
|
369bb06206 | ||
|
|
2cb31d288e | ||
|
|
c723d1cd8d | ||
|
|
1f55234057 | ||
|
|
04006fae8d | ||
|
|
4cb13d0d6a | ||
|
|
a1f6f5c768 | ||
|
|
05c7feec77 | ||
|
|
bf83024826 | ||
|
|
a0cfd82dda | ||
|
|
1b734adb2d | ||
|
|
9b724d7277 | ||
|
|
c3a5dd3b5d | ||
|
|
e3755a624b | ||
|
|
95cf60e826 | ||
|
|
6b03e1e25d | ||
|
|
712b0b5b70 | ||
|
|
6a424391d9 | ||
|
|
dbf0157a26 | ||
|
|
7deef1ba67 | ||
|
|
fd6ca38262 | ||
|
|
bdafd88da0 | ||
|
|
59bbe4911a | ||
|
|
4f3c5e0627 | ||
|
|
ccff2c404d |
6
.github/ISSUE_TEMPLATE.md
vendored
6
.github/ISSUE_TEMPLATE.md
vendored
@@ -6,8 +6,8 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Make sure you are using the *latest* version: run `youtube-dl --version` and ensure your version is *2016.07.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.
|
### Make sure you are using the *latest* version: run `youtube-dl --version` and ensure your version is *2016.08.24*. 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 **2016.07.02**
|
- [ ] I've **verified** and **I assure** that I'm running youtube-dl **2016.08.24**
|
||||||
|
|
||||||
### 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 [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
|
||||||
@@ -35,7 +35,7 @@ $ youtube-dl -v <your command line>
|
|||||||
[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 2016.07.02
|
[debug] youtube-dl version 2016.08.24
|
||||||
[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: {}
|
||||||
|
|||||||
22
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
22
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
## Please follow the guide below
|
||||||
|
|
||||||
|
- You will be asked some questions, please read them **carefully** and answer honestly
|
||||||
|
- Put an `x` into all the boxes [ ] relevant to your *pull request* (like that [x])
|
||||||
|
- Use *Preview* tab to see how your *pull request* will actually look like
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 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
|
||||||
|
- [ ] [Searched](https://github.com/rg3/youtube-dl/search?q=is%3Apr&type=Issues) the bugtracker for similar pull requests
|
||||||
|
|
||||||
|
### What is the purpose of your *pull request*?
|
||||||
|
- [ ] Bug fix
|
||||||
|
- [ ] New extractor
|
||||||
|
- [ ] New feature
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Description of your *pull request* and other information
|
||||||
|
|
||||||
|
Explanation of your *pull request* in arbitrary form goes here. Please make sure the description explains the purpose and effect of your *pull request* and is worded well enough to be understood. Provide as much context and examples as possible.
|
||||||
@@ -7,9 +7,6 @@ python:
|
|||||||
- "3.4"
|
- "3.4"
|
||||||
- "3.5"
|
- "3.5"
|
||||||
sudo: false
|
sudo: false
|
||||||
install:
|
|
||||||
- bash ./devscripts/install_srelay.sh
|
|
||||||
- export PATH=$PATH:$(pwd)/tmp/srelay-0.4.8b6
|
|
||||||
script: nosetests test --verbose
|
script: nosetests test --verbose
|
||||||
notifications:
|
notifications:
|
||||||
email:
|
email:
|
||||||
|
|||||||
5
AUTHORS
5
AUTHORS
@@ -176,3 +176,8 @@ Déstin Reed
|
|||||||
Roman Tsiupa
|
Roman Tsiupa
|
||||||
Artur Krysiak
|
Artur Krysiak
|
||||||
Jakub Adam Wieczorek
|
Jakub Adam Wieczorek
|
||||||
|
Aleksandar Topuzović
|
||||||
|
Nehal Patel
|
||||||
|
Rob van Bekkum
|
||||||
|
Petr Zvoníček
|
||||||
|
Pratyush Singh
|
||||||
|
|||||||
154
CONTRIBUTING.md
154
CONTRIBUTING.md
@@ -46,7 +46,7 @@ Make sure that someone has not already opened the issue you're trying to open. S
|
|||||||
|
|
||||||
### Why are existing options not enough?
|
### Why are existing options not enough?
|
||||||
|
|
||||||
Before requesting a new feature, please have a quick peek at [the list of supported options](https://github.com/rg3/youtube-dl/blob/master/README.md#synopsis). Many feature requests are for features that actually exist already! Please, absolutely do show off your work in the issue report and detail how the existing similar options do *not* solve your problem.
|
Before requesting a new feature, please have a quick peek at [the list of supported options](https://github.com/rg3/youtube-dl/blob/master/README.md#options). Many feature requests are for features that actually exist already! Please, absolutely do show off your work in the issue report and detail how the existing similar options do *not* solve your problem.
|
||||||
|
|
||||||
### Is there enough context in your bug report?
|
### Is there enough context in your bug report?
|
||||||
|
|
||||||
@@ -97,9 +97,17 @@ If you want to add support for a new site, first of all **make sure** this site
|
|||||||
After you have ensured this site is distributing it's content legally, you can follow this quick list (assuming your service is called `yourextractor`):
|
After you have ensured this site is distributing it's content legally, you can follow this quick list (assuming your service is called `yourextractor`):
|
||||||
|
|
||||||
1. [Fork this repository](https://github.com/rg3/youtube-dl/fork)
|
1. [Fork this repository](https://github.com/rg3/youtube-dl/fork)
|
||||||
2. Check out the source code with `git clone git@github.com:YOUR_GITHUB_USERNAME/youtube-dl.git`
|
2. Check out the source code with:
|
||||||
3. Start a new git branch with `cd youtube-dl; git checkout -b yourextractor`
|
|
||||||
|
git clone git@github.com:YOUR_GITHUB_USERNAME/youtube-dl.git
|
||||||
|
|
||||||
|
3. Start a new git branch with
|
||||||
|
|
||||||
|
cd youtube-dl
|
||||||
|
git checkout -b yourextractor
|
||||||
|
|
||||||
4. Start with this simple template and save it to `youtube_dl/extractor/yourextractor.py`:
|
4. Start with this simple template and save it to `youtube_dl/extractor/yourextractor.py`:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
@@ -143,16 +151,148 @@ After you have ensured this site is distributing it's content legally, you can f
|
|||||||
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.
|
||||||
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. Keep in mind that the only mandatory fields in info dict for successful extraction process are `id`, `title` and either `url` or `formats`, i.e. these are the critical data the extraction does not make any sense without. This means that [any field](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/common.py#L148-L252) apart from aforementioned mandatory ones should be treated **as optional** and extraction should be **tolerate** to situations when sources for these fields can potentially be unavailable (even if they always available at the moment) and **future-proof** in order not to break the extraction of general purpose mandatory fields. For example, if you have some intermediate dict `meta` that is a source of metadata and it has a key `summary` that you want to extract and put into resulting info dict as `description`, you should be ready that this key may be missing from the `meta` dict, i.e. you should extract it as `meta.get('summary')` and not `meta['summary']`. Similarly, you should pass `fatal=False` when extracting data from a webpage with `_search_regex/_html_search_regex`.
|
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+.
|
||||||
9. 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+.
|
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:
|
||||||
10. When the tests pass, [add](http://git-scm.com/docs/git-add) the new files and [commit](http://git-scm.com/docs/git-commit) them and [push](http://git-scm.com/docs/git-push) the result, like this:
|
|
||||||
|
|
||||||
$ git add youtube_dl/extractor/extractors.py
|
$ git add youtube_dl/extractor/extractors.py
|
||||||
$ git add youtube_dl/extractor/yourextractor.py
|
$ git add youtube_dl/extractor/yourextractor.py
|
||||||
$ git commit -m '[yourextractor] Add new extractor'
|
$ git commit -m '[yourextractor] Add new extractor'
|
||||||
$ git push origin yourextractor
|
$ git push origin yourextractor
|
||||||
|
|
||||||
11. Finally, [create a pull request](https://help.github.com/articles/creating-a-pull-request). We'll then review and merge it.
|
10. Finally, [create a pull request](https://help.github.com/articles/creating-a-pull-request). We'll then review and merge it.
|
||||||
|
|
||||||
In any case, thank you very much for your contributions!
|
In any case, thank you very much for your contributions!
|
||||||
|
|
||||||
|
## youtube-dl coding conventions
|
||||||
|
|
||||||
|
This section introduces a guide lines for writing idiomatic, robust and future-proof extractor code.
|
||||||
|
|
||||||
|
Extractors are very fragile by nature since they depend on the layout of the source data provided by 3rd party media hoster out of your control and this layout tend to change. As an extractor implementer your task is not only to write code that will extract media links and metadata correctly but also to minimize code dependency on source's layout changes and even to make the code foresee potential future changes and be ready for that. This is important because it will allow extractor not to break on minor layout changes thus keeping old youtube-dl versions working. Even though this breakage issue is easily fixed by emitting a new version of youtube-dl with fix incorporated all the previous version become broken in all repositories and distros' packages that may not be so prompt in fetching the update from us. Needless to say some may never receive an update at all that is possible for non rolling release distros.
|
||||||
|
|
||||||
|
### Mandatory and optional metafields
|
||||||
|
|
||||||
|
For extraction to work youtube-dl relies on metadata your extractor extracts and provides to youtube-dl expressed by [information dictionary](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/common.py#L75-L257) or simply *info dict*. Only the following meta fields in *info dict* are considered mandatory for successful extraction process by youtube-dl:
|
||||||
|
|
||||||
|
- `id` (media identifier)
|
||||||
|
- `title` (media title)
|
||||||
|
- `url` (media download URL) or `formats`
|
||||||
|
|
||||||
|
In fact only the last option is technically mandatory (i.e. if you can't figure out the download location of the media the extraction does not make any sense). But by convention youtube-dl also treats `id` and `title` to be mandatory. Thus aforementioned metafields are the critical data the extraction does not make any sense without and if any of them fail to be extracted then extractor is considered completely broken.
|
||||||
|
|
||||||
|
[Any field](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/common.py#L149-L257) apart from the aforementioned ones are considered **optional**. That means that extraction should be **tolerate** to situations when sources for these fields can potentially be unavailable (even if they are always available at the moment) and **future-proof** in order not to break the extraction of general purpose mandatory fields.
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
Say you have some source dictionary `meta` that you've fetched as JSON with HTTP request and it has a key `summary`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
meta = self._download_json(url, video_id)
|
||||||
|
```
|
||||||
|
|
||||||
|
Assume at this point `meta`'s layout is:
|
||||||
|
|
||||||
|
```python
|
||||||
|
{
|
||||||
|
...
|
||||||
|
"summary": "some fancy summary text",
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Assume you want to extract `summary` and put into resulting info dict as `description`. Since `description` is optional metafield you should be ready that this key may be missing from the `meta` dict, so that you should extract it like:
|
||||||
|
|
||||||
|
```python
|
||||||
|
description = meta.get('summary') # correct
|
||||||
|
```
|
||||||
|
|
||||||
|
and not like:
|
||||||
|
|
||||||
|
```python
|
||||||
|
description = meta['summary'] # incorrect
|
||||||
|
```
|
||||||
|
|
||||||
|
The latter will break extraction process with `KeyError` if `summary` disappears from `meta` at some time later but with former approach extraction will just go ahead with `description` set to `None` that is perfectly fine (remember `None` is equivalent for absence of data).
|
||||||
|
|
||||||
|
Similarly, you should pass `fatal=False` when extracting optional data from a webpage with `_search_regex`, `_html_search_regex` or similar methods, for instance:
|
||||||
|
|
||||||
|
```python
|
||||||
|
description = self._search_regex(
|
||||||
|
r'<span[^>]+id="title"[^>]*>([^<]+)<',
|
||||||
|
webpage, 'description', fatal=False)
|
||||||
|
```
|
||||||
|
|
||||||
|
With `fatal` set to `False` if `_search_regex` fails to extract `description` it will emit a warning and continue extraction.
|
||||||
|
|
||||||
|
You can also pass `default=<some fallback value>`, for example:
|
||||||
|
|
||||||
|
```python
|
||||||
|
description = self._search_regex(
|
||||||
|
r'<span[^>]+id="title"[^>]*>([^<]+)<',
|
||||||
|
webpage, 'description', default=None)
|
||||||
|
```
|
||||||
|
|
||||||
|
On failure this code will silently continue the extraction with `description` set to `None`. That is useful for metafields that are known to may or may not be present.
|
||||||
|
|
||||||
|
### Provide fallbacks
|
||||||
|
|
||||||
|
When extracting metadata try to provide several scenarios for that. For example if `title` is present in several places/sources try extracting from at least some of them. This would make it more future-proof in case some of the sources became unavailable.
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
Say `meta` from previous example has a `title` and you are about to extract it. Since `title` is mandatory meta field you should end up with something like:
|
||||||
|
|
||||||
|
```python
|
||||||
|
title = meta['title']
|
||||||
|
```
|
||||||
|
|
||||||
|
If `title` disappeares from `meta` in future due to some changes on hoster's side the extraction would fail since `title` is mandatory. That's expected.
|
||||||
|
|
||||||
|
Assume that you have some another source you can extract `title` from, for example `og:title` HTML meta of a `webpage`. In this case you can provide a fallback scenario:
|
||||||
|
|
||||||
|
```python
|
||||||
|
title = meta.get('title') or self._og_search_title(webpage)
|
||||||
|
```
|
||||||
|
|
||||||
|
This code will try to extract from `meta` first and if it fails it will try extracting `og:title` from a `webpage`.
|
||||||
|
|
||||||
|
### Make regular expressions flexible
|
||||||
|
|
||||||
|
When using regular expressions try to write them fuzzy and flexible.
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
Say you need to extract `title` from the following HTML code:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<span style="position: absolute; left: 910px; width: 90px; float: right; z-index: 9999;" class="title">some fancy title</span>
|
||||||
|
```
|
||||||
|
|
||||||
|
The code for that task should look similar to:
|
||||||
|
|
||||||
|
```python
|
||||||
|
title = self._search_regex(
|
||||||
|
r'<span[^>]+class="title"[^>]*>([^<]+)', webpage, 'title')
|
||||||
|
```
|
||||||
|
|
||||||
|
Or even better:
|
||||||
|
|
||||||
|
```python
|
||||||
|
title = self._search_regex(
|
||||||
|
r'<span[^>]+class=(["\'])title\1[^>]*>(?P<title>[^<]+)',
|
||||||
|
webpage, 'title', group='title')
|
||||||
|
```
|
||||||
|
|
||||||
|
Note how you tolerate potential changes in `style` attribute's value or switch from using double quotes to single for `class` attribute:
|
||||||
|
|
||||||
|
The code definitely should not look like:
|
||||||
|
|
||||||
|
```python
|
||||||
|
title = self._search_regex(
|
||||||
|
r'<span style="position: absolute; left: 910px; width: 90px; float: right; z-index: 9999;" class="title">(.*?)</span>',
|
||||||
|
webpage, 'title', group='title')
|
||||||
|
```
|
||||||
|
|
||||||
|
### Use safe conversion functions
|
||||||
|
|
||||||
|
Wrap all extracted numeric data into safe functions from `utils`: `int_or_none`, `float_or_none`. Use them for string to number conversions as well.
|
||||||
|
|
||||||
|
|||||||
478
ChangeLog
Normal file
478
ChangeLog
Normal file
@@ -0,0 +1,478 @@
|
|||||||
|
version 2016.08.24
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
* [youtube] Fix authentication (#10392)
|
||||||
|
* [openload] Fix extraction (#10408)
|
||||||
|
+ [bravotv] Add support for Adobe Pass (#10407)
|
||||||
|
* [bravotv] Fix clip info extraction (#10407)
|
||||||
|
* [eagleplatform] Improve embedded videos detection (#10409)
|
||||||
|
* [awaan] Fix extraction
|
||||||
|
* [mtvservices:embedded] Update config URL
|
||||||
|
+ [abc:iview] Add extractor (#6148)
|
||||||
|
|
||||||
|
|
||||||
|
version 2016.08.22
|
||||||
|
|
||||||
|
Core
|
||||||
|
* Improve formats and subtitles extension auto calculation
|
||||||
|
+ Recognize full unit names in parse_filesize
|
||||||
|
+ Add support for m3u8 manifests in HTML5 multimedia tags
|
||||||
|
* Fix octal/hexadecimal number detection in js_to_json
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
+ [ivi] Add support for 720p and 1080p
|
||||||
|
+ [charlierose] Add new extractor (#10382)
|
||||||
|
* [1tv] Fix extraction (#9249)
|
||||||
|
* [twitch] Renew authentication
|
||||||
|
* [kaltura] Improve subtitles extension calculation
|
||||||
|
+ [zingmp3] Add support for video clips
|
||||||
|
* [zingmp3] Fix extraction (#10041)
|
||||||
|
* [kaltura] Improve subtitles extraction (#10279)
|
||||||
|
* [cultureunplugged] Fix extraction (#10330)
|
||||||
|
+ [cnn] Add support for money.cnn.com (#2797)
|
||||||
|
* [cbsnews] Fix extraction (#10362)
|
||||||
|
* [cbs] Fix extraction (#10393)
|
||||||
|
+ [litv] Support 'promo' URLs (#10385)
|
||||||
|
* [snotr] Fix extraction (#10338)
|
||||||
|
* [n-tv.de] Fix extraction (#10331)
|
||||||
|
* [globo:article] Relax URL and video id regular expressions (#10379)
|
||||||
|
|
||||||
|
|
||||||
|
version 2016.08.19
|
||||||
|
|
||||||
|
Core
|
||||||
|
- Remove output template description from --help
|
||||||
|
* Recognize lowercase units in parse_filesize
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
+ [porncom] Add extractor for porn.com (#2251, #10251)
|
||||||
|
+ [generic] Add support for DBTV embeds
|
||||||
|
* [vk:wallpost] Fix audio extraction for new site layout
|
||||||
|
* [vk] Fix authentication
|
||||||
|
+ [hgtvcom:show] Add extractor for hgtv.com shows (#10365)
|
||||||
|
+ [discoverygo] Add support for another GO network sites
|
||||||
|
|
||||||
|
|
||||||
|
version 2016.08.17
|
||||||
|
|
||||||
|
Core
|
||||||
|
+ Add _get_netrc_login_info
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
* [mofosex] Extract all formats (#10335)
|
||||||
|
+ [generic] Add support for vbox7 embeds
|
||||||
|
+ [vbox7] Add support for embed URLs
|
||||||
|
+ [viafree] Add extractor (#10358)
|
||||||
|
+ [mtg] Add support for viafree URLs (#10358)
|
||||||
|
* [theplatform] Extract all subtitles per language
|
||||||
|
+ [xvideos] Fix HLS extraction (#10356)
|
||||||
|
+ [amcnetworks] Add extractor
|
||||||
|
+ [bbc:playlist] Add support for pagination (#10349)
|
||||||
|
+ [fxnetworks] Add extractor (#9462)
|
||||||
|
* [cbslocal] Fix extraction for SendtoNews-based videos
|
||||||
|
* [sendtonews] Fix extraction
|
||||||
|
* [jwplatform] Extract video id from JWPlayer data
|
||||||
|
- [zippcast] Remove extractor (#10332)
|
||||||
|
+ [viceland] Add extractor (#8799)
|
||||||
|
+ [adobepass] Add base extractor for Adobe Pass Authentication
|
||||||
|
* [life:embed] Improve extraction
|
||||||
|
* [vgtv] Detect geo restricted videos (#10348)
|
||||||
|
+ [uplynk] Add extractor
|
||||||
|
* [xiami] Fix extraction (#10342)
|
||||||
|
|
||||||
|
|
||||||
|
version 2016.08.13
|
||||||
|
|
||||||
|
Core
|
||||||
|
* Show progress for curl external downloader
|
||||||
|
* Forward more options to curl external downloader
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
* [pbs] Fix description extraction
|
||||||
|
* [franceculture] Fix extraction (#10324)
|
||||||
|
* [pornotube] Fix extraction (#10322)
|
||||||
|
* [4tube] Fix metadata extraction (#10321)
|
||||||
|
* [imgur] Fix width and height extraction (#10325)
|
||||||
|
* [expotv] Improve extraction
|
||||||
|
+ [vbox7] Fix extraction (#10309)
|
||||||
|
- [tapely] Remove extractor (#10323)
|
||||||
|
* [muenchentv] Fix extraction (#10313)
|
||||||
|
+ [24video] Add support for .me and .xxx TLDs
|
||||||
|
* [24video] Fix comment count extraction
|
||||||
|
* [sunporno] Add support for embed URLs
|
||||||
|
* [sunporno] Fix metadata extraction (#10316)
|
||||||
|
+ [hgtv] Add extractor for hgtv.ca (#3999)
|
||||||
|
- [pbs] Remove request to unavailable API
|
||||||
|
+ [pbs] Add support for high quality HTTP formats
|
||||||
|
+ [crunchyroll] Add support for HLS formats (#10301)
|
||||||
|
|
||||||
|
|
||||||
|
version 2016.08.12
|
||||||
|
|
||||||
|
Core
|
||||||
|
* Subtitles are now written as is. Newline conversions are disabled. (#10268)
|
||||||
|
+ Recognize more formats in unified_timestamp
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
- [goldenmoustache] Remove extractor (#10298)
|
||||||
|
* [drtuber] Improve title extraction
|
||||||
|
* [drtuber] Make dislike count optional (#10297)
|
||||||
|
* [chirbit] Fix extraction (#10296)
|
||||||
|
* [francetvinfo] Relax URL regular expression
|
||||||
|
* [rtlnl] Relax URL regular expression (#10282)
|
||||||
|
* [formula1] Relax URL regular expression (#10283)
|
||||||
|
* [wat] Improve extraction (#10281)
|
||||||
|
* [ctsnews] Fix extraction
|
||||||
|
|
||||||
|
|
||||||
|
version 2016.08.10
|
||||||
|
|
||||||
|
Core
|
||||||
|
* Make --metadata-from-title non fatal when title does not match the pattern
|
||||||
|
* Introduce options for randomized sleep before each download
|
||||||
|
--min-sleep-interval and --max-sleep-interval (#9930)
|
||||||
|
* Respect default in _search_json_ld
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
+ [uol] Add extractor for uol.com.br (#4263)
|
||||||
|
* [rbmaradio] Fix extraction and extract all formats (#10242)
|
||||||
|
+ [sonyliv] Add extractor for sonyliv.com (#10258)
|
||||||
|
* [aparat] Fix extraction
|
||||||
|
* [cwtv] Extract HTTP formats
|
||||||
|
+ [rozhlas] Add extractor for prehravac.rozhlas.cz (#10253)
|
||||||
|
* [kuwo:singer] Fix extraction
|
||||||
|
|
||||||
|
|
||||||
|
version 2016.08.07
|
||||||
|
|
||||||
|
Core
|
||||||
|
+ Add support for TV Parental Guidelines ratings in parse_age_limit
|
||||||
|
+ Add decode_png (#9706)
|
||||||
|
+ Add support for partOfTVSeries in JSON-LD
|
||||||
|
* Lower master M3U8 manifest preference for better format sorting
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
+ [discoverygo] Add extractor (#10245)
|
||||||
|
* [flipagram] Make JSON-LD extraction non fatal
|
||||||
|
* [generic] Make JSON-LD extraction non fatal
|
||||||
|
+ [bbc] Add support for morph embeds (#10239)
|
||||||
|
* [tnaflixnetworkbase] Improve title extraction
|
||||||
|
* [tnaflix] Fix metadata extraction (#10249)
|
||||||
|
* [fox] Fix theplatform release URL query
|
||||||
|
* [openload] Fix extraction (#9706)
|
||||||
|
* [bbc] Skip duplicate manifest URLs
|
||||||
|
* [bbc] Improve format code
|
||||||
|
+ [bbc] Add support for DASH and F4M
|
||||||
|
* [bbc] Improve format sorting and listing
|
||||||
|
* [bbc] Improve playlist extraction
|
||||||
|
+ [pokemon] Add extractor (#10093)
|
||||||
|
+ [condenast] Add fallback scenario for video info extraction
|
||||||
|
|
||||||
|
|
||||||
|
version 2016.08.06
|
||||||
|
|
||||||
|
Core
|
||||||
|
* Add support for JSON-LD root list entries (#10203)
|
||||||
|
* Improve unified_timestamp
|
||||||
|
* Lower preference of RTSP formats in generic sorting
|
||||||
|
+ Add support for multiple properties in _og_search_property
|
||||||
|
* Improve password hiding from verbose output
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
+ [adultswim] Add support for trailers (#10235)
|
||||||
|
* [archiveorg] Improve extraction (#10219)
|
||||||
|
+ [jwplatform] Add support for playlists
|
||||||
|
+ [jwplatform] Add support for relative URLs
|
||||||
|
* [jwplatform] Improve audio detection
|
||||||
|
+ [tvplay] Capture and output native error message
|
||||||
|
+ [tvplay] Extract series metadata
|
||||||
|
+ [tvplay] Add support for subtitles (#10194)
|
||||||
|
* [tvp] Improve extraction (#7799)
|
||||||
|
* [cbslocal] Fix timestamp parsing (#10213)
|
||||||
|
+ [naver] Add support for subtitles (#8096)
|
||||||
|
* [naver] Improve extraction
|
||||||
|
* [condenast] Improve extraction
|
||||||
|
* [engadget] Relax URL regular expression
|
||||||
|
* [5min] Fix extraction
|
||||||
|
+ [nationalgeographic] Add support for Episode Guide
|
||||||
|
+ [kaltura] Add support for subtitles
|
||||||
|
* [kaltura] Optimize network requests
|
||||||
|
+ [vodplatform] Add extractor for vod-platform.net
|
||||||
|
- [gamekings] Remove extractor
|
||||||
|
* [limelight] Extract HTTP formats
|
||||||
|
* [ntvru] Fix extraction
|
||||||
|
+ [comedycentral] Re-add :tds and :thedailyshow shortnames
|
||||||
|
|
||||||
|
|
||||||
|
version 2016.08.01
|
||||||
|
|
||||||
|
Fixed/improved extractors
|
||||||
|
- [yandexmusic:track] Adapt to changes in track location JSON (#10193)
|
||||||
|
- [bloomberg] Support another form of player (#10187)
|
||||||
|
- [limelight] Skip DRM protected videos
|
||||||
|
- [safari] Relax regular expressions for URL matching (#10202)
|
||||||
|
- [cwtv] Add support for cwtvpr.com (#10196)
|
||||||
|
|
||||||
|
|
||||||
|
version 2016.07.30
|
||||||
|
|
||||||
|
Fixed/improved extractors
|
||||||
|
- [twitch:clips] Sort formats
|
||||||
|
- [tv2] Use m3u8_native
|
||||||
|
- [tv2:article] Fix video detection (#10188)
|
||||||
|
- rtve (#10076)
|
||||||
|
- [dailymotion:playlist] Optimize download archive processing (#10180)
|
||||||
|
|
||||||
|
|
||||||
|
version 2016.07.28
|
||||||
|
|
||||||
|
Fixed/improved extractors
|
||||||
|
- shared (#10170)
|
||||||
|
- soundcloud (#10179)
|
||||||
|
- twitch (#9767)
|
||||||
|
|
||||||
|
|
||||||
|
version 2016.07.26.2
|
||||||
|
|
||||||
|
Fixed/improved extractors
|
||||||
|
- smotri
|
||||||
|
- camdemy
|
||||||
|
- mtv
|
||||||
|
- comedycentral
|
||||||
|
- cmt
|
||||||
|
- cbc
|
||||||
|
- mgtv
|
||||||
|
- orf
|
||||||
|
|
||||||
|
|
||||||
|
version 2016.07.24
|
||||||
|
|
||||||
|
New extractors
|
||||||
|
- arkena (#8682)
|
||||||
|
- lcp (#8682)
|
||||||
|
|
||||||
|
Fixed/improved extractors
|
||||||
|
- facebook (#10151)
|
||||||
|
- dailymail
|
||||||
|
- telegraaf
|
||||||
|
- dcn
|
||||||
|
- onet
|
||||||
|
- tvp
|
||||||
|
|
||||||
|
Miscellaneous
|
||||||
|
- Support $Time$ in DASH manifests
|
||||||
|
|
||||||
|
|
||||||
|
version 2016.07.22
|
||||||
|
|
||||||
|
New extractors
|
||||||
|
- odatv (#9285)
|
||||||
|
|
||||||
|
Fixed/improved extractors
|
||||||
|
- bbc
|
||||||
|
- youjizz (#10131)
|
||||||
|
- youtube (#10140)
|
||||||
|
- pornhub (#10138)
|
||||||
|
- eporner (#10139)
|
||||||
|
|
||||||
|
|
||||||
|
version 2016.07.17
|
||||||
|
|
||||||
|
New extractors
|
||||||
|
- nintendo (#9986)
|
||||||
|
- streamable (#9122)
|
||||||
|
|
||||||
|
Fixed/improved extractors
|
||||||
|
- ard (#10095)
|
||||||
|
- mtv
|
||||||
|
- comedycentral (#10101)
|
||||||
|
- viki (#10098)
|
||||||
|
- spike (#10106)
|
||||||
|
|
||||||
|
Miscellaneous
|
||||||
|
- Improved twitter player detection (#10090)
|
||||||
|
|
||||||
|
|
||||||
|
version 2016.07.16
|
||||||
|
|
||||||
|
New extractors
|
||||||
|
- ninenow (#5181)
|
||||||
|
|
||||||
|
Fixed/improved extractors
|
||||||
|
- rtve (#10076)
|
||||||
|
- brightcove
|
||||||
|
- 3qsdn
|
||||||
|
- syfy (#9087, #3820, #2388)
|
||||||
|
- youtube (#10083)
|
||||||
|
|
||||||
|
Miscellaneous
|
||||||
|
- Fix subtitle embedding for video-only and audio-only files (#10081)
|
||||||
|
|
||||||
|
|
||||||
|
version 2016.07.13
|
||||||
|
|
||||||
|
New extractors
|
||||||
|
- rudo
|
||||||
|
|
||||||
|
Fixed/improved extractors
|
||||||
|
- biobiochiletv
|
||||||
|
- tvplay
|
||||||
|
- dbtv
|
||||||
|
- brightcove
|
||||||
|
- tmz
|
||||||
|
- youtube (#10059)
|
||||||
|
- shahid (#10062)
|
||||||
|
- vk
|
||||||
|
- ellentv (#10067)
|
||||||
|
|
||||||
|
|
||||||
|
version 2016.07.11
|
||||||
|
|
||||||
|
New Extractors
|
||||||
|
- roosterteeth (#9864)
|
||||||
|
|
||||||
|
Fixed/improved extractors
|
||||||
|
- miomio (#9605)
|
||||||
|
- vuclip
|
||||||
|
- youtube
|
||||||
|
- vidzi (#10058)
|
||||||
|
|
||||||
|
|
||||||
|
version 2016.07.09.2
|
||||||
|
|
||||||
|
Fixed/improved extractors
|
||||||
|
- vimeo (#1638)
|
||||||
|
- facebook (#10048)
|
||||||
|
- lynda (#10047)
|
||||||
|
- animeondemand
|
||||||
|
|
||||||
|
Fixed/improved features
|
||||||
|
- Embedding subtitles no longer throws an error with problematic inputs (#9063)
|
||||||
|
|
||||||
|
|
||||||
|
version 2016.07.09.1
|
||||||
|
|
||||||
|
Fixed/improved extractors
|
||||||
|
- youtube
|
||||||
|
- ard
|
||||||
|
- srmediatek (#9373)
|
||||||
|
|
||||||
|
|
||||||
|
version 2016.07.09
|
||||||
|
|
||||||
|
New extractors
|
||||||
|
- Flipagram (#9898)
|
||||||
|
|
||||||
|
Fixed/improved extractors
|
||||||
|
- telecinco
|
||||||
|
- toutv
|
||||||
|
- radiocanada
|
||||||
|
- tweakers (#9516)
|
||||||
|
- lynda
|
||||||
|
- nick (#7542)
|
||||||
|
- polskieradio (#10028)
|
||||||
|
- le
|
||||||
|
- facebook (#9851)
|
||||||
|
- mgtv
|
||||||
|
- animeondemand (#10031)
|
||||||
|
|
||||||
|
Fixed/improved features
|
||||||
|
- `--postprocessor-args` and `--downloader-args` now accepts non-ASCII inputs
|
||||||
|
on non-Windows systems
|
||||||
|
|
||||||
|
|
||||||
|
version 2016.07.07
|
||||||
|
|
||||||
|
New extractors
|
||||||
|
- kamcord (#10001)
|
||||||
|
|
||||||
|
Fixed/improved extractors
|
||||||
|
- spiegel (#10018)
|
||||||
|
- metacafe (#8539, #3253)
|
||||||
|
- onet (#9950)
|
||||||
|
- francetv (#9955)
|
||||||
|
- brightcove (#9965)
|
||||||
|
- daum (#9972)
|
||||||
|
|
||||||
|
|
||||||
|
version 2016.07.06
|
||||||
|
|
||||||
|
Fixed/improved extractors
|
||||||
|
- youtube (#10007, #10009)
|
||||||
|
- xuite
|
||||||
|
- stitcher
|
||||||
|
- spiegel
|
||||||
|
- slideshare
|
||||||
|
- sandia
|
||||||
|
- rtvnh
|
||||||
|
- prosiebensat1
|
||||||
|
- onionstudios
|
||||||
|
|
||||||
|
|
||||||
|
version 2016.07.05
|
||||||
|
|
||||||
|
Fixed/improved extractors
|
||||||
|
- brightcove
|
||||||
|
- yahoo (#9995)
|
||||||
|
- pornhub (#9997)
|
||||||
|
- iqiyi
|
||||||
|
- kaltura (#5557)
|
||||||
|
- la7
|
||||||
|
- Changed features
|
||||||
|
- Rename --cn-verfication-proxy to --geo-verification-proxy
|
||||||
|
Miscellaneous
|
||||||
|
- Add script for displaying downloads statistics
|
||||||
|
|
||||||
|
|
||||||
|
version 2016.07.03.1
|
||||||
|
|
||||||
|
Fixed/improved extractors
|
||||||
|
- theplatform
|
||||||
|
- aenetworks
|
||||||
|
- nationalgeographic
|
||||||
|
- hrti (#9482)
|
||||||
|
- facebook (#5701)
|
||||||
|
- buzzfeed (#5701)
|
||||||
|
- rai (#8617, #9157, #9232, #8552, #8551)
|
||||||
|
- nationalgeographic (#9991)
|
||||||
|
- iqiyi
|
||||||
|
|
||||||
|
|
||||||
|
version 2016.07.03
|
||||||
|
|
||||||
|
New extractors
|
||||||
|
- hrti (#9482)
|
||||||
|
|
||||||
|
Fixed/improved extractors
|
||||||
|
- vk (#9981)
|
||||||
|
- facebook (#9938)
|
||||||
|
- xtube (#9953, #9961)
|
||||||
|
|
||||||
|
|
||||||
|
version 2016.07.02
|
||||||
|
|
||||||
|
New extractors
|
||||||
|
- fusion (#9958)
|
||||||
|
|
||||||
|
Fixed/improved extractors
|
||||||
|
- twitch (#9975)
|
||||||
|
- vine (#9970)
|
||||||
|
- periscope (#9967)
|
||||||
|
- pornhub (#8696)
|
||||||
|
|
||||||
|
|
||||||
|
version 2016.07.01
|
||||||
|
|
||||||
|
New extractors
|
||||||
|
- 9c9media
|
||||||
|
- ctvnews (#2156)
|
||||||
|
- ctv (#4077)
|
||||||
|
|
||||||
|
Fixed/Improved extractors
|
||||||
|
- rds
|
||||||
|
- meta (#8789)
|
||||||
|
- pornhub (#9964)
|
||||||
|
- sixplay (#2183)
|
||||||
|
|
||||||
|
New features
|
||||||
|
- Accept quoted strings across multiple lines (#9940)
|
||||||
4
Makefile
4
Makefile
@@ -94,7 +94,7 @@ _EXTRACTOR_FILES != find youtube_dl/extractor -iname '*.py' -and -not -iname 'la
|
|||||||
youtube_dl/extractor/lazy_extractors.py: devscripts/make_lazy_extractors.py devscripts/lazy_load_template.py $(_EXTRACTOR_FILES)
|
youtube_dl/extractor/lazy_extractors.py: devscripts/make_lazy_extractors.py devscripts/lazy_load_template.py $(_EXTRACTOR_FILES)
|
||||||
$(PYTHON) devscripts/make_lazy_extractors.py $@
|
$(PYTHON) devscripts/make_lazy_extractors.py $@
|
||||||
|
|
||||||
youtube-dl.tar.gz: youtube-dl README.md README.txt youtube-dl.1 youtube-dl.bash-completion youtube-dl.zsh youtube-dl.fish
|
youtube-dl.tar.gz: youtube-dl README.md README.txt youtube-dl.1 youtube-dl.bash-completion youtube-dl.zsh youtube-dl.fish ChangeLog
|
||||||
@tar -czf youtube-dl.tar.gz --transform "s|^|youtube-dl/|" --owner 0 --group 0 \
|
@tar -czf youtube-dl.tar.gz --transform "s|^|youtube-dl/|" --owner 0 --group 0 \
|
||||||
--exclude '*.DS_Store' \
|
--exclude '*.DS_Store' \
|
||||||
--exclude '*.kate-swp' \
|
--exclude '*.kate-swp' \
|
||||||
@@ -107,7 +107,7 @@ youtube-dl.tar.gz: youtube-dl README.md README.txt youtube-dl.1 youtube-dl.bash-
|
|||||||
--exclude 'docs/_build' \
|
--exclude 'docs/_build' \
|
||||||
-- \
|
-- \
|
||||||
bin devscripts test youtube_dl docs \
|
bin devscripts test youtube_dl docs \
|
||||||
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 \
|
||||||
youtube-dl
|
youtube-dl
|
||||||
|
|||||||
207
README.md
207
README.md
@@ -17,7 +17,7 @@ youtube-dl - download videos from youtube.com or other video platforms
|
|||||||
|
|
||||||
To install it right away for all UNIX users (Linux, OS X, etc.), type:
|
To install it right away for all UNIX users (Linux, OS X, etc.), type:
|
||||||
|
|
||||||
sudo curl -L https://yt-dl.org/latest/youtube-dl -o /usr/local/bin/youtube-dl
|
sudo curl -L 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
|
||||||
|
|
||||||
If you do not have curl, you can alternatively use a recent wget:
|
If you do not have curl, you can alternatively use a recent wget:
|
||||||
@@ -103,9 +103,9 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
(experimental)
|
(experimental)
|
||||||
-6, --force-ipv6 Make all connections via IPv6
|
-6, --force-ipv6 Make all connections via IPv6
|
||||||
(experimental)
|
(experimental)
|
||||||
--cn-verification-proxy URL Use this proxy to verify the IP address for
|
--geo-verification-proxy URL Use this proxy to verify the IP address for
|
||||||
some Chinese sites. The default proxy
|
some geo-restricted sites. The default
|
||||||
specified by --proxy (or none, if the
|
proxy specified by --proxy (or none, if the
|
||||||
options is not present) is used for the
|
options is not present) is used for the
|
||||||
actual downloading. (experimental)
|
actual downloading. (experimental)
|
||||||
|
|
||||||
@@ -201,32 +201,8 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
-a, --batch-file FILE File containing URLs to download ('-' for
|
-a, --batch-file FILE File containing URLs to download ('-' for
|
||||||
stdin)
|
stdin)
|
||||||
--id Use only video ID in file name
|
--id Use only video ID in file name
|
||||||
-o, --output TEMPLATE Output filename template. Use %(title)s to
|
-o, --output TEMPLATE Output filename template, see the "OUTPUT
|
||||||
get the title, %(uploader)s for the
|
TEMPLATE" for all the info
|
||||||
uploader name, %(uploader_id)s for the
|
|
||||||
uploader nickname if different,
|
|
||||||
%(autonumber)s to get an automatically
|
|
||||||
incremented number, %(ext)s for the
|
|
||||||
filename extension, %(format)s for the
|
|
||||||
format description (like "22 - 1280x720" or
|
|
||||||
"HD"), %(format_id)s for the unique id of
|
|
||||||
the format (like YouTube's itags: "137"),
|
|
||||||
%(upload_date)s for the upload date
|
|
||||||
(YYYYMMDD), %(extractor)s for the provider
|
|
||||||
(youtube, metacafe, etc), %(id)s for the
|
|
||||||
video id, %(playlist_title)s,
|
|
||||||
%(playlist_id)s, or %(playlist)s (=title if
|
|
||||||
present, ID otherwise) for the playlist the
|
|
||||||
video is in, %(playlist_index)s for the
|
|
||||||
position in the playlist. %(height)s and
|
|
||||||
%(width)s for the width and height of the
|
|
||||||
video format. %(resolution)s for a textual
|
|
||||||
description of the resolution of the video
|
|
||||||
format. %% for a literal percent. Use - to
|
|
||||||
output to stdout. Can also be used to
|
|
||||||
download to a different directory, for
|
|
||||||
example with -o '/my/downloads/%(uploader)s
|
|
||||||
/%(title)s-%(id)s.%(ext)s' .
|
|
||||||
--autonumber-size NUMBER Specify the number of digits in
|
--autonumber-size NUMBER Specify the number of digits in
|
||||||
%(autonumber)s when it is present in output
|
%(autonumber)s when it is present in output
|
||||||
filename template or --auto-number option
|
filename template or --auto-number option
|
||||||
@@ -330,7 +306,15 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
bidirectional text support. Requires bidiv
|
bidirectional text support. Requires bidiv
|
||||||
or fribidi executable in PATH
|
or fribidi executable in PATH
|
||||||
--sleep-interval SECONDS Number of seconds to sleep before each
|
--sleep-interval SECONDS Number of seconds to sleep before each
|
||||||
download.
|
download when used alone or a lower bound
|
||||||
|
of a range for randomized sleep before each
|
||||||
|
download (minimum possible number of
|
||||||
|
seconds to sleep) when used along with
|
||||||
|
--max-sleep-interval.
|
||||||
|
--max-sleep-interval SECONDS Upper bound of a range for randomized sleep
|
||||||
|
before each download (maximum possible
|
||||||
|
number of seconds to sleep). Must only be
|
||||||
|
used along with --min-sleep-interval.
|
||||||
|
|
||||||
## Video Format Options:
|
## Video Format Options:
|
||||||
-f, --format FORMAT Video format code, see the "FORMAT
|
-f, --format FORMAT Video format code, see the "FORMAT
|
||||||
@@ -424,7 +408,7 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
|
|
||||||
# CONFIGURATION
|
# CONFIGURATION
|
||||||
|
|
||||||
You can configure youtube-dl by placing any supported command line option to a configuration file. On Linux and OS X, the system wide configuration file is located at `/etc/youtube-dl.conf` and the user wide configuration file at `~/.config/youtube-dl/config`. On Windows, the user wide configuration file locations are `%APPDATA%\youtube-dl\config.txt` or `C:\Users\<user name>\youtube-dl.conf`.
|
You can configure youtube-dl by placing any supported command line option to a configuration file. On Linux and OS X, the system wide configuration file is located at `/etc/youtube-dl.conf` and the user wide configuration file at `~/.config/youtube-dl/config`. On Windows, the user wide configuration file locations are `%APPDATA%\youtube-dl\config.txt` or `C:\Users\<user name>\youtube-dl.conf`. Note that by default configuration file may not exist so you may need to create it yourself.
|
||||||
|
|
||||||
For example, with the following configuration file youtube-dl will always extract the audio, not copy the mtime, use a proxy and save all videos under `Movies` directory in your home directory:
|
For example, with the following configuration file youtube-dl will always extract the audio, not copy the mtime, use a proxy and save all videos under `Movies` directory in your home directory:
|
||||||
```
|
```
|
||||||
@@ -432,6 +416,7 @@ For example, with the following configuration file youtube-dl will always extrac
|
|||||||
--no-mtime
|
--no-mtime
|
||||||
--proxy 127.0.0.1:3128
|
--proxy 127.0.0.1:3128
|
||||||
-o ~/Movies/%(title)s.%(ext)s
|
-o ~/Movies/%(title)s.%(ext)s
|
||||||
|
# Lines starting with # are comments
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that options in configuration file are just the same options aka switches used in regular command line calls thus there **must be no whitespace** after `-` or `--`, e.g. `-o` or `--proxy` but not `- o` or `-- proxy`.
|
Note that options in configuration file are just the same options aka switches used in regular command line calls thus there **must be no whitespace** after `-` or `--`, e.g. `-o` or `--proxy` but not `- o` or `-- proxy`.
|
||||||
@@ -660,7 +645,11 @@ $ youtube-dl -f 'best[filesize<50M]'
|
|||||||
|
|
||||||
# Download best format available via direct link over HTTP/HTTPS protocol
|
# Download best format available via direct link over HTTP/HTTPS protocol
|
||||||
$ youtube-dl -f '(bestvideo+bestaudio/best)[protocol^=http]'
|
$ youtube-dl -f '(bestvideo+bestaudio/best)[protocol^=http]'
|
||||||
|
|
||||||
|
# Download the best video format and the best audio format without merging them
|
||||||
|
$ youtube-dl -f 'bestvideo,bestaudio' -o '%(title)s.f%(format_id)s.%(ext)s'
|
||||||
```
|
```
|
||||||
|
Note that in the last example, an output template is recommended as bestvideo and bestaudio may have the same file name.
|
||||||
|
|
||||||
|
|
||||||
# VIDEO SELECTION
|
# VIDEO SELECTION
|
||||||
@@ -890,9 +879,17 @@ If you want to add support for a new site, first of all **make sure** this site
|
|||||||
After you have ensured this site is distributing it's content legally, you can follow this quick list (assuming your service is called `yourextractor`):
|
After you have ensured this site is distributing it's content legally, you can follow this quick list (assuming your service is called `yourextractor`):
|
||||||
|
|
||||||
1. [Fork this repository](https://github.com/rg3/youtube-dl/fork)
|
1. [Fork this repository](https://github.com/rg3/youtube-dl/fork)
|
||||||
2. Check out the source code with `git clone git@github.com:YOUR_GITHUB_USERNAME/youtube-dl.git`
|
2. Check out the source code with:
|
||||||
3. Start a new git branch with `cd youtube-dl; git checkout -b yourextractor`
|
|
||||||
|
git clone git@github.com:YOUR_GITHUB_USERNAME/youtube-dl.git
|
||||||
|
|
||||||
|
3. Start a new git branch with
|
||||||
|
|
||||||
|
cd youtube-dl
|
||||||
|
git checkout -b yourextractor
|
||||||
|
|
||||||
4. Start with this simple template and save it to `youtube_dl/extractor/yourextractor.py`:
|
4. Start with this simple template and save it to `youtube_dl/extractor/yourextractor.py`:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
@@ -936,19 +933,151 @@ After you have ensured this site is distributing it's content legally, you can f
|
|||||||
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.
|
||||||
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. Keep in mind that the only mandatory fields in info dict for successful extraction process are `id`, `title` and either `url` or `formats`, i.e. these are the critical data the extraction does not make any sense without. This means that [any field](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/common.py#L148-L252) apart from aforementioned mandatory ones should be treated **as optional** and extraction should be **tolerate** to situations when sources for these fields can potentially be unavailable (even if they always available at the moment) and **future-proof** in order not to break the extraction of general purpose mandatory fields. For example, if you have some intermediate dict `meta` that is a source of metadata and it has a key `summary` that you want to extract and put into resulting info dict as `description`, you should be ready that this key may be missing from the `meta` dict, i.e. you should extract it as `meta.get('summary')` and not `meta['summary']`. Similarly, you should pass `fatal=False` when extracting data from a webpage with `_search_regex/_html_search_regex`.
|
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+.
|
||||||
9. 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+.
|
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:
|
||||||
10. When the tests pass, [add](http://git-scm.com/docs/git-add) the new files and [commit](http://git-scm.com/docs/git-commit) them and [push](http://git-scm.com/docs/git-push) the result, like this:
|
|
||||||
|
|
||||||
$ git add youtube_dl/extractor/extractors.py
|
$ git add youtube_dl/extractor/extractors.py
|
||||||
$ git add youtube_dl/extractor/yourextractor.py
|
$ git add youtube_dl/extractor/yourextractor.py
|
||||||
$ git commit -m '[yourextractor] Add new extractor'
|
$ git commit -m '[yourextractor] Add new extractor'
|
||||||
$ git push origin yourextractor
|
$ git push origin yourextractor
|
||||||
|
|
||||||
11. Finally, [create a pull request](https://help.github.com/articles/creating-a-pull-request). We'll then review and merge it.
|
10. Finally, [create a pull request](https://help.github.com/articles/creating-a-pull-request). We'll then review and merge it.
|
||||||
|
|
||||||
In any case, thank you very much for your contributions!
|
In any case, thank you very much for your contributions!
|
||||||
|
|
||||||
|
## youtube-dl coding conventions
|
||||||
|
|
||||||
|
This section introduces a guide lines for writing idiomatic, robust and future-proof extractor code.
|
||||||
|
|
||||||
|
Extractors are very fragile by nature since they depend on the layout of the source data provided by 3rd party media hoster out of your control and this layout tend to change. As an extractor implementer your task is not only to write code that will extract media links and metadata correctly but also to minimize code dependency on source's layout changes and even to make the code foresee potential future changes and be ready for that. This is important because it will allow extractor not to break on minor layout changes thus keeping old youtube-dl versions working. Even though this breakage issue is easily fixed by emitting a new version of youtube-dl with fix incorporated all the previous version become broken in all repositories and distros' packages that may not be so prompt in fetching the update from us. Needless to say some may never receive an update at all that is possible for non rolling release distros.
|
||||||
|
|
||||||
|
### Mandatory and optional metafields
|
||||||
|
|
||||||
|
For extraction to work youtube-dl relies on metadata your extractor extracts and provides to youtube-dl expressed by [information dictionary](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/common.py#L75-L257) or simply *info dict*. Only the following meta fields in *info dict* are considered mandatory for successful extraction process by youtube-dl:
|
||||||
|
|
||||||
|
- `id` (media identifier)
|
||||||
|
- `title` (media title)
|
||||||
|
- `url` (media download URL) or `formats`
|
||||||
|
|
||||||
|
In fact only the last option is technically mandatory (i.e. if you can't figure out the download location of the media the extraction does not make any sense). But by convention youtube-dl also treats `id` and `title` to be mandatory. Thus aforementioned metafields are the critical data the extraction does not make any sense without and if any of them fail to be extracted then extractor is considered completely broken.
|
||||||
|
|
||||||
|
[Any field](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/common.py#L149-L257) apart from the aforementioned ones are considered **optional**. That means that extraction should be **tolerate** to situations when sources for these fields can potentially be unavailable (even if they are always available at the moment) and **future-proof** in order not to break the extraction of general purpose mandatory fields.
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
Say you have some source dictionary `meta` that you've fetched as JSON with HTTP request and it has a key `summary`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
meta = self._download_json(url, video_id)
|
||||||
|
```
|
||||||
|
|
||||||
|
Assume at this point `meta`'s layout is:
|
||||||
|
|
||||||
|
```python
|
||||||
|
{
|
||||||
|
...
|
||||||
|
"summary": "some fancy summary text",
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Assume you want to extract `summary` and put into resulting info dict as `description`. Since `description` is optional metafield you should be ready that this key may be missing from the `meta` dict, so that you should extract it like:
|
||||||
|
|
||||||
|
```python
|
||||||
|
description = meta.get('summary') # correct
|
||||||
|
```
|
||||||
|
|
||||||
|
and not like:
|
||||||
|
|
||||||
|
```python
|
||||||
|
description = meta['summary'] # incorrect
|
||||||
|
```
|
||||||
|
|
||||||
|
The latter will break extraction process with `KeyError` if `summary` disappears from `meta` at some time later but with former approach extraction will just go ahead with `description` set to `None` that is perfectly fine (remember `None` is equivalent for absence of data).
|
||||||
|
|
||||||
|
Similarly, you should pass `fatal=False` when extracting optional data from a webpage with `_search_regex`, `_html_search_regex` or similar methods, for instance:
|
||||||
|
|
||||||
|
```python
|
||||||
|
description = self._search_regex(
|
||||||
|
r'<span[^>]+id="title"[^>]*>([^<]+)<',
|
||||||
|
webpage, 'description', fatal=False)
|
||||||
|
```
|
||||||
|
|
||||||
|
With `fatal` set to `False` if `_search_regex` fails to extract `description` it will emit a warning and continue extraction.
|
||||||
|
|
||||||
|
You can also pass `default=<some fallback value>`, for example:
|
||||||
|
|
||||||
|
```python
|
||||||
|
description = self._search_regex(
|
||||||
|
r'<span[^>]+id="title"[^>]*>([^<]+)<',
|
||||||
|
webpage, 'description', default=None)
|
||||||
|
```
|
||||||
|
|
||||||
|
On failure this code will silently continue the extraction with `description` set to `None`. That is useful for metafields that are known to may or may not be present.
|
||||||
|
|
||||||
|
### Provide fallbacks
|
||||||
|
|
||||||
|
When extracting metadata try to provide several scenarios for that. For example if `title` is present in several places/sources try extracting from at least some of them. This would make it more future-proof in case some of the sources became unavailable.
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
Say `meta` from previous example has a `title` and you are about to extract it. Since `title` is mandatory meta field you should end up with something like:
|
||||||
|
|
||||||
|
```python
|
||||||
|
title = meta['title']
|
||||||
|
```
|
||||||
|
|
||||||
|
If `title` disappeares from `meta` in future due to some changes on hoster's side the extraction would fail since `title` is mandatory. That's expected.
|
||||||
|
|
||||||
|
Assume that you have some another source you can extract `title` from, for example `og:title` HTML meta of a `webpage`. In this case you can provide a fallback scenario:
|
||||||
|
|
||||||
|
```python
|
||||||
|
title = meta.get('title') or self._og_search_title(webpage)
|
||||||
|
```
|
||||||
|
|
||||||
|
This code will try to extract from `meta` first and if it fails it will try extracting `og:title` from a `webpage`.
|
||||||
|
|
||||||
|
### Make regular expressions flexible
|
||||||
|
|
||||||
|
When using regular expressions try to write them fuzzy and flexible.
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
Say you need to extract `title` from the following HTML code:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<span style="position: absolute; left: 910px; width: 90px; float: right; z-index: 9999;" class="title">some fancy title</span>
|
||||||
|
```
|
||||||
|
|
||||||
|
The code for that task should look similar to:
|
||||||
|
|
||||||
|
```python
|
||||||
|
title = self._search_regex(
|
||||||
|
r'<span[^>]+class="title"[^>]*>([^<]+)', webpage, 'title')
|
||||||
|
```
|
||||||
|
|
||||||
|
Or even better:
|
||||||
|
|
||||||
|
```python
|
||||||
|
title = self._search_regex(
|
||||||
|
r'<span[^>]+class=(["\'])title\1[^>]*>(?P<title>[^<]+)',
|
||||||
|
webpage, 'title', group='title')
|
||||||
|
```
|
||||||
|
|
||||||
|
Note how you tolerate potential changes in `style` attribute's value or switch from using double quotes to single for `class` attribute:
|
||||||
|
|
||||||
|
The code definitely should not look like:
|
||||||
|
|
||||||
|
```python
|
||||||
|
title = self._search_regex(
|
||||||
|
r'<span style="position: absolute; left: 910px; width: 90px; float: right; z-index: 9999;" class="title">(.*?)</span>',
|
||||||
|
webpage, 'title', group='title')
|
||||||
|
```
|
||||||
|
|
||||||
|
### Use safe conversion functions
|
||||||
|
|
||||||
|
Wrap all extracted numeric data into safe functions from `utils`: `int_or_none`, `float_or_none`. Use them for string to number conversions as well.
|
||||||
|
|
||||||
# EMBEDDING YOUTUBE-DL
|
# EMBEDDING YOUTUBE-DL
|
||||||
|
|
||||||
youtube-dl makes the best effort to be a good command-line program, and thus should be callable from any programming language. If you encounter any problems parsing its output, feel free to [create a report](https://github.com/rg3/youtube-dl/issues/new).
|
youtube-dl makes the best effort to be a good command-line program, and thus should be callable from any programming language. If you encounter any problems parsing its output, feel free to [create a report](https://github.com/rg3/youtube-dl/issues/new).
|
||||||
@@ -1055,7 +1184,7 @@ Make sure that someone has not already opened the issue you're trying to open. S
|
|||||||
|
|
||||||
### Why are existing options not enough?
|
### Why are existing options not enough?
|
||||||
|
|
||||||
Before requesting a new feature, please have a quick peek at [the list of supported options](https://github.com/rg3/youtube-dl/blob/master/README.md#synopsis). Many feature requests are for features that actually exist already! Please, absolutely do show off your work in the issue report and detail how the existing similar options do *not* solve your problem.
|
Before requesting a new feature, please have a quick peek at [the list of supported options](https://github.com/rg3/youtube-dl/blob/master/README.md#options). Many feature requests are for features that actually exist already! Please, absolutely do show off your work in the issue report and detail how the existing similar options do *not* solve your problem.
|
||||||
|
|
||||||
### Is there enough context in your bug report?
|
### Is there enough context in your bug report?
|
||||||
|
|
||||||
|
|||||||
@@ -15,13 +15,9 @@ data = urllib.request.urlopen(URL).read()
|
|||||||
with open('download.html.in', 'r', encoding='utf-8') as tmplf:
|
with open('download.html.in', 'r', encoding='utf-8') as tmplf:
|
||||||
template = tmplf.read()
|
template = tmplf.read()
|
||||||
|
|
||||||
md5sum = hashlib.md5(data).hexdigest()
|
|
||||||
sha1sum = hashlib.sha1(data).hexdigest()
|
|
||||||
sha256sum = hashlib.sha256(data).hexdigest()
|
sha256sum = hashlib.sha256(data).hexdigest()
|
||||||
template = template.replace('@PROGRAM_VERSION@', version)
|
template = template.replace('@PROGRAM_VERSION@', version)
|
||||||
template = template.replace('@PROGRAM_URL@', URL)
|
template = template.replace('@PROGRAM_URL@', URL)
|
||||||
template = template.replace('@PROGRAM_MD5SUM@', md5sum)
|
|
||||||
template = template.replace('@PROGRAM_SHA1SUM@', sha1sum)
|
|
||||||
template = template.replace('@PROGRAM_SHA256SUM@', sha256sum)
|
template = template.replace('@PROGRAM_SHA256SUM@', sha256sum)
|
||||||
template = template.replace('@EXE_URL@', versions_info['versions'][version]['exe'][0])
|
template = template.replace('@EXE_URL@', versions_info['versions'][version]['exe'][0])
|
||||||
template = template.replace('@EXE_SHA256SUM@', versions_info['versions'][version]['exe'][1])
|
template = template.replace('@EXE_SHA256SUM@', versions_info['versions'][version]['exe'][1])
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
mkdir -p tmp && cd tmp
|
|
||||||
wget -N http://downloads.sourceforge.net/project/socks-relay/socks-relay/srelay-0.4.8/srelay-0.4.8b6.tar.gz
|
|
||||||
tar zxvf srelay-0.4.8b6.tar.gz
|
|
||||||
cd srelay-0.4.8b6
|
|
||||||
./configure
|
|
||||||
make
|
|
||||||
@@ -54,17 +54,21 @@ def filter_options(readme):
|
|||||||
|
|
||||||
if in_options:
|
if in_options:
|
||||||
if line.lstrip().startswith('-'):
|
if line.lstrip().startswith('-'):
|
||||||
option, description = re.split(r'\s{2,}', line.lstrip())
|
split = re.split(r'\s{2,}', line.lstrip())
|
||||||
split_option = option.split(' ')
|
# Description string may start with `-` as well. If there is
|
||||||
|
# only one piece then it's a description bit not an option.
|
||||||
|
if len(split) > 1:
|
||||||
|
option, description = split
|
||||||
|
split_option = option.split(' ')
|
||||||
|
|
||||||
if not split_option[-1].startswith('-'): # metavar
|
if not split_option[-1].startswith('-'): # metavar
|
||||||
option = ' '.join(split_option[:-1] + ['*%s*' % split_option[-1]])
|
option = ' '.join(split_option[:-1] + ['*%s*' % split_option[-1]])
|
||||||
|
|
||||||
# Pandoc's definition_lists. See http://pandoc.org/README.html
|
# Pandoc's definition_lists. See http://pandoc.org/README.html
|
||||||
# for more information.
|
# for more information.
|
||||||
ret += '\n%s\n: %s\n' % (option, description)
|
ret += '\n%s\n: %s\n' % (option, description)
|
||||||
else:
|
continue
|
||||||
ret += line.lstrip() + '\n'
|
ret += line.lstrip() + '\n'
|
||||||
else:
|
else:
|
||||||
ret += line + '\n'
|
ret += line + '\n'
|
||||||
|
|
||||||
|
|||||||
@@ -71,9 +71,12 @@ fi
|
|||||||
/bin/echo -e "\n### Changing version in version.py..."
|
/bin/echo -e "\n### Changing version in version.py..."
|
||||||
sed -i "s/__version__ = '.*'/__version__ = '$version'/" youtube_dl/version.py
|
sed -i "s/__version__ = '.*'/__version__ = '$version'/" youtube_dl/version.py
|
||||||
|
|
||||||
|
/bin/echo -e "\n### Changing version in ChangeLog..."
|
||||||
|
sed -i "s/<unreleased>/$version/" ChangeLog
|
||||||
|
|
||||||
/bin/echo -e "\n### Committing documentation, templates and youtube_dl/version.py..."
|
/bin/echo -e "\n### Committing documentation, templates and youtube_dl/version.py..."
|
||||||
make README.md CONTRIBUTING.md .github/ISSUE_TEMPLATE.md supportedsites
|
make README.md CONTRIBUTING.md .github/ISSUE_TEMPLATE.md supportedsites
|
||||||
git add README.md CONTRIBUTING.md .github/ISSUE_TEMPLATE.md docs/supportedsites.md youtube_dl/version.py
|
git add README.md CONTRIBUTING.md .github/ISSUE_TEMPLATE.md docs/supportedsites.md youtube_dl/version.py ChangeLog
|
||||||
git commit $gpg_sign_commits -m "release $version"
|
git commit $gpg_sign_commits -m "release $version"
|
||||||
|
|
||||||
/bin/echo -e "\n### Now tagging, signing and pushing..."
|
/bin/echo -e "\n### Now tagging, signing and pushing..."
|
||||||
|
|||||||
47
devscripts/show-downloads-statistics.py
Normal file
47
devscripts/show-downloads-statistics.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import itertools
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
from youtube_dl.compat import (
|
||||||
|
compat_print,
|
||||||
|
compat_urllib_request,
|
||||||
|
)
|
||||||
|
from youtube_dl.utils import format_bytes
|
||||||
|
|
||||||
|
|
||||||
|
def format_size(bytes):
|
||||||
|
return '%s (%d bytes)' % (format_bytes(bytes), bytes)
|
||||||
|
|
||||||
|
|
||||||
|
total_bytes = 0
|
||||||
|
|
||||||
|
for page in itertools.count(1):
|
||||||
|
releases = json.loads(compat_urllib_request.urlopen(
|
||||||
|
'https://api.github.com/repos/rg3/youtube-dl/releases?page=%s' % page
|
||||||
|
).read().decode('utf-8'))
|
||||||
|
|
||||||
|
if not releases:
|
||||||
|
break
|
||||||
|
|
||||||
|
for release in releases:
|
||||||
|
compat_print(release['name'])
|
||||||
|
for asset in release['assets']:
|
||||||
|
asset_name = asset['name']
|
||||||
|
total_bytes += asset['download_count'] * asset['size']
|
||||||
|
if all(not re.match(p, asset_name) for p in (
|
||||||
|
r'^youtube-dl$',
|
||||||
|
r'^youtube-dl-\d{4}\.\d{2}\.\d{2}(?:\.\d+)?\.tar\.gz$',
|
||||||
|
r'^youtube-dl\.exe$')):
|
||||||
|
continue
|
||||||
|
compat_print(
|
||||||
|
' %s size: %s downloads: %d'
|
||||||
|
% (asset_name, format_size(asset['size']), asset['download_count']))
|
||||||
|
|
||||||
|
compat_print('total downloads traffic: %s' % format_size(total_bytes))
|
||||||
@@ -14,7 +14,9 @@
|
|||||||
- **8tracks**
|
- **8tracks**
|
||||||
- **91porn**
|
- **91porn**
|
||||||
- **9gag**
|
- **9gag**
|
||||||
|
- **9now.com.au**
|
||||||
- **abc.net.au**
|
- **abc.net.au**
|
||||||
|
- **abc.net.au:iview**
|
||||||
- **Abc7News**
|
- **Abc7News**
|
||||||
- **abcnews**
|
- **abcnews**
|
||||||
- **abcnews:video**
|
- **abcnews:video**
|
||||||
@@ -34,6 +36,7 @@
|
|||||||
- **AlJazeera**
|
- **AlJazeera**
|
||||||
- **Allocine**
|
- **Allocine**
|
||||||
- **AlphaPorno**
|
- **AlphaPorno**
|
||||||
|
- **AMCNetworks**
|
||||||
- **AnimeOnDemand**
|
- **AnimeOnDemand**
|
||||||
- **anitube.se**
|
- **anitube.se**
|
||||||
- **AnySex**
|
- **AnySex**
|
||||||
@@ -45,6 +48,7 @@
|
|||||||
- **archive.org**: archive.org videos
|
- **archive.org**: archive.org videos
|
||||||
- **ARD**
|
- **ARD**
|
||||||
- **ARD:mediathek**
|
- **ARD:mediathek**
|
||||||
|
- **Arkena**
|
||||||
- **arte.tv**
|
- **arte.tv**
|
||||||
- **arte.tv:+7**
|
- **arte.tv:+7**
|
||||||
- **arte.tv:cinema**
|
- **arte.tv:cinema**
|
||||||
@@ -63,6 +67,10 @@
|
|||||||
- **audiomack**
|
- **audiomack**
|
||||||
- **audiomack:album**
|
- **audiomack:album**
|
||||||
- **auroravid**: AuroraVid
|
- **auroravid**: AuroraVid
|
||||||
|
- **AWAAN**
|
||||||
|
- **awaan:live**
|
||||||
|
- **awaan:season**
|
||||||
|
- **awaan:video**
|
||||||
- **Azubu**
|
- **Azubu**
|
||||||
- **AzubuLive**
|
- **AzubuLive**
|
||||||
- **BaiduVideo**: 百度视频
|
- **BaiduVideo**: 百度视频
|
||||||
@@ -118,6 +126,7 @@
|
|||||||
- **CDA**
|
- **CDA**
|
||||||
- **CeskaTelevize**
|
- **CeskaTelevize**
|
||||||
- **channel9**: Channel 9
|
- **channel9**: Channel 9
|
||||||
|
- **CharlieRose**
|
||||||
- **Chaturbate**
|
- **Chaturbate**
|
||||||
- **Chilloutzone**
|
- **Chilloutzone**
|
||||||
- **chirbit**
|
- **chirbit**
|
||||||
@@ -140,7 +149,8 @@
|
|||||||
- **CollegeRama**
|
- **CollegeRama**
|
||||||
- **ComCarCoff**
|
- **ComCarCoff**
|
||||||
- **ComedyCentral**
|
- **ComedyCentral**
|
||||||
- **ComedyCentralShows**: The Daily Show / The Colbert Report
|
- **ComedyCentralShortname**
|
||||||
|
- **ComedyCentralTV**
|
||||||
- **CondeNast**: Condé Nast media group: Allure, Architectural Digest, Ars Technica, Bon Appétit, Brides, Condé Nast, Condé Nast Traveler, Details, Epicurious, GQ, Glamour, Golf Digest, SELF, Teen Vogue, The New Yorker, Vanity Fair, Vogue, W Magazine, WIRED
|
- **CondeNast**: Condé Nast media group: Allure, Architectural Digest, Ars Technica, Bon Appétit, Brides, Condé Nast, Condé Nast Traveler, Details, Epicurious, GQ, Glamour, Golf Digest, SELF, Teen Vogue, The New Yorker, Vanity Fair, Vogue, W Magazine, WIRED
|
||||||
- **Coub**
|
- **Coub**
|
||||||
- **Cracked**
|
- **Cracked**
|
||||||
@@ -167,10 +177,6 @@
|
|||||||
- **daum.net:playlist**
|
- **daum.net:playlist**
|
||||||
- **daum.net:user**
|
- **daum.net:user**
|
||||||
- **DBTV**
|
- **DBTV**
|
||||||
- **DCN**
|
|
||||||
- **dcn:live**
|
|
||||||
- **dcn:season**
|
|
||||||
- **dcn:video**
|
|
||||||
- **DctpTv**
|
- **DctpTv**
|
||||||
- **DeezerPlaylist**
|
- **DeezerPlaylist**
|
||||||
- **defense.gouv.fr**
|
- **defense.gouv.fr**
|
||||||
@@ -179,6 +185,7 @@
|
|||||||
- **DigitallySpeaking**
|
- **DigitallySpeaking**
|
||||||
- **Digiteka**
|
- **Digiteka**
|
||||||
- **Discovery**
|
- **Discovery**
|
||||||
|
- **DiscoveryGo**
|
||||||
- **Dotsub**
|
- **Dotsub**
|
||||||
- **DouyuTV**: 斗鱼
|
- **DouyuTV**: 斗鱼
|
||||||
- **DPlay**
|
- **DPlay**
|
||||||
@@ -224,6 +231,7 @@
|
|||||||
- **Firstpost**
|
- **Firstpost**
|
||||||
- **FiveTV**
|
- **FiveTV**
|
||||||
- **Flickr**
|
- **Flickr**
|
||||||
|
- **Flipagram**
|
||||||
- **Folketinget**: Folketinget (ft.dk; Danish parliament)
|
- **Folketinget**: Folketinget (ft.dk; Danish parliament)
|
||||||
- **FootyRoom**
|
- **FootyRoom**
|
||||||
- **Formula1**
|
- **Formula1**
|
||||||
@@ -233,7 +241,6 @@
|
|||||||
- **FoxSports**
|
- **FoxSports**
|
||||||
- **france2.fr:generation-quoi**
|
- **france2.fr:generation-quoi**
|
||||||
- **FranceCulture**
|
- **FranceCulture**
|
||||||
- **FranceCultureEmission**
|
|
||||||
- **FranceInter**
|
- **FranceInter**
|
||||||
- **francetv**: France 2, 3, 4, 5 and Ô
|
- **francetv**: France 2, 3, 4, 5 and Ô
|
||||||
- **francetvinfo.fr**
|
- **francetvinfo.fr**
|
||||||
@@ -243,8 +250,8 @@
|
|||||||
- **Funimation**
|
- **Funimation**
|
||||||
- **FunnyOrDie**
|
- **FunnyOrDie**
|
||||||
- **Fusion**
|
- **Fusion**
|
||||||
|
- **FXNetworks**
|
||||||
- **GameInformer**
|
- **GameInformer**
|
||||||
- **Gamekings**
|
|
||||||
- **GameOne**
|
- **GameOne**
|
||||||
- **gameone:playlist**
|
- **gameone:playlist**
|
||||||
- **Gamersyde**
|
- **Gamersyde**
|
||||||
@@ -261,7 +268,6 @@
|
|||||||
- **GloboArticle**
|
- **GloboArticle**
|
||||||
- **GodTube**
|
- **GodTube**
|
||||||
- **GodTV**
|
- **GodTV**
|
||||||
- **GoldenMoustache**
|
|
||||||
- **Golem**
|
- **Golem**
|
||||||
- **GoogleDrive**
|
- **GoogleDrive**
|
||||||
- **Goshgay**
|
- **Goshgay**
|
||||||
@@ -274,6 +280,8 @@
|
|||||||
- **HellPorno**
|
- **HellPorno**
|
||||||
- **Helsinki**: helsinki.fi
|
- **Helsinki**: helsinki.fi
|
||||||
- **HentaiStigma**
|
- **HentaiStigma**
|
||||||
|
- **HGTV**
|
||||||
|
- **hgtv.com:show**
|
||||||
- **HistoricFilms**
|
- **HistoricFilms**
|
||||||
- **history:topic**: History.com Topic
|
- **history:topic**: History.com Topic
|
||||||
- **hitbox**
|
- **hitbox**
|
||||||
@@ -283,6 +291,8 @@
|
|||||||
- **HotStar**
|
- **HotStar**
|
||||||
- **Howcast**
|
- **Howcast**
|
||||||
- **HowStuffWorks**
|
- **HowStuffWorks**
|
||||||
|
- **HRTi**
|
||||||
|
- **HRTiPlaylist**
|
||||||
- **HuffPost**: Huffington Post
|
- **HuffPost**: Huffington Post
|
||||||
- **Hypem**
|
- **Hypem**
|
||||||
- **Iconosquare**
|
- **Iconosquare**
|
||||||
@@ -310,6 +320,7 @@
|
|||||||
- **jpopsuki.tv**
|
- **jpopsuki.tv**
|
||||||
- **JWPlatform**
|
- **JWPlatform**
|
||||||
- **Kaltura**
|
- **Kaltura**
|
||||||
|
- **Kamcord**
|
||||||
- **KanalPlay**: Kanal 5/9/11 Play
|
- **KanalPlay**: Kanal 5/9/11 Play
|
||||||
- **Kankan**
|
- **Kankan**
|
||||||
- **Karaoketv**
|
- **Karaoketv**
|
||||||
@@ -329,8 +340,10 @@
|
|||||||
- **kuwo:mv**: 酷我音乐 - MV
|
- **kuwo:mv**: 酷我音乐 - MV
|
||||||
- **kuwo:singer**: 酷我音乐 - 歌手
|
- **kuwo:singer**: 酷我音乐 - 歌手
|
||||||
- **kuwo:song**: 酷我音乐
|
- **kuwo:song**: 酷我音乐
|
||||||
- **la7.tv**
|
- **la7.it**
|
||||||
- **Laola1Tv**
|
- **Laola1Tv**
|
||||||
|
- **Lcp**
|
||||||
|
- **LcpPlay**
|
||||||
- **Le**: 乐视网
|
- **Le**: 乐视网
|
||||||
- **Learnr**
|
- **Learnr**
|
||||||
- **Lecture2Go**
|
- **Lecture2Go**
|
||||||
@@ -390,9 +403,9 @@
|
|||||||
- **Moviezine**
|
- **Moviezine**
|
||||||
- **MPORA**
|
- **MPORA**
|
||||||
- **MSN**
|
- **MSN**
|
||||||
|
- **mtg**: MTG services
|
||||||
- **MTV**
|
- **MTV**
|
||||||
- **mtv.de**
|
- **mtv.de**
|
||||||
- **mtviggy.com**
|
|
||||||
- **mtvservices:embedded**
|
- **mtvservices:embedded**
|
||||||
- **MuenchenTV**: münchen.tv
|
- **MuenchenTV**: münchen.tv
|
||||||
- **MusicPlayOn**
|
- **MusicPlayOn**
|
||||||
@@ -408,7 +421,8 @@
|
|||||||
- **MyVidster**
|
- **MyVidster**
|
||||||
- **n-tv.de**
|
- **n-tv.de**
|
||||||
- **natgeo**
|
- **natgeo**
|
||||||
- **natgeo:channel**
|
- **natgeo:episodeguide**
|
||||||
|
- **natgeo:video**
|
||||||
- **Naver**
|
- **Naver**
|
||||||
- **NBA**
|
- **NBA**
|
||||||
- **NBC**
|
- **NBC**
|
||||||
@@ -432,7 +446,6 @@
|
|||||||
- **Newstube**
|
- **Newstube**
|
||||||
- **NextMedia**: 蘋果日報
|
- **NextMedia**: 蘋果日報
|
||||||
- **NextMediaActionNews**: 蘋果日報 - 動新聞
|
- **NextMediaActionNews**: 蘋果日報 - 動新聞
|
||||||
- **nextmovie.com**
|
|
||||||
- **nfb**: National Film Board of Canada
|
- **nfb**: National Film Board of Canada
|
||||||
- **nfl.com**
|
- **nfl.com**
|
||||||
- **nhl.com**
|
- **nhl.com**
|
||||||
@@ -444,6 +457,7 @@
|
|||||||
- **niconico**: ニコニコ動画
|
- **niconico**: ニコニコ動画
|
||||||
- **NiconicoPlaylist**
|
- **NiconicoPlaylist**
|
||||||
- **NineCNineMedia**
|
- **NineCNineMedia**
|
||||||
|
- **Nintendo**
|
||||||
- **njoy**: N-JOY
|
- **njoy**: N-JOY
|
||||||
- **njoy:embed**
|
- **njoy:embed**
|
||||||
- **Noco**
|
- **Noco**
|
||||||
@@ -471,9 +485,12 @@
|
|||||||
- **NYTimes**
|
- **NYTimes**
|
||||||
- **NYTimesArticle**
|
- **NYTimesArticle**
|
||||||
- **ocw.mit.edu**
|
- **ocw.mit.edu**
|
||||||
|
- **OdaTV**
|
||||||
- **Odnoklassniki**
|
- **Odnoklassniki**
|
||||||
- **OktoberfestTV**
|
- **OktoberfestTV**
|
||||||
- **on.aol.com**
|
- **on.aol.com**
|
||||||
|
- **onet.tv**
|
||||||
|
- **onet.tv:channel**
|
||||||
- **OnionStudios**
|
- **OnionStudios**
|
||||||
- **Ooyala**
|
- **Ooyala**
|
||||||
- **OoyalaExternal**
|
- **OoyalaExternal**
|
||||||
@@ -507,7 +524,9 @@
|
|||||||
- **plus.google**: Google Plus
|
- **plus.google**: Google Plus
|
||||||
- **pluzz.francetv.fr**
|
- **pluzz.francetv.fr**
|
||||||
- **podomatic**
|
- **podomatic**
|
||||||
|
- **Pokemon**
|
||||||
- **PolskieRadio**
|
- **PolskieRadio**
|
||||||
|
- **PornCom**
|
||||||
- **PornHd**
|
- **PornHd**
|
||||||
- **PornHub**: PornHub and Thumbzilla
|
- **PornHub**: PornHub and Thumbzilla
|
||||||
- **PornHubPlaylist**
|
- **PornHubPlaylist**
|
||||||
@@ -548,8 +567,10 @@
|
|||||||
- **RICE**
|
- **RICE**
|
||||||
- **RingTV**
|
- **RingTV**
|
||||||
- **RockstarGames**
|
- **RockstarGames**
|
||||||
|
- **RoosterTeeth**
|
||||||
- **RottenTomatoes**
|
- **RottenTomatoes**
|
||||||
- **Roxwel**
|
- **Roxwel**
|
||||||
|
- **Rozhlas**
|
||||||
- **RTBF**
|
- **RTBF**
|
||||||
- **rte**: Raidió Teilifís Éireann TV
|
- **rte**: Raidió Teilifís Éireann TV
|
||||||
- **rte:radio**: Raidió Teilifís Éireann radio
|
- **rte:radio**: Raidió Teilifís Éireann radio
|
||||||
@@ -560,7 +581,9 @@
|
|||||||
- **rtve.es:alacarta**: RTVE a la carta
|
- **rtve.es:alacarta**: RTVE a la carta
|
||||||
- **rtve.es:infantil**: RTVE infantil
|
- **rtve.es:infantil**: RTVE infantil
|
||||||
- **rtve.es:live**: RTVE.es live streams
|
- **rtve.es:live**: RTVE.es live streams
|
||||||
|
- **rtve.es:television**
|
||||||
- **RTVNH**
|
- **RTVNH**
|
||||||
|
- **Rudo**
|
||||||
- **RUHD**
|
- **RUHD**
|
||||||
- **RulePorn**
|
- **RulePorn**
|
||||||
- **rutube**: Rutube videos
|
- **rutube**: Rutube videos
|
||||||
@@ -605,6 +628,7 @@
|
|||||||
- **smotri:user**: Smotri.com user videos
|
- **smotri:user**: Smotri.com user videos
|
||||||
- **Snotr**
|
- **Snotr**
|
||||||
- **Sohu**
|
- **Sohu**
|
||||||
|
- **SonyLIV**
|
||||||
- **soundcloud**
|
- **soundcloud**
|
||||||
- **soundcloud:playlist**
|
- **soundcloud:playlist**
|
||||||
- **soundcloud:search**: Soundcloud search
|
- **soundcloud:search**: Soundcloud search
|
||||||
@@ -635,6 +659,7 @@
|
|||||||
- **stanfordoc**: Stanford Open ClassRoom
|
- **stanfordoc**: Stanford Open ClassRoom
|
||||||
- **Steam**
|
- **Steam**
|
||||||
- **Stitcher**
|
- **Stitcher**
|
||||||
|
- **Streamable**
|
||||||
- **streamcloud.eu**
|
- **streamcloud.eu**
|
||||||
- **StreamCZ**
|
- **StreamCZ**
|
||||||
- **StreetVoice**
|
- **StreetVoice**
|
||||||
@@ -646,7 +671,6 @@
|
|||||||
- **SztvHu**
|
- **SztvHu**
|
||||||
- **Tagesschau**
|
- **Tagesschau**
|
||||||
- **tagesschau:player**
|
- **tagesschau:player**
|
||||||
- **Tapely**
|
|
||||||
- **Tass**
|
- **Tass**
|
||||||
- **TDSLifeway**
|
- **TDSLifeway**
|
||||||
- **teachertube**: teachertube.com videos
|
- **teachertube**: teachertube.com videos
|
||||||
@@ -682,6 +706,7 @@
|
|||||||
- **TNAFlix**
|
- **TNAFlix**
|
||||||
- **TNAFlixNetworkEmbed**
|
- **TNAFlixNetworkEmbed**
|
||||||
- **toggle**
|
- **toggle**
|
||||||
|
- **Tosh**: Tosh.0
|
||||||
- **tou.tv**
|
- **tou.tv**
|
||||||
- **Toypics**: Toypics user profile
|
- **Toypics**: Toypics user profile
|
||||||
- **ToypicsUser**: Toypics user profile
|
- **ToypicsUser**: Toypics user profile
|
||||||
@@ -711,8 +736,8 @@
|
|||||||
- **tvigle**: Интернет-телевидение Tvigle.ru
|
- **tvigle**: Интернет-телевидение Tvigle.ru
|
||||||
- **tvland.com**
|
- **tvland.com**
|
||||||
- **tvp**: Telewizja Polska
|
- **tvp**: Telewizja Polska
|
||||||
|
- **tvp:embed**: Telewizja Polska
|
||||||
- **tvp:series**
|
- **tvp:series**
|
||||||
- **TVPlay**: TV3Play and related services
|
|
||||||
- **Tweakers**
|
- **Tweakers**
|
||||||
- **twitch:chapter**
|
- **twitch:chapter**
|
||||||
- **twitch:clips**
|
- **twitch:clips**
|
||||||
@@ -728,6 +753,9 @@
|
|||||||
- **udemy:course**
|
- **udemy:course**
|
||||||
- **UDNEmbed**: 聯合影音
|
- **UDNEmbed**: 聯合影音
|
||||||
- **Unistra**
|
- **Unistra**
|
||||||
|
- **uol.com.br**
|
||||||
|
- **uplynk**
|
||||||
|
- **uplynk:preplay**
|
||||||
- **Urort**: NRK P3 Urørt
|
- **Urort**: NRK P3 Urørt
|
||||||
- **URPlay**
|
- **URPlay**
|
||||||
- **USAToday**
|
- **USAToday**
|
||||||
@@ -745,7 +773,9 @@
|
|||||||
- **VevoPlaylist**
|
- **VevoPlaylist**
|
||||||
- **VGTV**: VGTV, BTTV, FTV, Aftenposten and Aftonbladet
|
- **VGTV**: VGTV, BTTV, FTV, Aftenposten and Aftonbladet
|
||||||
- **vh1.com**
|
- **vh1.com**
|
||||||
|
- **Viafree**
|
||||||
- **Vice**
|
- **Vice**
|
||||||
|
- **Viceland**
|
||||||
- **ViceShow**
|
- **ViceShow**
|
||||||
- **Vidbit**
|
- **Vidbit**
|
||||||
- **Viddler**
|
- **Viddler**
|
||||||
@@ -787,8 +817,10 @@
|
|||||||
- **vine:user**
|
- **vine:user**
|
||||||
- **vk**: VK
|
- **vk**: VK
|
||||||
- **vk:uservideos**: VK - User's Videos
|
- **vk:uservideos**: VK - User's Videos
|
||||||
|
- **vk:wallpost**
|
||||||
- **vlive**
|
- **vlive**
|
||||||
- **Vodlocker**
|
- **Vodlocker**
|
||||||
|
- **VODPlatform**
|
||||||
- **VoiceRepublic**
|
- **VoiceRepublic**
|
||||||
- **VoxMedia**
|
- **VoxMedia**
|
||||||
- **Vporn**
|
- **Vporn**
|
||||||
@@ -855,6 +887,7 @@
|
|||||||
- **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)
|
||||||
@@ -862,6 +895,4 @@
|
|||||||
- **Zapiks**
|
- **Zapiks**
|
||||||
- **ZDF**
|
- **ZDF**
|
||||||
- **ZDFChannel**
|
- **ZDFChannel**
|
||||||
- **zingmp3:album**: mp3.zing.vn albums
|
- **zingmp3**: mp3.zing.vn
|
||||||
- **zingmp3:song**: mp3.zing.vn songs
|
|
||||||
- **ZippCast**
|
|
||||||
|
|||||||
@@ -48,6 +48,9 @@ class TestInfoExtractor(unittest.TestCase):
|
|||||||
self.assertEqual(ie._og_search_property('foobar', html), 'Foo')
|
self.assertEqual(ie._og_search_property('foobar', html), 'Foo')
|
||||||
self.assertEqual(ie._og_search_property('test1', html), 'foo > < bar')
|
self.assertEqual(ie._og_search_property('test1', html), 'foo > < bar')
|
||||||
self.assertEqual(ie._og_search_property('test2', html), 'foo >//< bar')
|
self.assertEqual(ie._og_search_property('test2', html), 'foo >//< bar')
|
||||||
|
self.assertEqual(ie._og_search_property(('test0', 'test1'), html), 'foo > < bar')
|
||||||
|
self.assertRaises(RegexNotFoundError, ie._og_search_property, 'test0', html, None, fatal=True)
|
||||||
|
self.assertRaises(RegexNotFoundError, ie._og_search_property, ('test0', 'test00'), html, None, fatal=True)
|
||||||
|
|
||||||
def test_html_search_meta(self):
|
def test_html_search_meta(self):
|
||||||
ie = self.ie
|
ie = self.ie
|
||||||
|
|||||||
@@ -335,6 +335,40 @@ class TestFormatSelection(unittest.TestCase):
|
|||||||
downloaded = ydl.downloaded_info_dicts[0]
|
downloaded = ydl.downloaded_info_dicts[0]
|
||||||
self.assertEqual(downloaded['format_id'], f1['format_id'])
|
self.assertEqual(downloaded['format_id'], f1['format_id'])
|
||||||
|
|
||||||
|
def test_audio_only_extractor_format_selection(self):
|
||||||
|
# For extractors with incomplete formats (all formats are audio-only or
|
||||||
|
# video-only) best and worst should fallback to corresponding best/worst
|
||||||
|
# video-only or audio-only formats (as per
|
||||||
|
# https://github.com/rg3/youtube-dl/pull/5556)
|
||||||
|
formats = [
|
||||||
|
{'format_id': 'low', 'ext': 'mp3', 'preference': 1, 'vcodec': 'none', 'url': TEST_URL},
|
||||||
|
{'format_id': 'high', 'ext': 'mp3', 'preference': 2, 'vcodec': 'none', 'url': TEST_URL},
|
||||||
|
]
|
||||||
|
info_dict = _make_result(formats)
|
||||||
|
|
||||||
|
ydl = YDL({'format': 'best'})
|
||||||
|
ydl.process_ie_result(info_dict.copy())
|
||||||
|
downloaded = ydl.downloaded_info_dicts[0]
|
||||||
|
self.assertEqual(downloaded['format_id'], 'high')
|
||||||
|
|
||||||
|
ydl = YDL({'format': 'worst'})
|
||||||
|
ydl.process_ie_result(info_dict.copy())
|
||||||
|
downloaded = ydl.downloaded_info_dicts[0]
|
||||||
|
self.assertEqual(downloaded['format_id'], 'low')
|
||||||
|
|
||||||
|
def test_format_not_available(self):
|
||||||
|
formats = [
|
||||||
|
{'format_id': 'regular', 'ext': 'mp4', 'height': 360, 'url': TEST_URL},
|
||||||
|
{'format_id': 'video', 'ext': 'mp4', 'height': 720, 'acodec': 'none', 'url': TEST_URL},
|
||||||
|
]
|
||||||
|
info_dict = _make_result(formats)
|
||||||
|
|
||||||
|
# This must fail since complete video-audio format does not match filter
|
||||||
|
# and extractor does not provide incomplete only formats (i.e. only
|
||||||
|
# video-only or audio-only).
|
||||||
|
ydl = YDL({'format': 'best[height>360]'})
|
||||||
|
self.assertRaises(ExtractorError, ydl.process_ie_result, info_dict.copy())
|
||||||
|
|
||||||
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})
|
||||||
|
|||||||
@@ -101,8 +101,6 @@ class TestAllURLsMatching(unittest.TestCase):
|
|||||||
self.assertMatch(':ytsubs', ['youtube:subscriptions'])
|
self.assertMatch(':ytsubs', ['youtube:subscriptions'])
|
||||||
self.assertMatch(':ytsubscriptions', ['youtube:subscriptions'])
|
self.assertMatch(':ytsubscriptions', ['youtube:subscriptions'])
|
||||||
self.assertMatch(':ythistory', ['youtube:history'])
|
self.assertMatch(':ythistory', ['youtube:history'])
|
||||||
self.assertMatch(':thedailyshow', ['ComedyCentralShows'])
|
|
||||||
self.assertMatch(':tds', ['ComedyCentralShows'])
|
|
||||||
|
|
||||||
def test_vimeo_matching(self):
|
def test_vimeo_matching(self):
|
||||||
self.assertMatch('https://vimeo.com/channels/tributes', ['vimeo:channel'])
|
self.assertMatch('https://vimeo.com/channels/tributes', ['vimeo:channel'])
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ class TestCompat(unittest.TestCase):
|
|||||||
def test_compat_shlex_split(self):
|
def test_compat_shlex_split(self):
|
||||||
self.assertEqual(compat_shlex_split('-option "one two"'), ['-option', 'one two'])
|
self.assertEqual(compat_shlex_split('-option "one two"'), ['-option', 'one two'])
|
||||||
self.assertEqual(compat_shlex_split('-option "one\ntwo" \n -flag'), ['-option', 'one\ntwo', '-flag'])
|
self.assertEqual(compat_shlex_split('-option "one\ntwo" \n -flag'), ['-option', 'one\ntwo', '-flag'])
|
||||||
|
self.assertEqual(compat_shlex_split('-val 中文'), ['-val', '中文'])
|
||||||
|
|
||||||
def test_compat_etree_fromstring(self):
|
def test_compat_etree_fromstring(self):
|
||||||
xml = '''
|
xml = '''
|
||||||
|
|||||||
@@ -138,27 +138,27 @@ class TestProxy(unittest.TestCase):
|
|||||||
self.proxy_thread.daemon = True
|
self.proxy_thread.daemon = True
|
||||||
self.proxy_thread.start()
|
self.proxy_thread.start()
|
||||||
|
|
||||||
self.cn_proxy = compat_http_server.HTTPServer(
|
self.geo_proxy = compat_http_server.HTTPServer(
|
||||||
('localhost', 0), _build_proxy_handler('cn'))
|
('localhost', 0), _build_proxy_handler('geo'))
|
||||||
self.cn_port = http_server_port(self.cn_proxy)
|
self.geo_port = http_server_port(self.geo_proxy)
|
||||||
self.cn_proxy_thread = threading.Thread(target=self.cn_proxy.serve_forever)
|
self.geo_proxy_thread = threading.Thread(target=self.geo_proxy.serve_forever)
|
||||||
self.cn_proxy_thread.daemon = True
|
self.geo_proxy_thread.daemon = True
|
||||||
self.cn_proxy_thread.start()
|
self.geo_proxy_thread.start()
|
||||||
|
|
||||||
def test_proxy(self):
|
def test_proxy(self):
|
||||||
cn_proxy = 'localhost:{0}'.format(self.cn_port)
|
geo_proxy = 'localhost:{0}'.format(self.geo_port)
|
||||||
ydl = YoutubeDL({
|
ydl = YoutubeDL({
|
||||||
'proxy': 'localhost:{0}'.format(self.port),
|
'proxy': 'localhost:{0}'.format(self.port),
|
||||||
'cn_verification_proxy': cn_proxy,
|
'geo_verification_proxy': geo_proxy,
|
||||||
})
|
})
|
||||||
url = 'http://foo.com/bar'
|
url = 'http://foo.com/bar'
|
||||||
response = ydl.urlopen(url).read().decode('utf-8')
|
response = ydl.urlopen(url).read().decode('utf-8')
|
||||||
self.assertEqual(response, 'normal: {0}'.format(url))
|
self.assertEqual(response, 'normal: {0}'.format(url))
|
||||||
|
|
||||||
req = compat_urllib_request.Request(url)
|
req = compat_urllib_request.Request(url)
|
||||||
req.add_header('Ytdl-request-proxy', cn_proxy)
|
req.add_header('Ytdl-request-proxy', geo_proxy)
|
||||||
response = ydl.urlopen(req).read().decode('utf-8')
|
response = ydl.urlopen(req).read().decode('utf-8')
|
||||||
self.assertEqual(response, 'cn: {0}'.format(url))
|
self.assertEqual(response, 'geo: {0}'.format(url))
|
||||||
|
|
||||||
def test_proxy_with_idn(self):
|
def test_proxy_with_idn(self):
|
||||||
ydl = YoutubeDL({
|
ydl = YoutubeDL({
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ from youtube_dl.utils import (
|
|||||||
ExtractorError,
|
ExtractorError,
|
||||||
find_xpath_attr,
|
find_xpath_attr,
|
||||||
fix_xml_ampersands,
|
fix_xml_ampersands,
|
||||||
|
get_element_by_class,
|
||||||
InAdvancePagedList,
|
InAdvancePagedList,
|
||||||
intlist_to_bytes,
|
intlist_to_bytes,
|
||||||
is_html,
|
is_html,
|
||||||
@@ -41,6 +42,7 @@ from youtube_dl.utils import (
|
|||||||
ohdave_rsa_encrypt,
|
ohdave_rsa_encrypt,
|
||||||
OnDemandPagedList,
|
OnDemandPagedList,
|
||||||
orderedSet,
|
orderedSet,
|
||||||
|
parse_age_limit,
|
||||||
parse_duration,
|
parse_duration,
|
||||||
parse_filesize,
|
parse_filesize,
|
||||||
parse_count,
|
parse_count,
|
||||||
@@ -80,6 +82,7 @@ from youtube_dl.utils import (
|
|||||||
cli_option,
|
cli_option,
|
||||||
cli_valueless_option,
|
cli_valueless_option,
|
||||||
cli_bool_option,
|
cli_bool_option,
|
||||||
|
parse_codecs,
|
||||||
)
|
)
|
||||||
from youtube_dl.compat import (
|
from youtube_dl.compat import (
|
||||||
compat_chr,
|
compat_chr,
|
||||||
@@ -306,6 +309,7 @@ class TestUtil(unittest.TestCase):
|
|||||||
self.assertEqual(unified_timestamp('25-09-2014'), 1411603200)
|
self.assertEqual(unified_timestamp('25-09-2014'), 1411603200)
|
||||||
self.assertEqual(unified_timestamp('27.02.2016 17:30'), 1456594200)
|
self.assertEqual(unified_timestamp('27.02.2016 17:30'), 1456594200)
|
||||||
self.assertEqual(unified_timestamp('UNKNOWN DATE FORMAT'), None)
|
self.assertEqual(unified_timestamp('UNKNOWN DATE FORMAT'), None)
|
||||||
|
self.assertEqual(unified_timestamp('May 16, 2016 11:15 PM'), 1463440500)
|
||||||
|
|
||||||
def test_determine_ext(self):
|
def test_determine_ext(self):
|
||||||
self.assertEqual(determine_ext('http://example.com/foo/bar.mp4/?download'), 'mp4')
|
self.assertEqual(determine_ext('http://example.com/foo/bar.mp4/?download'), 'mp4')
|
||||||
@@ -405,6 +409,12 @@ class TestUtil(unittest.TestCase):
|
|||||||
self.assertEqual(res_url, url)
|
self.assertEqual(res_url, url)
|
||||||
self.assertEqual(res_data, None)
|
self.assertEqual(res_data, None)
|
||||||
|
|
||||||
|
smug_url = smuggle_url(url, {'a': 'b'})
|
||||||
|
smug_smug_url = smuggle_url(smug_url, {'c': 'd'})
|
||||||
|
res_url, res_data = unsmuggle_url(smug_smug_url)
|
||||||
|
self.assertEqual(res_url, url)
|
||||||
|
self.assertEqual(res_data, {'a': 'b', 'c': 'd'})
|
||||||
|
|
||||||
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'""")
|
||||||
@@ -423,6 +433,20 @@ class TestUtil(unittest.TestCase):
|
|||||||
url_basename('http://media.w3.org/2010/05/sintel/trailer.mp4'),
|
url_basename('http://media.w3.org/2010/05/sintel/trailer.mp4'),
|
||||||
'trailer.mp4')
|
'trailer.mp4')
|
||||||
|
|
||||||
|
def test_parse_age_limit(self):
|
||||||
|
self.assertEqual(parse_age_limit(None), None)
|
||||||
|
self.assertEqual(parse_age_limit(False), None)
|
||||||
|
self.assertEqual(parse_age_limit('invalid'), None)
|
||||||
|
self.assertEqual(parse_age_limit(0), 0)
|
||||||
|
self.assertEqual(parse_age_limit(18), 18)
|
||||||
|
self.assertEqual(parse_age_limit(21), 21)
|
||||||
|
self.assertEqual(parse_age_limit(22), None)
|
||||||
|
self.assertEqual(parse_age_limit('18'), 18)
|
||||||
|
self.assertEqual(parse_age_limit('18+'), 18)
|
||||||
|
self.assertEqual(parse_age_limit('PG-13'), 13)
|
||||||
|
self.assertEqual(parse_age_limit('TV-14'), 14)
|
||||||
|
self.assertEqual(parse_age_limit('TV-MA'), 17)
|
||||||
|
|
||||||
def test_parse_duration(self):
|
def test_parse_duration(self):
|
||||||
self.assertEqual(parse_duration(None), None)
|
self.assertEqual(parse_duration(None), None)
|
||||||
self.assertEqual(parse_duration(False), None)
|
self.assertEqual(parse_duration(False), None)
|
||||||
@@ -601,6 +625,29 @@ class TestUtil(unittest.TestCase):
|
|||||||
limit_length('foo bar baz asd', 12).startswith('foo bar'))
|
limit_length('foo bar baz asd', 12).startswith('foo bar'))
|
||||||
self.assertTrue('...' in limit_length('foo bar baz asd', 12))
|
self.assertTrue('...' in limit_length('foo bar baz asd', 12))
|
||||||
|
|
||||||
|
def test_parse_codecs(self):
|
||||||
|
self.assertEqual(parse_codecs(''), {})
|
||||||
|
self.assertEqual(parse_codecs('avc1.77.30, mp4a.40.2'), {
|
||||||
|
'vcodec': 'avc1.77.30',
|
||||||
|
'acodec': 'mp4a.40.2',
|
||||||
|
})
|
||||||
|
self.assertEqual(parse_codecs('mp4a.40.2'), {
|
||||||
|
'vcodec': 'none',
|
||||||
|
'acodec': 'mp4a.40.2',
|
||||||
|
})
|
||||||
|
self.assertEqual(parse_codecs('mp4a.40.5,avc1.42001e'), {
|
||||||
|
'vcodec': 'avc1.42001e',
|
||||||
|
'acodec': 'mp4a.40.5',
|
||||||
|
})
|
||||||
|
self.assertEqual(parse_codecs('avc3.640028'), {
|
||||||
|
'vcodec': 'avc3.640028',
|
||||||
|
'acodec': 'none',
|
||||||
|
})
|
||||||
|
self.assertEqual(parse_codecs(', h264,,newcodec,aac'), {
|
||||||
|
'vcodec': 'h264',
|
||||||
|
'acodec': 'aac',
|
||||||
|
})
|
||||||
|
|
||||||
def test_escape_rfc3986(self):
|
def test_escape_rfc3986(self):
|
||||||
reserved = "!*'();:@&=+$,/?#[]"
|
reserved = "!*'();:@&=+$,/?#[]"
|
||||||
unreserved = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~'
|
unreserved = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~'
|
||||||
@@ -665,6 +712,9 @@ class TestUtil(unittest.TestCase):
|
|||||||
inp = '''{"foo":101}'''
|
inp = '''{"foo":101}'''
|
||||||
self.assertEqual(js_to_json(inp), '''{"foo":101}''')
|
self.assertEqual(js_to_json(inp), '''{"foo":101}''')
|
||||||
|
|
||||||
|
inp = '''{"duration": "00:01:07"}'''
|
||||||
|
self.assertEqual(js_to_json(inp), '''{"duration": "00:01:07"}''')
|
||||||
|
|
||||||
def test_js_to_json_edgecases(self):
|
def test_js_to_json_edgecases(self):
|
||||||
on = js_to_json("{abc_def:'1\\'\\\\2\\\\\\'3\"4'}")
|
on = js_to_json("{abc_def:'1\\'\\\\2\\\\\\'3\"4'}")
|
||||||
self.assertEqual(json.loads(on), {"abc_def": "1'\\2\\'3\"4"})
|
self.assertEqual(json.loads(on), {"abc_def": "1'\\2\\'3\"4"})
|
||||||
@@ -770,7 +820,10 @@ class TestUtil(unittest.TestCase):
|
|||||||
self.assertEqual(parse_filesize('2 MiB'), 2097152)
|
self.assertEqual(parse_filesize('2 MiB'), 2097152)
|
||||||
self.assertEqual(parse_filesize('5 GB'), 5000000000)
|
self.assertEqual(parse_filesize('5 GB'), 5000000000)
|
||||||
self.assertEqual(parse_filesize('1.2Tb'), 1200000000000)
|
self.assertEqual(parse_filesize('1.2Tb'), 1200000000000)
|
||||||
|
self.assertEqual(parse_filesize('1.2tb'), 1200000000000)
|
||||||
self.assertEqual(parse_filesize('1,24 KB'), 1240)
|
self.assertEqual(parse_filesize('1,24 KB'), 1240)
|
||||||
|
self.assertEqual(parse_filesize('1,24 kb'), 1240)
|
||||||
|
self.assertEqual(parse_filesize('8.5 megabytes'), 8500000)
|
||||||
|
|
||||||
def test_parse_count(self):
|
def test_parse_count(self):
|
||||||
self.assertEqual(parse_count(None), None)
|
self.assertEqual(parse_count(None), None)
|
||||||
@@ -921,6 +974,7 @@ The first line
|
|||||||
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'), [])
|
||||||
self.assertEqual(cli_option({}, '--proxy', 'proxy'), [])
|
self.assertEqual(cli_option({}, '--proxy', 'proxy'), [])
|
||||||
|
self.assertEqual(cli_option({'retries': 10}, '--retries', 'retries'), ['--retries', '10'])
|
||||||
|
|
||||||
def test_cli_valueless_option(self):
|
def test_cli_valueless_option(self):
|
||||||
self.assertEqual(cli_valueless_option(
|
self.assertEqual(cli_valueless_option(
|
||||||
@@ -985,5 +1039,13 @@ The first line
|
|||||||
self.assertEqual(urshift(3, 1), 1)
|
self.assertEqual(urshift(3, 1), 1)
|
||||||
self.assertEqual(urshift(-3, 1), 2147483646)
|
self.assertEqual(urshift(-3, 1), 2147483646)
|
||||||
|
|
||||||
|
def test_get_element_by_class(self):
|
||||||
|
html = '''
|
||||||
|
<span class="foo bar">nice</span>
|
||||||
|
'''
|
||||||
|
|
||||||
|
self.assertEqual(get_element_by_class('foo', html), 'nice')
|
||||||
|
self.assertEqual(get_element_by_class('no-such-class', html), None)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
70
test/test_verbose_output.py
Normal file
70
test/test_verbose_output.py
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# coding: utf-8
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
rootDir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
|
||||||
|
class TestVerboseOutput(unittest.TestCase):
|
||||||
|
def test_private_info_arg(self):
|
||||||
|
outp = subprocess.Popen(
|
||||||
|
[
|
||||||
|
sys.executable, 'youtube_dl/__main__.py', '-v',
|
||||||
|
'--username', 'johnsmith@gmail.com',
|
||||||
|
'--password', 'secret',
|
||||||
|
], cwd=rootDir, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
|
sout, serr = outp.communicate()
|
||||||
|
self.assertTrue(b'--username' in serr)
|
||||||
|
self.assertTrue(b'johnsmith' not in serr)
|
||||||
|
self.assertTrue(b'--password' in serr)
|
||||||
|
self.assertTrue(b'secret' not in serr)
|
||||||
|
|
||||||
|
def test_private_info_shortarg(self):
|
||||||
|
outp = subprocess.Popen(
|
||||||
|
[
|
||||||
|
sys.executable, 'youtube_dl/__main__.py', '-v',
|
||||||
|
'-u', 'johnsmith@gmail.com',
|
||||||
|
'-p', 'secret',
|
||||||
|
], cwd=rootDir, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
|
sout, serr = outp.communicate()
|
||||||
|
self.assertTrue(b'-u' in serr)
|
||||||
|
self.assertTrue(b'johnsmith' not in serr)
|
||||||
|
self.assertTrue(b'-p' in serr)
|
||||||
|
self.assertTrue(b'secret' not in serr)
|
||||||
|
|
||||||
|
def test_private_info_eq(self):
|
||||||
|
outp = subprocess.Popen(
|
||||||
|
[
|
||||||
|
sys.executable, 'youtube_dl/__main__.py', '-v',
|
||||||
|
'--username=johnsmith@gmail.com',
|
||||||
|
'--password=secret',
|
||||||
|
], cwd=rootDir, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
|
sout, serr = outp.communicate()
|
||||||
|
self.assertTrue(b'--username' in serr)
|
||||||
|
self.assertTrue(b'johnsmith' not in serr)
|
||||||
|
self.assertTrue(b'--password' in serr)
|
||||||
|
self.assertTrue(b'secret' not in serr)
|
||||||
|
|
||||||
|
def test_private_info_shortarg_eq(self):
|
||||||
|
outp = subprocess.Popen(
|
||||||
|
[
|
||||||
|
sys.executable, 'youtube_dl/__main__.py', '-v',
|
||||||
|
'-u=johnsmith@gmail.com',
|
||||||
|
'-p=secret',
|
||||||
|
], cwd=rootDir, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
|
sout, serr = outp.communicate()
|
||||||
|
self.assertTrue(b'-u' in serr)
|
||||||
|
self.assertTrue(b'johnsmith' not in serr)
|
||||||
|
self.assertTrue(b'-p' in serr)
|
||||||
|
self.assertTrue(b'secret' not in serr)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
@@ -5,6 +5,7 @@ from __future__ import absolute_import, unicode_literals
|
|||||||
|
|
||||||
import collections
|
import collections
|
||||||
import contextlib
|
import contextlib
|
||||||
|
import copy
|
||||||
import datetime
|
import datetime
|
||||||
import errno
|
import errno
|
||||||
import fileinput
|
import fileinput
|
||||||
@@ -196,8 +197,8 @@ class YoutubeDL(object):
|
|||||||
prefer_insecure: Use HTTP instead of HTTPS to retrieve information.
|
prefer_insecure: Use HTTP instead of HTTPS to retrieve information.
|
||||||
At the moment, this is only supported by YouTube.
|
At the moment, this is only supported by YouTube.
|
||||||
proxy: URL of the proxy server to use
|
proxy: URL of the proxy server to use
|
||||||
cn_verification_proxy: URL of the proxy to use for IP address verification
|
geo_verification_proxy: URL of the proxy to use for IP address verification
|
||||||
on Chinese sites. (Experimental)
|
on geo-restricted sites. (Experimental)
|
||||||
socket_timeout: Time to wait for unresponsive hosts, in seconds
|
socket_timeout: Time to wait for unresponsive hosts, in seconds
|
||||||
bidi_workaround: Work around buggy terminals without bidirectional text
|
bidi_workaround: Work around buggy terminals without bidirectional text
|
||||||
support, using fridibi
|
support, using fridibi
|
||||||
@@ -248,7 +249,16 @@ class YoutubeDL(object):
|
|||||||
source_address: (Experimental) Client-side IP address to bind to.
|
source_address: (Experimental) Client-side IP address to bind to.
|
||||||
call_home: Boolean, true iff we are allowed to contact the
|
call_home: Boolean, true iff we are allowed to contact the
|
||||||
youtube-dl servers for debugging.
|
youtube-dl servers for debugging.
|
||||||
sleep_interval: Number of seconds to sleep before each download.
|
sleep_interval: Number of seconds to sleep before each download when
|
||||||
|
used alone or a lower bound of a range for randomized
|
||||||
|
sleep before each download (minimum possible number
|
||||||
|
of seconds to sleep) when used along with
|
||||||
|
max_sleep_interval.
|
||||||
|
max_sleep_interval:Upper bound of a range for randomized sleep before each
|
||||||
|
download (maximum possible number of seconds to sleep).
|
||||||
|
Must only be used along with sleep_interval.
|
||||||
|
Actual sleep time will be a random float from range
|
||||||
|
[sleep_interval; max_sleep_interval].
|
||||||
listformats: Print an overview of available video formats and exit.
|
listformats: Print an overview of available video formats and exit.
|
||||||
list_thumbnails: Print a table of all thumbnails and exit.
|
list_thumbnails: Print a table of all thumbnails and exit.
|
||||||
match_filter: A function that gets called with the info_dict of
|
match_filter: A function that gets called with the info_dict of
|
||||||
@@ -304,6 +314,11 @@ class YoutubeDL(object):
|
|||||||
self.params.update(params)
|
self.params.update(params)
|
||||||
self.cache = Cache(self)
|
self.cache = Cache(self)
|
||||||
|
|
||||||
|
if self.params.get('cn_verification_proxy') is not None:
|
||||||
|
self.report_warning('--cn-verification-proxy is deprecated. Use --geo-verification-proxy instead.')
|
||||||
|
if self.params.get('geo_verification_proxy') is None:
|
||||||
|
self.params['geo_verification_proxy'] = self.params['cn_verification_proxy']
|
||||||
|
|
||||||
if params.get('bidi_workaround', False):
|
if params.get('bidi_workaround', False):
|
||||||
try:
|
try:
|
||||||
import pty
|
import pty
|
||||||
@@ -1046,9 +1061,9 @@ class YoutubeDL(object):
|
|||||||
if isinstance(selector, list):
|
if isinstance(selector, list):
|
||||||
fs = [_build_selector_function(s) for s in selector]
|
fs = [_build_selector_function(s) for s in selector]
|
||||||
|
|
||||||
def selector_function(formats):
|
def selector_function(ctx):
|
||||||
for f in fs:
|
for f in fs:
|
||||||
for format in f(formats):
|
for format in f(ctx):
|
||||||
yield format
|
yield format
|
||||||
return selector_function
|
return selector_function
|
||||||
elif selector.type == GROUP:
|
elif selector.type == GROUP:
|
||||||
@@ -1056,17 +1071,17 @@ class YoutubeDL(object):
|
|||||||
elif selector.type == PICKFIRST:
|
elif selector.type == PICKFIRST:
|
||||||
fs = [_build_selector_function(s) for s in selector.selector]
|
fs = [_build_selector_function(s) for s in selector.selector]
|
||||||
|
|
||||||
def selector_function(formats):
|
def selector_function(ctx):
|
||||||
for f in fs:
|
for f in fs:
|
||||||
picked_formats = list(f(formats))
|
picked_formats = list(f(ctx))
|
||||||
if picked_formats:
|
if picked_formats:
|
||||||
return picked_formats
|
return picked_formats
|
||||||
return []
|
return []
|
||||||
elif selector.type == SINGLE:
|
elif selector.type == SINGLE:
|
||||||
format_spec = selector.selector
|
format_spec = selector.selector
|
||||||
|
|
||||||
def selector_function(formats):
|
def selector_function(ctx):
|
||||||
formats = list(formats)
|
formats = list(ctx['formats'])
|
||||||
if not formats:
|
if not formats:
|
||||||
return
|
return
|
||||||
if format_spec == 'all':
|
if format_spec == 'all':
|
||||||
@@ -1079,9 +1094,10 @@ class YoutubeDL(object):
|
|||||||
if f.get('vcodec') != 'none' and f.get('acodec') != 'none']
|
if f.get('vcodec') != 'none' and f.get('acodec') != 'none']
|
||||||
if audiovideo_formats:
|
if audiovideo_formats:
|
||||||
yield audiovideo_formats[format_idx]
|
yield audiovideo_formats[format_idx]
|
||||||
# for audio only (soundcloud) or video only (imgur) urls, select the best/worst audio format
|
# for extractors with incomplete formats (audio only (soundcloud)
|
||||||
elif (all(f.get('acodec') != 'none' for f in formats) or
|
# or video only (imgur)) we will fallback to best/worst
|
||||||
all(f.get('vcodec') != 'none' for f in formats)):
|
# {video,audio}-only format
|
||||||
|
elif ctx['incomplete_formats']:
|
||||||
yield formats[format_idx]
|
yield formats[format_idx]
|
||||||
elif format_spec == 'bestaudio':
|
elif format_spec == 'bestaudio':
|
||||||
audio_formats = [
|
audio_formats = [
|
||||||
@@ -1155,17 +1171,18 @@ class YoutubeDL(object):
|
|||||||
}
|
}
|
||||||
video_selector, audio_selector = map(_build_selector_function, selector.selector)
|
video_selector, audio_selector = map(_build_selector_function, selector.selector)
|
||||||
|
|
||||||
def selector_function(formats):
|
def selector_function(ctx):
|
||||||
formats = list(formats)
|
for pair in itertools.product(
|
||||||
for pair in itertools.product(video_selector(formats), audio_selector(formats)):
|
video_selector(copy.deepcopy(ctx)), audio_selector(copy.deepcopy(ctx))):
|
||||||
yield _merge(pair)
|
yield _merge(pair)
|
||||||
|
|
||||||
filters = [self._build_format_filter(f) for f in selector.filters]
|
filters = [self._build_format_filter(f) for f in selector.filters]
|
||||||
|
|
||||||
def final_selector(formats):
|
def final_selector(ctx):
|
||||||
|
ctx_copy = copy.deepcopy(ctx)
|
||||||
for _filter in filters:
|
for _filter in filters:
|
||||||
formats = list(filter(_filter, formats))
|
ctx_copy['formats'] = list(filter(_filter, ctx_copy['formats']))
|
||||||
return selector_function(formats)
|
return selector_function(ctx_copy)
|
||||||
return final_selector
|
return final_selector
|
||||||
|
|
||||||
stream = io.BytesIO(format_spec.encode('utf-8'))
|
stream = io.BytesIO(format_spec.encode('utf-8'))
|
||||||
@@ -1282,7 +1299,7 @@ class YoutubeDL(object):
|
|||||||
for subtitle_format in subtitle:
|
for subtitle_format in subtitle:
|
||||||
if subtitle_format.get('url'):
|
if subtitle_format.get('url'):
|
||||||
subtitle_format['url'] = sanitize_url(subtitle_format['url'])
|
subtitle_format['url'] = sanitize_url(subtitle_format['url'])
|
||||||
if 'ext' not in subtitle_format:
|
if subtitle_format.get('ext') is None:
|
||||||
subtitle_format['ext'] = determine_ext(subtitle_format['url']).lower()
|
subtitle_format['ext'] = determine_ext(subtitle_format['url']).lower()
|
||||||
|
|
||||||
if self.params.get('listsubtitles', False):
|
if self.params.get('listsubtitles', False):
|
||||||
@@ -1337,7 +1354,7 @@ class YoutubeDL(object):
|
|||||||
note=' ({0})'.format(format['format_note']) if format.get('format_note') is not None else '',
|
note=' ({0})'.format(format['format_note']) if format.get('format_note') is not None else '',
|
||||||
)
|
)
|
||||||
# Automatically determine file extension if missing
|
# Automatically determine file extension if missing
|
||||||
if 'ext' not in format:
|
if format.get('ext') is None:
|
||||||
format['ext'] = determine_ext(format['url']).lower()
|
format['ext'] = determine_ext(format['url']).lower()
|
||||||
# Automatically determine protocol if missing (useful for format
|
# Automatically determine protocol if missing (useful for format
|
||||||
# selection purposes)
|
# selection purposes)
|
||||||
@@ -1372,7 +1389,34 @@ class YoutubeDL(object):
|
|||||||
req_format_list.append('best')
|
req_format_list.append('best')
|
||||||
req_format = '/'.join(req_format_list)
|
req_format = '/'.join(req_format_list)
|
||||||
format_selector = self.build_format_selector(req_format)
|
format_selector = self.build_format_selector(req_format)
|
||||||
formats_to_download = list(format_selector(formats))
|
|
||||||
|
# While in format selection we may need to have an access to the original
|
||||||
|
# format set in order to calculate some metrics or do some processing.
|
||||||
|
# For now we need to be able to guess whether original formats provided
|
||||||
|
# by extractor are incomplete or not (i.e. whether extractor provides only
|
||||||
|
# video-only or audio-only formats) for proper formats selection for
|
||||||
|
# extractors with such incomplete formats (see
|
||||||
|
# https://github.com/rg3/youtube-dl/pull/5556).
|
||||||
|
# Since formats may be filtered during format selection and may not match
|
||||||
|
# the original formats the results may be incorrect. Thus original formats
|
||||||
|
# or pre-calculated metrics should be passed to format selection routines
|
||||||
|
# as well.
|
||||||
|
# We will pass a context object containing all necessary additional data
|
||||||
|
# instead of just formats.
|
||||||
|
# This fixes incorrect format selection issue (see
|
||||||
|
# https://github.com/rg3/youtube-dl/issues/10083).
|
||||||
|
incomplete_formats = (
|
||||||
|
# All formats are video-only or
|
||||||
|
all(f.get('vcodec') != 'none' and f.get('acodec') == 'none' for f in formats) or
|
||||||
|
# all formats are audio-only
|
||||||
|
all(f.get('vcodec') == 'none' and f.get('acodec') != 'none' for f in formats))
|
||||||
|
|
||||||
|
ctx = {
|
||||||
|
'formats': formats,
|
||||||
|
'incomplete_formats': incomplete_formats,
|
||||||
|
}
|
||||||
|
|
||||||
|
formats_to_download = list(format_selector(ctx))
|
||||||
if not formats_to_download:
|
if not formats_to_download:
|
||||||
raise ExtractorError('requested format not available',
|
raise ExtractorError('requested format not available',
|
||||||
expected=True)
|
expected=True)
|
||||||
@@ -1559,7 +1603,9 @@ class YoutubeDL(object):
|
|||||||
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)
|
||||||
with io.open(encodeFilename(sub_filename), 'w', encoding='utf-8') as subfile:
|
# Use newline='' to prevent conversion of newline characters
|
||||||
|
# See https://github.com/rg3/youtube-dl/issues/10268
|
||||||
|
with io.open(encodeFilename(sub_filename), 'w', encoding='utf-8', newline='') as subfile:
|
||||||
subfile.write(sub_data)
|
subfile.write(sub_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)
|
||||||
|
|||||||
@@ -145,6 +145,16 @@ def _real_main(argv=None):
|
|||||||
if numeric_limit is None:
|
if numeric_limit is None:
|
||||||
parser.error('invalid max_filesize specified')
|
parser.error('invalid max_filesize specified')
|
||||||
opts.max_filesize = numeric_limit
|
opts.max_filesize = numeric_limit
|
||||||
|
if opts.sleep_interval is not None:
|
||||||
|
if opts.sleep_interval < 0:
|
||||||
|
parser.error('sleep interval must be positive or 0')
|
||||||
|
if opts.max_sleep_interval is not None:
|
||||||
|
if opts.max_sleep_interval < 0:
|
||||||
|
parser.error('max sleep interval must be positive or 0')
|
||||||
|
if opts.max_sleep_interval < opts.sleep_interval:
|
||||||
|
parser.error('max sleep interval must be greater than or equal to min sleep interval')
|
||||||
|
else:
|
||||||
|
opts.max_sleep_interval = opts.sleep_interval
|
||||||
|
|
||||||
def parse_retries(retries):
|
def parse_retries(retries):
|
||||||
if retries in ('inf', 'infinite'):
|
if retries in ('inf', 'infinite'):
|
||||||
@@ -370,6 +380,7 @@ def _real_main(argv=None):
|
|||||||
'source_address': opts.source_address,
|
'source_address': opts.source_address,
|
||||||
'call_home': opts.call_home,
|
'call_home': opts.call_home,
|
||||||
'sleep_interval': opts.sleep_interval,
|
'sleep_interval': opts.sleep_interval,
|
||||||
|
'max_sleep_interval': opts.max_sleep_interval,
|
||||||
'external_downloader': opts.external_downloader,
|
'external_downloader': opts.external_downloader,
|
||||||
'list_thumbnails': opts.list_thumbnails,
|
'list_thumbnails': opts.list_thumbnails,
|
||||||
'playlist_items': opts.playlist_items,
|
'playlist_items': opts.playlist_items,
|
||||||
@@ -382,6 +393,8 @@ def _real_main(argv=None):
|
|||||||
'external_downloader_args': external_downloader_args,
|
'external_downloader_args': external_downloader_args,
|
||||||
'postprocessor_args': postprocessor_args,
|
'postprocessor_args': postprocessor_args,
|
||||||
'cn_verification_proxy': opts.cn_verification_proxy,
|
'cn_verification_proxy': opts.cn_verification_proxy,
|
||||||
|
'geo_verification_proxy': opts.geo_verification_proxy,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
with YoutubeDL(ydl_opts) as ydl:
|
with YoutubeDL(ydl_opts) as ydl:
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
# coding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import binascii
|
import binascii
|
||||||
@@ -2594,15 +2595,19 @@ except ImportError: # Python < 3.3
|
|||||||
return "'" + s.replace("'", "'\"'\"'") + "'"
|
return "'" + s.replace("'", "'\"'\"'") + "'"
|
||||||
|
|
||||||
|
|
||||||
if sys.version_info >= (2, 7, 3):
|
try:
|
||||||
|
args = shlex.split('中文')
|
||||||
|
assert (isinstance(args, list) and
|
||||||
|
isinstance(args[0], compat_str) and
|
||||||
|
args[0] == '中文')
|
||||||
compat_shlex_split = shlex.split
|
compat_shlex_split = shlex.split
|
||||||
else:
|
except (AssertionError, UnicodeEncodeError):
|
||||||
# Working around shlex issue with unicode strings on some python 2
|
# Working around shlex issue with unicode strings on some python 2
|
||||||
# versions (see http://bugs.python.org/issue1548891)
|
# versions (see http://bugs.python.org/issue1548891)
|
||||||
def compat_shlex_split(s, comments=False, posix=True):
|
def compat_shlex_split(s, comments=False, posix=True):
|
||||||
if isinstance(s, compat_str):
|
if isinstance(s, compat_str):
|
||||||
s = s.encode('utf-8')
|
s = s.encode('utf-8')
|
||||||
return shlex.split(s, comments, posix)
|
return list(map(lambda s: s.decode('utf-8'), shlex.split(s, comments, posix)))
|
||||||
|
|
||||||
|
|
||||||
def compat_ord(c):
|
def compat_ord(c):
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import os
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
import random
|
||||||
|
|
||||||
from ..compat import compat_os_name
|
from ..compat import compat_os_name
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
@@ -342,8 +343,11 @@ class FileDownloader(object):
|
|||||||
})
|
})
|
||||||
return True
|
return True
|
||||||
|
|
||||||
sleep_interval = self.params.get('sleep_interval')
|
min_sleep_interval = self.params.get('sleep_interval')
|
||||||
if sleep_interval:
|
if min_sleep_interval:
|
||||||
|
max_sleep_interval = self.params.get('max_sleep_interval', min_sleep_interval)
|
||||||
|
print(min_sleep_interval, max_sleep_interval)
|
||||||
|
sleep_interval = random.uniform(min_sleep_interval, max_sleep_interval)
|
||||||
self.to_screen('[download] Sleeping %s seconds...' % sleep_interval)
|
self.to_screen('[download] Sleeping %s seconds...' % sleep_interval)
|
||||||
time.sleep(sleep_interval)
|
time.sleep(sleep_interval)
|
||||||
|
|
||||||
|
|||||||
@@ -96,6 +96,12 @@ class CurlFD(ExternalFD):
|
|||||||
cmd = [self.exe, '--location', '-o', tmpfilename]
|
cmd = [self.exe, '--location', '-o', tmpfilename]
|
||||||
for key, val in info_dict['http_headers'].items():
|
for key, val in info_dict['http_headers'].items():
|
||||||
cmd += ['--header', '%s: %s' % (key, val)]
|
cmd += ['--header', '%s: %s' % (key, val)]
|
||||||
|
cmd += self._bool_option('--continue-at', 'continuedl', '-', '0')
|
||||||
|
cmd += self._valueless_option('--silent', 'noprogress')
|
||||||
|
cmd += self._valueless_option('--verbose', 'verbose')
|
||||||
|
cmd += self._option('--limit-rate', 'ratelimit')
|
||||||
|
cmd += self._option('--retry', 'retries')
|
||||||
|
cmd += self._option('--max-filesize', 'max_filesize')
|
||||||
cmd += self._option('--interface', 'source_address')
|
cmd += self._option('--interface', 'source_address')
|
||||||
cmd += self._option('--proxy', 'proxy')
|
cmd += self._option('--proxy', 'proxy')
|
||||||
cmd += self._valueless_option('--insecure', 'nocheckcertificate')
|
cmd += self._valueless_option('--insecure', 'nocheckcertificate')
|
||||||
@@ -103,6 +109,16 @@ class CurlFD(ExternalFD):
|
|||||||
cmd += ['--', info_dict['url']]
|
cmd += ['--', info_dict['url']]
|
||||||
return cmd
|
return cmd
|
||||||
|
|
||||||
|
def _call_downloader(self, tmpfilename, info_dict):
|
||||||
|
cmd = [encodeArgument(a) for a in self._make_cmd(tmpfilename, info_dict)]
|
||||||
|
|
||||||
|
self._debug_cmd(cmd)
|
||||||
|
|
||||||
|
# curl writes the progress to stderr so don't capture it.
|
||||||
|
p = subprocess.Popen(cmd)
|
||||||
|
p.communicate()
|
||||||
|
return p.returncode
|
||||||
|
|
||||||
|
|
||||||
class AxelFD(ExternalFD):
|
class AxelFD(ExternalFD):
|
||||||
AVAILABLE_OPT = '-V'
|
AVAILABLE_OPT = '-V'
|
||||||
|
|||||||
@@ -196,6 +196,11 @@ def build_fragments_list(boot_info):
|
|||||||
first_frag_number = fragment_run_entry_table[0]['first']
|
first_frag_number = fragment_run_entry_table[0]['first']
|
||||||
fragments_counter = itertools.count(first_frag_number)
|
fragments_counter = itertools.count(first_frag_number)
|
||||||
for segment, fragments_count in segment_run_table['segment_run']:
|
for segment, fragments_count in segment_run_table['segment_run']:
|
||||||
|
# In some live HDS streams (for example Rai), `fragments_count` is
|
||||||
|
# abnormal and causing out-of-memory errors. It's OK to change the
|
||||||
|
# number of fragments for live streams as they are updated periodically
|
||||||
|
if fragments_count == 4294967295 and boot_info['live']:
|
||||||
|
fragments_count = 2
|
||||||
for _ in range(fragments_count):
|
for _ in range(fragments_count):
|
||||||
res.append((segment, next(fragments_counter)))
|
res.append((segment, next(fragments_counter)))
|
||||||
|
|
||||||
@@ -329,7 +334,11 @@ class F4mFD(FragmentFD):
|
|||||||
|
|
||||||
base_url = compat_urlparse.urljoin(man_url, media.attrib['url'])
|
base_url = compat_urlparse.urljoin(man_url, media.attrib['url'])
|
||||||
bootstrap_node = doc.find(_add_ns('bootstrapInfo'))
|
bootstrap_node = doc.find(_add_ns('bootstrapInfo'))
|
||||||
boot_info, bootstrap_url = self._parse_bootstrap_node(bootstrap_node, base_url)
|
# From Adobe F4M 3.0 spec:
|
||||||
|
# The <baseURL> element SHALL be the base URL for all relative
|
||||||
|
# (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:
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ from ..utils import (
|
|||||||
encodeFilename,
|
encodeFilename,
|
||||||
sanitize_open,
|
sanitize_open,
|
||||||
parse_m3u8_attributes,
|
parse_m3u8_attributes,
|
||||||
|
update_url_query,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -82,6 +83,7 @@ class HlsFD(FragmentFD):
|
|||||||
|
|
||||||
self._prepare_and_start_frag_download(ctx)
|
self._prepare_and_start_frag_download(ctx)
|
||||||
|
|
||||||
|
extra_param_to_segment_url = info_dict.get('extra_param_to_segment_url')
|
||||||
i = 0
|
i = 0
|
||||||
media_sequence = 0
|
media_sequence = 0
|
||||||
decrypt_info = {'METHOD': 'NONE'}
|
decrypt_info = {'METHOD': 'NONE'}
|
||||||
@@ -95,6 +97,8 @@ class HlsFD(FragmentFD):
|
|||||||
if re.match(r'^https?://', line)
|
if re.match(r'^https?://', line)
|
||||||
else compat_urlparse.urljoin(man_url, line))
|
else compat_urlparse.urljoin(man_url, line))
|
||||||
frag_filename = '%s-Frag%d' % (ctx['tmpfilename'], i)
|
frag_filename = '%s-Frag%d' % (ctx['tmpfilename'], i)
|
||||||
|
if extra_param_to_segment_url:
|
||||||
|
frag_url = update_url_query(frag_url, extra_param_to_segment_url)
|
||||||
success = ctx['dl'].download(frag_filename, {'url': frag_url})
|
success = ctx['dl'].download(frag_filename, {'url': frag_url})
|
||||||
if not success:
|
if not success:
|
||||||
return False
|
return False
|
||||||
@@ -120,6 +124,8 @@ class HlsFD(FragmentFD):
|
|||||||
if not re.match(r'^https?://', decrypt_info['URI']):
|
if not re.match(r'^https?://', decrypt_info['URI']):
|
||||||
decrypt_info['URI'] = compat_urlparse.urljoin(
|
decrypt_info['URI'] = compat_urlparse.urljoin(
|
||||||
man_url, decrypt_info['URI'])
|
man_url, decrypt_info['URI'])
|
||||||
|
if extra_param_to_segment_url:
|
||||||
|
decrypt_info['URI'] = update_url_query(decrypt_info['URI'], extra_param_to_segment_url)
|
||||||
decrypt_info['KEY'] = self.ydl.urlopen(decrypt_info['URI']).read()
|
decrypt_info['KEY'] = self.ydl.urlopen(decrypt_info['URI']).read()
|
||||||
elif line.startswith('#EXT-X-MEDIA-SEQUENCE'):
|
elif line.startswith('#EXT-X-MEDIA-SEQUENCE'):
|
||||||
media_sequence = int(line[22:])
|
media_sequence = int(line[22:])
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from ..utils import (
|
|||||||
ExtractorError,
|
ExtractorError,
|
||||||
js_to_json,
|
js_to_json,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
|
parse_iso8601,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -93,3 +94,57 @@ class ABCIE(InfoExtractor):
|
|||||||
'description': self._og_search_description(webpage),
|
'description': self._og_search_description(webpage),
|
||||||
'thumbnail': self._og_search_thumbnail(webpage),
|
'thumbnail': self._og_search_thumbnail(webpage),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ABCIViewIE(InfoExtractor):
|
||||||
|
IE_NAME = 'abc.net.au:iview'
|
||||||
|
_VALID_URL = r'https?://iview\.abc\.net\.au/programs/[^/]+/(?P<id>[^/?#]+)'
|
||||||
|
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://iview.abc.net.au/programs/gardening-australia/FA1505V024S00',
|
||||||
|
'md5': '979d10b2939101f0d27a06b79edad536',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'FA1505V024S00',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Series 27 Ep 24',
|
||||||
|
'description': 'md5:b28baeae7504d1148e1d2f0e3ed3c15d',
|
||||||
|
'upload_date': '20160820',
|
||||||
|
'uploader_id': 'abc1',
|
||||||
|
'timestamp': 1471719600,
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
video_params = self._parse_json(self._search_regex(
|
||||||
|
r'videoParams\s*=\s*({.+?});', webpage, 'video params'), video_id)
|
||||||
|
title = video_params['title']
|
||||||
|
stream = next(s for s in video_params['playlist'] if s.get('type') == 'program')
|
||||||
|
|
||||||
|
formats = self._extract_akamai_formats(stream['hds-unmetered'], video_id)
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
subtitles = {}
|
||||||
|
src_vtt = stream.get('captions', {}).get('src-vtt')
|
||||||
|
if src_vtt:
|
||||||
|
subtitles['en'] = [{
|
||||||
|
'url': src_vtt,
|
||||||
|
'ext': 'vtt',
|
||||||
|
}]
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'description': self._html_search_meta(['og:description', 'twitter:description'], webpage),
|
||||||
|
'thumbnail': self._html_search_meta(['og:image', 'twitter:image:src'], webpage),
|
||||||
|
'duration': int_or_none(video_params.get('eventDuration')),
|
||||||
|
'timestamp': parse_iso8601(video_params.get('pubDate'), ' '),
|
||||||
|
'series': video_params.get('seriesTitle'),
|
||||||
|
'series_id': video_params.get('seriesHouseNumber') or video_id[:7],
|
||||||
|
'episode_number': int_or_none(self._html_search_meta('episodeNumber', webpage)),
|
||||||
|
'episode': self._html_search_meta('episode_title', webpage),
|
||||||
|
'uploader_id': video_params.get('channel'),
|
||||||
|
'formats': formats,
|
||||||
|
'subtitles': subtitles,
|
||||||
|
}
|
||||||
|
|||||||
134
youtube_dl/extractor/adobepass.py
Normal file
134
youtube_dl/extractor/adobepass.py
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
import xml.etree.ElementTree as etree
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
unescapeHTML,
|
||||||
|
urlencode_postdata,
|
||||||
|
unified_timestamp,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AdobePassIE(InfoExtractor):
|
||||||
|
_SERVICE_PROVIDER_TEMPLATE = 'https://sp.auth.adobe.com/adobe-services/%s'
|
||||||
|
_USER_AGENT = 'Mozilla/5.0 (X11; Linux i686; rv:47.0) Gecko/20100101 Firefox/47.0'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_mvpd_resource(provider_id, title, guid, rating):
|
||||||
|
channel = etree.Element('channel')
|
||||||
|
channel_title = etree.SubElement(channel, 'title')
|
||||||
|
channel_title.text = provider_id
|
||||||
|
item = etree.SubElement(channel, 'item')
|
||||||
|
resource_title = etree.SubElement(item, 'title')
|
||||||
|
resource_title.text = title
|
||||||
|
resource_guid = etree.SubElement(item, 'guid')
|
||||||
|
resource_guid.text = guid
|
||||||
|
resource_rating = etree.SubElement(item, 'media:rating')
|
||||||
|
resource_rating.attrib = {'scheme': 'urn:v-chip'}
|
||||||
|
resource_rating.text = rating
|
||||||
|
return '<rss version="2.0" xmlns:media="http://search.yahoo.com/mrss/">' + etree.tostring(channel).decode() + '</rss>'
|
||||||
|
|
||||||
|
def _extract_mvpd_auth(self, url, video_id, requestor_id, resource):
|
||||||
|
def xml_text(xml_str, tag):
|
||||||
|
return self._search_regex(
|
||||||
|
'<%s>(.+?)</%s>' % (tag, tag), xml_str, tag)
|
||||||
|
|
||||||
|
mvpd_headers = {
|
||||||
|
'ap_42': 'anonymous',
|
||||||
|
'ap_11': 'Linux i686',
|
||||||
|
'ap_z': self._USER_AGENT,
|
||||||
|
'User-Agent': self._USER_AGENT,
|
||||||
|
}
|
||||||
|
|
||||||
|
guid = xml_text(resource, 'guid')
|
||||||
|
requestor_info = self._downloader.cache.load('mvpd', requestor_id) or {}
|
||||||
|
authn_token = requestor_info.get('authn_token')
|
||||||
|
if authn_token:
|
||||||
|
token_expires = unified_timestamp(re.sub(r'[_ ]GMT', '', xml_text(authn_token, 'simpleTokenExpires')))
|
||||||
|
if token_expires and token_expires <= int(time.time()):
|
||||||
|
authn_token = None
|
||||||
|
requestor_info = {}
|
||||||
|
if not authn_token:
|
||||||
|
# TODO add support for other TV Providers
|
||||||
|
mso_id = 'DTV'
|
||||||
|
username, password = self._get_netrc_login_info(mso_id)
|
||||||
|
if not username or not password:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def post_form(form_page, note, data={}):
|
||||||
|
post_url = self._html_search_regex(r'<form[^>]+action=(["\'])(?P<url>.+?)\1', form_page, 'post url', group='url')
|
||||||
|
return self._download_webpage(
|
||||||
|
post_url, video_id, note, data=urlencode_postdata(data or self._hidden_inputs(form_page)), headers={
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
})
|
||||||
|
|
||||||
|
provider_redirect_page = self._download_webpage(
|
||||||
|
self._SERVICE_PROVIDER_TEMPLATE % 'authenticate/saml', video_id,
|
||||||
|
'Downloading Provider Redirect Page', query={
|
||||||
|
'noflash': 'true',
|
||||||
|
'mso_id': mso_id,
|
||||||
|
'requestor_id': requestor_id,
|
||||||
|
'no_iframe': 'false',
|
||||||
|
'domain_name': 'adobe.com',
|
||||||
|
'redirect_url': url,
|
||||||
|
})
|
||||||
|
provider_login_page = post_form(
|
||||||
|
provider_redirect_page, 'Downloading Provider Login Page')
|
||||||
|
mvpd_confirm_page = post_form(provider_login_page, 'Logging in', {
|
||||||
|
'username': username,
|
||||||
|
'password': password,
|
||||||
|
})
|
||||||
|
post_form(mvpd_confirm_page, 'Confirming Login')
|
||||||
|
|
||||||
|
session = self._download_webpage(
|
||||||
|
self._SERVICE_PROVIDER_TEMPLATE % 'session', video_id,
|
||||||
|
'Retrieving Session', data=urlencode_postdata({
|
||||||
|
'_method': 'GET',
|
||||||
|
'requestor_id': requestor_id,
|
||||||
|
}), headers=mvpd_headers)
|
||||||
|
if '<pendingLogout' in session:
|
||||||
|
self._downloader.cache.store('mvpd', requestor_id, {})
|
||||||
|
return self._extract_mvpd_auth(url, video_id, requestor_id, resource)
|
||||||
|
authn_token = unescapeHTML(xml_text(session, 'authnToken'))
|
||||||
|
requestor_info['authn_token'] = authn_token
|
||||||
|
self._downloader.cache.store('mvpd', requestor_id, requestor_info)
|
||||||
|
|
||||||
|
authz_token = requestor_info.get(guid)
|
||||||
|
if not authz_token:
|
||||||
|
authorize = self._download_webpage(
|
||||||
|
self._SERVICE_PROVIDER_TEMPLATE % 'authorize', video_id,
|
||||||
|
'Retrieving Authorization Token', data=urlencode_postdata({
|
||||||
|
'resource_id': resource,
|
||||||
|
'requestor_id': requestor_id,
|
||||||
|
'authentication_token': authn_token,
|
||||||
|
'mso_id': xml_text(authn_token, 'simpleTokenMsoID'),
|
||||||
|
'userMeta': '1',
|
||||||
|
}), headers=mvpd_headers)
|
||||||
|
if '<pendingLogout' in authorize:
|
||||||
|
self._downloader.cache.store('mvpd', requestor_id, {})
|
||||||
|
return self._extract_mvpd_auth(url, video_id, requestor_id, resource)
|
||||||
|
authz_token = unescapeHTML(xml_text(authorize, 'authzToken'))
|
||||||
|
requestor_info[guid] = authz_token
|
||||||
|
self._downloader.cache.store('mvpd', requestor_id, requestor_info)
|
||||||
|
|
||||||
|
mvpd_headers.update({
|
||||||
|
'ap_19': xml_text(authn_token, 'simpleSamlNameID'),
|
||||||
|
'ap_23': xml_text(authn_token, 'simpleSamlSessionIndex'),
|
||||||
|
})
|
||||||
|
|
||||||
|
short_authorize = self._download_webpage(
|
||||||
|
self._SERVICE_PROVIDER_TEMPLATE % 'shortAuthorize',
|
||||||
|
video_id, 'Retrieving Media Token', data=urlencode_postdata({
|
||||||
|
'authz_token': authz_token,
|
||||||
|
'requestor_id': requestor_id,
|
||||||
|
'session_guid': xml_text(authn_token, 'simpleTokenAuthenticationGuid'),
|
||||||
|
'hashed_guid': 'false',
|
||||||
|
}), headers=mvpd_headers)
|
||||||
|
if '<pendingLogout' in short_authorize:
|
||||||
|
self._downloader.cache.store('mvpd', requestor_id, {})
|
||||||
|
return self._extract_mvpd_auth(url, video_id, requestor_id, resource)
|
||||||
|
return short_authorize
|
||||||
@@ -83,6 +83,20 @@ class AdultSwimIE(InfoExtractor):
|
|||||||
# m3u8 download
|
# m3u8 download
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
# heroMetadata.trailer
|
||||||
|
'url': 'http://www.adultswim.com/videos/decker/inside-decker-a-new-hero/',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'I0LQFQkaSUaFp8PnAWHhoQ',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Decker - Inside Decker: A New Hero',
|
||||||
|
'description': 'md5:c916df071d425d62d70c86d4399d3ee0',
|
||||||
|
'duration': 249.008,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# m3u8 download
|
||||||
|
'skip_download': True,
|
||||||
|
}
|
||||||
}]
|
}]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -133,20 +147,26 @@ class AdultSwimIE(InfoExtractor):
|
|||||||
if video_info is None:
|
if video_info is None:
|
||||||
if bootstrapped_data.get('slugged_video', {}).get('slug') == episode_path:
|
if bootstrapped_data.get('slugged_video', {}).get('slug') == episode_path:
|
||||||
video_info = bootstrapped_data['slugged_video']
|
video_info = bootstrapped_data['slugged_video']
|
||||||
else:
|
if not video_info:
|
||||||
raise ExtractorError('Unable to find video info')
|
video_info = bootstrapped_data.get('heroMetadata', {}).get('trailer').get('video')
|
||||||
|
if not video_info:
|
||||||
|
raise ExtractorError('Unable to find video info')
|
||||||
|
|
||||||
show = bootstrapped_data['show']
|
show = bootstrapped_data['show']
|
||||||
show_title = show['title']
|
show_title = show['title']
|
||||||
stream = video_info.get('stream')
|
stream = video_info.get('stream')
|
||||||
clips = [stream] if stream else video_info.get('clips')
|
if stream and stream.get('videoPlaybackID'):
|
||||||
if not clips:
|
segment_ids = [stream['videoPlaybackID']]
|
||||||
|
elif video_info.get('clips'):
|
||||||
|
segment_ids = [clip['videoPlaybackID'] for clip in video_info['clips']]
|
||||||
|
elif video_info.get('videoPlaybackID'):
|
||||||
|
segment_ids = [video_info['videoPlaybackID']]
|
||||||
|
else:
|
||||||
raise ExtractorError(
|
raise ExtractorError(
|
||||||
'This video is only available via cable service provider subscription that'
|
'This video is only available via cable service provider subscription that'
|
||||||
' is not currently supported. You may want to use --cookies.'
|
' is not currently supported. You may want to use --cookies.'
|
||||||
if video_info.get('auth') is True else 'Unable to find stream or clips',
|
if video_info.get('auth') is True else 'Unable to find stream or clips',
|
||||||
expected=True)
|
expected=True)
|
||||||
segment_ids = [clip['videoPlaybackID'] for clip in clips]
|
|
||||||
|
|
||||||
episode_id = video_info['id']
|
episode_id = video_info['id']
|
||||||
episode_title = video_info['title']
|
episode_title = video_info['title']
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .theplatform import ThePlatformIE
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
smuggle_url,
|
smuggle_url,
|
||||||
update_url_query,
|
update_url_query,
|
||||||
@@ -15,28 +15,15 @@ from ..compat import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class AENetworksBaseIE(InfoExtractor):
|
class AENetworksBaseIE(ThePlatformIE):
|
||||||
def theplatform_url_result(self, theplatform_url, video_id, query):
|
_THEPLATFORM_KEY = 'crazyjava'
|
||||||
return {
|
_THEPLATFORM_SECRET = 's3cr3t'
|
||||||
'_type': 'url_transparent',
|
|
||||||
'id': video_id,
|
|
||||||
'url': smuggle_url(
|
|
||||||
update_url_query(theplatform_url, query),
|
|
||||||
{
|
|
||||||
'sig': {
|
|
||||||
'key': 'crazyjava',
|
|
||||||
'secret': 's3cr3t'
|
|
||||||
},
|
|
||||||
'force_smil_url': True
|
|
||||||
}),
|
|
||||||
'ie_key': 'ThePlatform',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class AENetworksIE(AENetworksBaseIE):
|
class AENetworksIE(AENetworksBaseIE):
|
||||||
IE_NAME = 'aenetworks'
|
IE_NAME = 'aenetworks'
|
||||||
IE_DESC = 'A+E Networks: A&E, Lifetime, History.com, FYI Network'
|
IE_DESC = 'A+E Networks: A&E, Lifetime, History.com, FYI Network'
|
||||||
_VALID_URL = r'https?://(?:www\.)?(?:(?:history|aetv|mylifetime)\.com|fyi\.tv)/(?:shows/(?P<show_path>[^/]+(?:/[^/]+){0,2})|movies/(?P<movie_display_id>[^/]+)/full-movie)'
|
_VALID_URL = r'https?://(?:www\.)?(?P<domain>(?:history|aetv|mylifetime)\.com|fyi\.tv)/(?:shows/(?P<show_path>[^/]+(?:/[^/]+){0,2})|movies/(?P<movie_display_id>[^/]+)/full-movie)'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.history.com/shows/mountain-men/season-1/episode-1',
|
'url': 'http://www.history.com/shows/mountain-men/season-1/episode-1',
|
||||||
'md5': '8ff93eb073449f151d6b90c0ae1ef0c7',
|
'md5': '8ff93eb073449f151d6b90c0ae1ef0c7',
|
||||||
@@ -76,9 +63,15 @@ class AENetworksIE(AENetworksBaseIE):
|
|||||||
'url': 'http://www.mylifetime.com/movies/center-stage-on-pointe/full-movie',
|
'url': 'http://www.mylifetime.com/movies/center-stage-on-pointe/full-movie',
|
||||||
'only_matching': True
|
'only_matching': True
|
||||||
}]
|
}]
|
||||||
|
_DOMAIN_TO_REQUESTOR_ID = {
|
||||||
|
'history.com': 'HISTORY',
|
||||||
|
'aetv.com': 'AETV',
|
||||||
|
'mylifetime.com': 'LIFETIME',
|
||||||
|
'fyi.tv': 'FYI',
|
||||||
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
show_path, movie_display_id = re.match(self._VALID_URL, url).groups()
|
domain, show_path, movie_display_id = re.match(self._VALID_URL, url).groups()
|
||||||
display_id = show_path or movie_display_id
|
display_id = show_path or movie_display_id
|
||||||
webpage = self._download_webpage(url, display_id)
|
webpage = self._download_webpage(url, display_id)
|
||||||
if show_path:
|
if show_path:
|
||||||
@@ -103,23 +96,42 @@ class AENetworksIE(AENetworksBaseIE):
|
|||||||
episode_attributes['data-videoid']))
|
episode_attributes['data-videoid']))
|
||||||
return self.playlist_result(
|
return self.playlist_result(
|
||||||
entries, self._html_search_meta('aetn:SeasonId', webpage))
|
entries, self._html_search_meta('aetn:SeasonId', webpage))
|
||||||
|
|
||||||
|
query = {
|
||||||
|
'mbr': 'true',
|
||||||
|
'assetTypes': 'medium_video_s3'
|
||||||
|
}
|
||||||
video_id = self._html_search_meta('aetn:VideoID', webpage)
|
video_id = self._html_search_meta('aetn:VideoID', webpage)
|
||||||
media_url = self._search_regex(
|
media_url = self._search_regex(
|
||||||
r"media_url\s*=\s*'([^']+)'", webpage, 'video url')
|
r"media_url\s*=\s*'([^']+)'", webpage, 'video url')
|
||||||
|
theplatform_metadata = self._download_theplatform_metadata(self._search_regex(
|
||||||
info = self._search_json_ld(webpage, video_id, fatal=False)
|
r'https?://link.theplatform.com/s/([^?]+)', media_url, 'theplatform_path'), video_id)
|
||||||
info.update(self.theplatform_url_result(
|
info = self._parse_theplatform_metadata(theplatform_metadata)
|
||||||
media_url, video_id, {
|
if theplatform_metadata.get('AETN$isBehindWall'):
|
||||||
'mbr': 'true',
|
requestor_id = self._DOMAIN_TO_REQUESTOR_ID[domain]
|
||||||
'assetTypes': 'medium_video_s3'
|
resource = self._get_mvpd_resource(
|
||||||
}))
|
requestor_id, theplatform_metadata['title'],
|
||||||
|
theplatform_metadata.get('AETN$PPL_pplProgramId') or theplatform_metadata.get('AETN$PPL_pplProgramId_OLD'),
|
||||||
|
theplatform_metadata['ratings'][0]['rating'])
|
||||||
|
query['auth'] = self._extract_mvpd_auth(
|
||||||
|
url, video_id, requestor_id, resource)
|
||||||
|
info.update(self._search_json_ld(webpage, video_id, fatal=False))
|
||||||
|
media_url = update_url_query(media_url, query)
|
||||||
|
media_url = self._sign_url(media_url, self._THEPLATFORM_KEY, self._THEPLATFORM_SECRET)
|
||||||
|
formats, subtitles = self._extract_theplatform_smil(media_url, video_id)
|
||||||
|
self._sort_formats(formats)
|
||||||
|
info.update({
|
||||||
|
'id': video_id,
|
||||||
|
'formats': formats,
|
||||||
|
'subtitles': subtitles,
|
||||||
|
})
|
||||||
return info
|
return info
|
||||||
|
|
||||||
|
|
||||||
class HistoryTopicIE(AENetworksBaseIE):
|
class HistoryTopicIE(AENetworksBaseIE):
|
||||||
IE_NAME = 'history:topic'
|
IE_NAME = 'history:topic'
|
||||||
IE_DESC = 'History.com Topic'
|
IE_DESC = 'History.com Topic'
|
||||||
_VALID_URL = r'https?://(?:www\.)?history\.com/topics/(?:[^/]+/)?(?P<topic_id>[^/]+)/videos(?:/(?P<video_display_id>[^/?#]+))?'
|
_VALID_URL = r'https?://(?:www\.)?history\.com/topics/(?:[^/]+/)?(?P<topic_id>[^/]+)(?:/[^/]+(?:/(?P<video_display_id>[^/?#]+))?)?'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.history.com/topics/valentines-day/history-of-valentines-day/videos/bet-you-didnt-know-valentines-day?m=528e394da93ae&s=undefined&f=1&free=false',
|
'url': 'http://www.history.com/topics/valentines-day/history-of-valentines-day/videos/bet-you-didnt-know-valentines-day?m=528e394da93ae&s=undefined&f=1&free=false',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@@ -147,8 +159,30 @@ class HistoryTopicIE(AENetworksBaseIE):
|
|||||||
}, {
|
}, {
|
||||||
'url': 'http://www.history.com/topics/world-war-i-history/videos',
|
'url': 'http://www.history.com/topics/world-war-i-history/videos',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.history.com/topics/world-war-i/world-war-i-history',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.history.com/topics/world-war-i/world-war-i-history/speeches',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
|
def theplatform_url_result(self, theplatform_url, video_id, query):
|
||||||
|
return {
|
||||||
|
'_type': 'url_transparent',
|
||||||
|
'id': video_id,
|
||||||
|
'url': smuggle_url(
|
||||||
|
update_url_query(theplatform_url, query),
|
||||||
|
{
|
||||||
|
'sig': {
|
||||||
|
'key': self._THEPLATFORM_KEY,
|
||||||
|
'secret': self._THEPLATFORM_SECRET,
|
||||||
|
},
|
||||||
|
'force_smil_url': True
|
||||||
|
}),
|
||||||
|
'ie_key': 'ThePlatform',
|
||||||
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
topic_id, video_display_id = re.match(self._VALID_URL, url).groups()
|
topic_id, video_display_id = re.match(self._VALID_URL, url).groups()
|
||||||
if video_display_id:
|
if video_display_id:
|
||||||
|
|||||||
91
youtube_dl/extractor/amcnetworks.py
Normal file
91
youtube_dl/extractor/amcnetworks.py
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .theplatform import ThePlatformIE
|
||||||
|
from ..utils import (
|
||||||
|
update_url_query,
|
||||||
|
parse_age_limit,
|
||||||
|
int_or_none,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AMCNetworksIE(ThePlatformIE):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?(?:amc|bbcamerica|ifc|wetv)\.com/(?:movies/|shows/[^/]+/(?:full-episodes/)?season-\d+/episode-\d+(?:-(?:[^/]+/)?|/))(?P<id>[^/?#]+)'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://www.ifc.com/shows/maron/season-04/episode-01/step-1',
|
||||||
|
'md5': '',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 's3MX01Nl4vPH',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Maron - Season 4 - Step 1',
|
||||||
|
'description': 'In denial about his current situation, Marc is reluctantly convinced by his friends to enter rehab. Starring Marc Maron and Constance Zimmer.',
|
||||||
|
'age_limit': 17,
|
||||||
|
'upload_date': '20160505',
|
||||||
|
'timestamp': 1462468831,
|
||||||
|
'uploader': 'AMCN',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# m3u8 download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.bbcamerica.com/shows/the-hunt/full-episodes/season-1/episode-01-the-hardest-challenge',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.amc.com/shows/preacher/full-episodes/season-01/episode-00/pilot',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.wetv.com/shows/million-dollar-matchmaker/season-01/episode-06-the-dumped-dj-and-shallow-hal',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.ifc.com/movies/chaos',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
display_id = self._match_id(url)
|
||||||
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
query = {
|
||||||
|
'mbr': 'true',
|
||||||
|
'manifest': 'm3u',
|
||||||
|
}
|
||||||
|
media_url = self._search_regex(r'window\.platformLinkURL\s*=\s*[\'"]([^\'"]+)', webpage, 'media url')
|
||||||
|
theplatform_metadata = self._download_theplatform_metadata(self._search_regex(
|
||||||
|
r'https?://link.theplatform.com/s/([^?]+)', media_url, 'theplatform_path'), display_id)
|
||||||
|
info = self._parse_theplatform_metadata(theplatform_metadata)
|
||||||
|
video_id = theplatform_metadata['pid']
|
||||||
|
title = theplatform_metadata['title']
|
||||||
|
rating = theplatform_metadata['ratings'][0]['rating']
|
||||||
|
auth_required = self._search_regex(r'window\.authRequired\s*=\s*(true|false);', webpage, 'auth required')
|
||||||
|
if auth_required == 'true':
|
||||||
|
requestor_id = self._search_regex(r'window\.requestor_id\s*=\s*[\'"]([^\'"]+)', webpage, 'requestor id')
|
||||||
|
resource = self._get_mvpd_resource(requestor_id, title, video_id, rating)
|
||||||
|
query['auth'] = self._extract_mvpd_auth(url, video_id, requestor_id, resource)
|
||||||
|
media_url = update_url_query(media_url, query)
|
||||||
|
formats, subtitles = self._extract_theplatform_smil(media_url, video_id)
|
||||||
|
self._sort_formats(formats)
|
||||||
|
info.update({
|
||||||
|
'id': video_id,
|
||||||
|
'subtitles': subtitles,
|
||||||
|
'formats': formats,
|
||||||
|
'age_limit': parse_age_limit(parse_age_limit(rating)),
|
||||||
|
})
|
||||||
|
ns_keys = theplatform_metadata.get('$xmlns', {}).keys()
|
||||||
|
if ns_keys:
|
||||||
|
ns = list(ns_keys)[0]
|
||||||
|
series = theplatform_metadata.get(ns + '$show')
|
||||||
|
season_number = int_or_none(theplatform_metadata.get(ns + '$season'))
|
||||||
|
episode = theplatform_metadata.get(ns + '$episodeTitle')
|
||||||
|
episode_number = int_or_none(theplatform_metadata.get(ns + '$episode'))
|
||||||
|
if season_number:
|
||||||
|
title = 'Season %d - %s' % (season_number, title)
|
||||||
|
if series:
|
||||||
|
title = '%s - %s' % (series, title)
|
||||||
|
info.update({
|
||||||
|
'title': title,
|
||||||
|
'series': series,
|
||||||
|
'season_number': season_number,
|
||||||
|
'episode': episode,
|
||||||
|
'episode_number': episode_number,
|
||||||
|
})
|
||||||
|
return info
|
||||||
@@ -5,6 +5,8 @@ from .common import InfoExtractor
|
|||||||
from ..utils import (
|
from ..utils import (
|
||||||
int_or_none,
|
int_or_none,
|
||||||
parse_iso8601,
|
parse_iso8601,
|
||||||
|
mimetype2ext,
|
||||||
|
determine_ext,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -50,21 +52,25 @@ class AMPIE(InfoExtractor):
|
|||||||
if isinstance(media_content, dict):
|
if isinstance(media_content, dict):
|
||||||
media_content = [media_content]
|
media_content = [media_content]
|
||||||
for media_data in media_content:
|
for media_data in media_content:
|
||||||
media = media_data['@attributes']
|
media = media_data.get('@attributes', {})
|
||||||
media_type = media['type']
|
media_url = media.get('url')
|
||||||
if media_type in ('video/f4m', 'application/f4m+xml'):
|
if not media_url:
|
||||||
|
continue
|
||||||
|
ext = mimetype2ext(media.get('type')) or determine_ext(media_url)
|
||||||
|
if ext == 'f4m':
|
||||||
formats.extend(self._extract_f4m_formats(
|
formats.extend(self._extract_f4m_formats(
|
||||||
media['url'] + '?hdcore=3.4.0&plugin=aasp-3.4.0.132.124',
|
media_url + '?hdcore=3.4.0&plugin=aasp-3.4.0.132.124',
|
||||||
video_id, f4m_id='hds', fatal=False))
|
video_id, f4m_id='hds', fatal=False))
|
||||||
elif media_type == 'application/x-mpegURL':
|
elif ext == 'm3u8':
|
||||||
formats.extend(self._extract_m3u8_formats(
|
formats.extend(self._extract_m3u8_formats(
|
||||||
media['url'], video_id, 'mp4', m3u8_id='hls', fatal=False))
|
media_url, video_id, 'mp4', m3u8_id='hls', fatal=False))
|
||||||
else:
|
else:
|
||||||
formats.append({
|
formats.append({
|
||||||
'format_id': media_data.get('media-category', {}).get('@attributes', {}).get('label'),
|
'format_id': media_data.get('media-category', {}).get('@attributes', {}).get('label'),
|
||||||
'url': media['url'],
|
'url': media['url'],
|
||||||
'tbr': int_or_none(media.get('bitrate')),
|
'tbr': int_or_none(media.get('bitrate')),
|
||||||
'filesize': int_or_none(media.get('fileSize')),
|
'filesize': int_or_none(media.get('fileSize')),
|
||||||
|
'ext': ext,
|
||||||
})
|
})
|
||||||
|
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class AnimeOnDemandIE(InfoExtractor):
|
|||||||
_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'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
|
# jap, OmU
|
||||||
'url': 'https://www.anime-on-demand.de/anime/161',
|
'url': 'https://www.anime-on-demand.de/anime/161',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '161',
|
'id': '161',
|
||||||
@@ -30,17 +31,21 @@ class AnimeOnDemandIE(InfoExtractor):
|
|||||||
},
|
},
|
||||||
'playlist_mincount': 4,
|
'playlist_mincount': 4,
|
||||||
}, {
|
}, {
|
||||||
# Film wording is used instead of Episode
|
# Film wording is used instead of Episode, ger/jap, Dub/OmU
|
||||||
'url': 'https://www.anime-on-demand.de/anime/39',
|
'url': 'https://www.anime-on-demand.de/anime/39',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
}, {
|
}, {
|
||||||
# Episodes without titles
|
# Episodes without titles, jap, OmU
|
||||||
'url': 'https://www.anime-on-demand.de/anime/162',
|
'url': 'https://www.anime-on-demand.de/anime/162',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
}, {
|
}, {
|
||||||
# ger/jap, Dub/OmU, account required
|
# ger/jap, Dub/OmU, account required
|
||||||
'url': 'https://www.anime-on-demand.de/anime/169',
|
'url': 'https://www.anime-on-demand.de/anime/169',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
# Full length film, non-series, ger/jap, Dub/OmU, account required
|
||||||
|
'url': 'https://www.anime-on-demand.de/anime/185',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _login(self):
|
def _login(self):
|
||||||
@@ -110,35 +115,12 @@ class AnimeOnDemandIE(InfoExtractor):
|
|||||||
|
|
||||||
entries = []
|
entries = []
|
||||||
|
|
||||||
for num, episode_html in enumerate(re.findall(
|
def extract_info(html, video_id, num=None):
|
||||||
r'(?s)<h3[^>]+class="episodebox-title".+?>Episodeninhalt<', webpage), 1):
|
title, description = [None] * 2
|
||||||
episodebox_title = self._search_regex(
|
|
||||||
(r'class="episodebox-title"[^>]+title=(["\'])(?P<title>.+?)\1',
|
|
||||||
r'class="episodebox-title"[^>]+>(?P<title>.+?)<'),
|
|
||||||
episode_html, 'episodebox title', default=None, group='title')
|
|
||||||
if not episodebox_title:
|
|
||||||
continue
|
|
||||||
|
|
||||||
episode_number = int(self._search_regex(
|
|
||||||
r'(?:Episode|Film)\s*(\d+)',
|
|
||||||
episodebox_title, 'episode number', default=num))
|
|
||||||
episode_title = self._search_regex(
|
|
||||||
r'(?:Episode|Film)\s*\d+\s*-\s*(.+)',
|
|
||||||
episodebox_title, 'episode title', default=None)
|
|
||||||
|
|
||||||
video_id = 'episode-%d' % episode_number
|
|
||||||
|
|
||||||
common_info = {
|
|
||||||
'id': video_id,
|
|
||||||
'series': anime_title,
|
|
||||||
'episode': episode_title,
|
|
||||||
'episode_number': episode_number,
|
|
||||||
}
|
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
|
|
||||||
for input_ in re.findall(
|
for input_ in re.findall(
|
||||||
r'<input[^>]+class=["\'].*?streamstarter_html5[^>]+>', episode_html):
|
r'<input[^>]+class=["\'].*?streamstarter_html5[^>]+>', html):
|
||||||
attributes = extract_attributes(input_)
|
attributes = extract_attributes(input_)
|
||||||
playlist_urls = []
|
playlist_urls = []
|
||||||
for playlist_key in ('data-playlist', 'data-otherplaylist'):
|
for playlist_key in ('data-playlist', 'data-otherplaylist'):
|
||||||
@@ -161,7 +143,7 @@ class AnimeOnDemandIE(InfoExtractor):
|
|||||||
format_id_list.append(lang)
|
format_id_list.append(lang)
|
||||||
if kind:
|
if kind:
|
||||||
format_id_list.append(kind)
|
format_id_list.append(kind)
|
||||||
if not format_id_list:
|
if not format_id_list and num is not None:
|
||||||
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)))
|
||||||
@@ -215,28 +197,74 @@ class AnimeOnDemandIE(InfoExtractor):
|
|||||||
})
|
})
|
||||||
formats.extend(file_formats)
|
formats.extend(file_formats)
|
||||||
|
|
||||||
if formats:
|
return {
|
||||||
self._sort_formats(formats)
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'formats': formats,
|
||||||
|
}
|
||||||
|
|
||||||
|
def extract_entries(html, video_id, common_info, num=None):
|
||||||
|
info = extract_info(html, video_id, num)
|
||||||
|
|
||||||
|
if info['formats']:
|
||||||
|
self._sort_formats(info['formats'])
|
||||||
f = common_info.copy()
|
f = common_info.copy()
|
||||||
f.update({
|
f.update(info)
|
||||||
'title': title,
|
|
||||||
'description': description,
|
|
||||||
'formats': formats,
|
|
||||||
})
|
|
||||||
entries.append(f)
|
entries.append(f)
|
||||||
|
|
||||||
# Extract teaser only when full episode is not available
|
# Extract teaser/trailer only when full episode is not available
|
||||||
if not formats:
|
if not info['formats']:
|
||||||
m = re.search(
|
m = re.search(
|
||||||
r'data-dialog-header=(["\'])(?P<title>.+?)\1[^>]+href=(["\'])(?P<href>.+?)\3[^>]*>Teaser<',
|
r'data-dialog-header=(["\'])(?P<title>.+?)\1[^>]+href=(["\'])(?P<href>.+?)\3[^>]*>(?P<kind>Teaser|Trailer)<',
|
||||||
episode_html)
|
html)
|
||||||
if m:
|
if m:
|
||||||
f = common_info.copy()
|
f = common_info.copy()
|
||||||
f.update({
|
f.update({
|
||||||
'id': '%s-teaser' % f['id'],
|
'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': compat_urlparse.urljoin(url, m.group('href')),
|
||||||
})
|
})
|
||||||
entries.append(f)
|
entries.append(f)
|
||||||
|
|
||||||
|
def extract_episodes(html):
|
||||||
|
for num, episode_html in enumerate(re.findall(
|
||||||
|
r'(?s)<h3[^>]+class="episodebox-title".+?>Episodeninhalt<', html), 1):
|
||||||
|
episodebox_title = self._search_regex(
|
||||||
|
(r'class="episodebox-title"[^>]+title=(["\'])(?P<title>.+?)\1',
|
||||||
|
r'class="episodebox-title"[^>]+>(?P<title>.+?)<'),
|
||||||
|
episode_html, 'episodebox title', default=None, group='title')
|
||||||
|
if not episodebox_title:
|
||||||
|
continue
|
||||||
|
|
||||||
|
episode_number = int(self._search_regex(
|
||||||
|
r'(?:Episode|Film)\s*(\d+)',
|
||||||
|
episodebox_title, 'episode number', default=num))
|
||||||
|
episode_title = self._search_regex(
|
||||||
|
r'(?:Episode|Film)\s*\d+\s*-\s*(.+)',
|
||||||
|
episodebox_title, 'episode title', default=None)
|
||||||
|
|
||||||
|
video_id = 'episode-%d' % episode_number
|
||||||
|
|
||||||
|
common_info = {
|
||||||
|
'id': video_id,
|
||||||
|
'series': anime_title,
|
||||||
|
'episode': episode_title,
|
||||||
|
'episode_number': episode_number,
|
||||||
|
}
|
||||||
|
|
||||||
|
extract_entries(episode_html, video_id, common_info)
|
||||||
|
|
||||||
|
def extract_film(html, video_id):
|
||||||
|
common_info = {
|
||||||
|
'id': anime_id,
|
||||||
|
'title': anime_title,
|
||||||
|
'description': anime_description,
|
||||||
|
}
|
||||||
|
extract_entries(html, video_id, common_info)
|
||||||
|
|
||||||
|
extract_episodes(webpage)
|
||||||
|
|
||||||
|
if not entries:
|
||||||
|
extract_film(webpage, anime_id)
|
||||||
|
|
||||||
return self.playlist_result(entries, anime_id, anime_title, anime_description)
|
return self.playlist_result(entries, anime_id, anime_title, anime_description)
|
||||||
|
|||||||
@@ -123,6 +123,10 @@ class AolFeaturesIE(InfoExtractor):
|
|||||||
'title': 'What To Watch - February 17, 2016',
|
'title': 'What To Watch - February 17, 2016',
|
||||||
},
|
},
|
||||||
'add_ie': ['FiveMin'],
|
'add_ie': ['FiveMin'],
|
||||||
|
'params': {
|
||||||
|
# encrypted m3u8 download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
@@ -15,7 +13,7 @@ class AparatIE(InfoExtractor):
|
|||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.aparat.com/v/wP8On',
|
'url': 'http://www.aparat.com/v/wP8On',
|
||||||
'md5': '6714e0af7e0d875c5a39c4dc4ab46ad1',
|
'md5': '131aca2e14fe7c4dcb3c4877ba300c89',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'wP8On',
|
'id': 'wP8On',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
@@ -31,13 +29,13 @@ 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/videohash/' +
|
embed_url = 'http://www.aparat.com/video/video/embed/vt/frame/showvideo/yes/videohash/' + video_id
|
||||||
video_id + '/vt/frame')
|
|
||||||
webpage = self._download_webpage(embed_url, video_id)
|
webpage = self._download_webpage(embed_url, video_id)
|
||||||
|
|
||||||
video_urls = [video_url.replace('\\/', '/') for video_url in re.findall(
|
file_list = self._parse_json(self._search_regex(
|
||||||
r'(?:fileList\[[0-9]+\]\s*=|"file"\s*:)\s*"([^"]+)"', webpage)]
|
r'fileList\s*=\s*JSON\.parse\(\'([^\']+)\'\)', webpage, 'file list'), video_id)
|
||||||
for i, video_url in enumerate(video_urls):
|
for i, item in enumerate(file_list[0]):
|
||||||
|
video_url = item['file']
|
||||||
req = HEADRequest(video_url)
|
req = HEADRequest(video_url)
|
||||||
res = self._request_webpage(
|
res = self._request_webpage(
|
||||||
req, video_id, note='Testing video URL %d' % i, errnote=False)
|
req, video_id, note='Testing video URL %d' % i, errnote=False)
|
||||||
|
|||||||
@@ -1,67 +1,65 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .jwplatform import JWPlatformBaseIE
|
||||||
from ..utils import unified_strdate
|
from ..utils import (
|
||||||
|
unified_strdate,
|
||||||
|
clean_html,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ArchiveOrgIE(InfoExtractor):
|
class ArchiveOrgIE(JWPlatformBaseIE):
|
||||||
IE_NAME = 'archive.org'
|
IE_NAME = 'archive.org'
|
||||||
IE_DESC = 'archive.org videos'
|
IE_DESC = 'archive.org videos'
|
||||||
_VALID_URL = r'https?://(?:www\.)?archive\.org/details/(?P<id>[^?/]+)(?:[?].*)?$'
|
_VALID_URL = r'https?://(?:www\.)?archive\.org/(?:details|embed)/(?P<id>[^/?#]+)(?:[?].*)?$'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://archive.org/details/XD300-23_68HighlightsAResearchCntAugHumanIntellect',
|
'url': 'http://archive.org/details/XD300-23_68HighlightsAResearchCntAugHumanIntellect',
|
||||||
'md5': '8af1d4cf447933ed3c7f4871162602db',
|
'md5': '8af1d4cf447933ed3c7f4871162602db',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'XD300-23_68HighlightsAResearchCntAugHumanIntellect',
|
'id': 'XD300-23_68HighlightsAResearchCntAugHumanIntellect',
|
||||||
'ext': 'ogv',
|
'ext': 'ogg',
|
||||||
'title': '1968 Demo - FJCC Conference Presentation Reel #1',
|
'title': '1968 Demo - FJCC Conference Presentation Reel #1',
|
||||||
'description': 'md5:1780b464abaca9991d8968c877bb53ed',
|
'description': 'md5:da45c349df039f1cc8075268eb1b5c25',
|
||||||
'upload_date': '19681210',
|
'upload_date': '19681210',
|
||||||
'uploader': 'SRI International'
|
'uploader': 'SRI International'
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
'url': 'https://archive.org/details/Cops1922',
|
'url': 'https://archive.org/details/Cops1922',
|
||||||
'md5': '18f2a19e6d89af8425671da1cf3d4e04',
|
'md5': 'bc73c8ab3838b5a8fc6c6651fa7b58ba',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'Cops1922',
|
'id': 'Cops1922',
|
||||||
'ext': 'ogv',
|
'ext': 'mp4',
|
||||||
'title': 'Buster Keaton\'s "Cops" (1922)',
|
'title': 'Buster Keaton\'s "Cops" (1922)',
|
||||||
'description': 'md5:70f72ee70882f713d4578725461ffcc3',
|
'description': 'md5:b4544662605877edd99df22f9620d858',
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
'url': 'http://archive.org/embed/XD300-23_68HighlightsAResearchCntAugHumanIntellect',
|
||||||
|
'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(
|
||||||
|
'http://archive.org/embed/' + video_id, video_id)
|
||||||
|
jwplayer_playlist = self._parse_json(self._search_regex(
|
||||||
|
r"(?s)Play\('[^']+'\s*,\s*(\[.+\])\s*,\s*{.*?}\);",
|
||||||
|
webpage, 'jwplayer playlist'), video_id)
|
||||||
|
info = self._parse_jwplayer_data(
|
||||||
|
{'playlist': jwplayer_playlist}, video_id, base_url=url)
|
||||||
|
|
||||||
json_url = url + ('&' if '?' in url else '?') + 'output=json'
|
def get_optional(metadata, field):
|
||||||
data = self._download_json(json_url, video_id)
|
return metadata.get(field, [None])[0]
|
||||||
|
|
||||||
def get_optional(data_dict, field):
|
metadata = self._download_json(
|
||||||
return data_dict['metadata'].get(field, [None])[0]
|
'http://archive.org/details/' + video_id, video_id, query={
|
||||||
|
'output': 'json',
|
||||||
title = get_optional(data, 'title')
|
})['metadata']
|
||||||
description = get_optional(data, 'description')
|
info.update({
|
||||||
uploader = get_optional(data, 'creator')
|
'title': get_optional(metadata, 'title') or info.get('title'),
|
||||||
upload_date = unified_strdate(get_optional(data, 'date'))
|
'description': clean_html(get_optional(metadata, 'description')),
|
||||||
|
})
|
||||||
formats = [
|
if info.get('_type') != 'playlist':
|
||||||
{
|
info.update({
|
||||||
'format': fdata['format'],
|
'uploader': get_optional(metadata, 'creator'),
|
||||||
'url': 'http://' + data['server'] + data['dir'] + fn,
|
'upload_date': unified_strdate(get_optional(metadata, 'date')),
|
||||||
'file_size': int(fdata['size']),
|
})
|
||||||
}
|
return info
|
||||||
for fn, fdata in data['files'].items()
|
|
||||||
if 'Video' in fdata['format']]
|
|
||||||
|
|
||||||
self._sort_formats(formats)
|
|
||||||
|
|
||||||
return {
|
|
||||||
'_type': 'video',
|
|
||||||
'id': video_id,
|
|
||||||
'title': title,
|
|
||||||
'formats': formats,
|
|
||||||
'description': description,
|
|
||||||
'uploader': uploader,
|
|
||||||
'upload_date': upload_date,
|
|
||||||
'thumbnail': data.get('misc', {}).get('image'),
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -13,13 +13,14 @@ from ..utils import (
|
|||||||
parse_duration,
|
parse_duration,
|
||||||
unified_strdate,
|
unified_strdate,
|
||||||
xpath_text,
|
xpath_text,
|
||||||
|
update_url_query,
|
||||||
)
|
)
|
||||||
from ..compat import compat_etree_fromstring
|
from ..compat import compat_etree_fromstring
|
||||||
|
|
||||||
|
|
||||||
class ARDMediathekIE(InfoExtractor):
|
class ARDMediathekIE(InfoExtractor):
|
||||||
IE_NAME = 'ARD:mediathek'
|
IE_NAME = 'ARD:mediathek'
|
||||||
_VALID_URL = r'^https?://(?:(?:www\.)?ardmediathek\.de|mediathek\.daserste\.de)/(?:.*/)(?P<video_id>[0-9]+|[^0-9][^/\?]+)[^/\?]*(?:\?.*)?'
|
_VALID_URL = r'^https?://(?:(?:www\.)?ardmediathek\.de|mediathek\.(?:daserste|rbb-online)\.de)/(?:.*/)(?P<video_id>[0-9]+|[^0-9][^/\?]+)[^/\?]*(?:\?.*)?'
|
||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.ardmediathek.de/tv/Dokumentation-und-Reportage/Ich-liebe-das-Leben-trotzdem/rbb-Fernsehen/Video?documentId=29582122&bcastId=3822114',
|
'url': 'http://www.ardmediathek.de/tv/Dokumentation-und-Reportage/Ich-liebe-das-Leben-trotzdem/rbb-Fernsehen/Video?documentId=29582122&bcastId=3822114',
|
||||||
@@ -34,6 +35,7 @@ class ARDMediathekIE(InfoExtractor):
|
|||||||
# m3u8 download
|
# m3u8 download
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
},
|
},
|
||||||
|
'skip': 'HTTP Error 404: Not Found',
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.ardmediathek.de/tv/Tatort/Tatort-Scheinwelten-H%C3%B6rfassung-Video/Das-Erste/Video?documentId=29522730&bcastId=602916',
|
'url': 'http://www.ardmediathek.de/tv/Tatort/Tatort-Scheinwelten-H%C3%B6rfassung-Video/Das-Erste/Video?documentId=29522730&bcastId=602916',
|
||||||
'md5': 'f4d98b10759ac06c0072bbcd1f0b9e3e',
|
'md5': 'f4d98b10759ac06c0072bbcd1f0b9e3e',
|
||||||
@@ -44,6 +46,7 @@ class ARDMediathekIE(InfoExtractor):
|
|||||||
'description': 'md5:196392e79876d0ac94c94e8cdb2875f1',
|
'description': 'md5:196392e79876d0ac94c94e8cdb2875f1',
|
||||||
'duration': 5252,
|
'duration': 5252,
|
||||||
},
|
},
|
||||||
|
'skip': 'HTTP Error 404: Not Found',
|
||||||
}, {
|
}, {
|
||||||
# audio
|
# audio
|
||||||
'url': 'http://www.ardmediathek.de/tv/WDR-H%C3%B6rspiel-Speicher/Tod-eines-Fu%C3%9Fballers/WDR-3/Audio-Podcast?documentId=28488308&bcastId=23074086',
|
'url': 'http://www.ardmediathek.de/tv/WDR-H%C3%B6rspiel-Speicher/Tod-eines-Fu%C3%9Fballers/WDR-3/Audio-Podcast?documentId=28488308&bcastId=23074086',
|
||||||
@@ -55,9 +58,22 @@ class ARDMediathekIE(InfoExtractor):
|
|||||||
'description': 'md5:f6e39f3461f0e1f54bfa48c8875c86ef',
|
'description': 'md5:f6e39f3461f0e1f54bfa48c8875c86ef',
|
||||||
'duration': 3240,
|
'duration': 3240,
|
||||||
},
|
},
|
||||||
|
'skip': 'HTTP Error 404: Not Found',
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://mediathek.daserste.de/sendungen_a-z/328454_anne-will/22429276_vertrauen-ist-gut-spionieren-ist-besser-geht',
|
'url': 'http://mediathek.daserste.de/sendungen_a-z/328454_anne-will/22429276_vertrauen-ist-gut-spionieren-ist-besser-geht',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
# audio
|
||||||
|
'url': 'http://mediathek.rbb-online.de/radio/Hörspiel/Vor-dem-Fest/kulturradio/Audio?documentId=30796318&topRessort=radio&bcastId=9839158',
|
||||||
|
'md5': '4e8f00631aac0395fee17368ac0e9867',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '30796318',
|
||||||
|
'ext': 'mp3',
|
||||||
|
'title': 'Vor dem Fest',
|
||||||
|
'description': 'md5:c0c1c8048514deaed2a73b3a60eecacb',
|
||||||
|
'duration': 3287,
|
||||||
|
},
|
||||||
|
'skip': 'Video is no longer available',
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _extract_media_info(self, media_info_url, webpage, video_id):
|
def _extract_media_info(self, media_info_url, webpage, video_id):
|
||||||
@@ -113,11 +129,14 @@ class ARDMediathekIE(InfoExtractor):
|
|||||||
continue
|
continue
|
||||||
if ext == 'f4m':
|
if ext == 'f4m':
|
||||||
formats.extend(self._extract_f4m_formats(
|
formats.extend(self._extract_f4m_formats(
|
||||||
stream_url + '?hdcore=3.1.1&plugin=aasp-3.1.1.69.124',
|
update_url_query(stream_url, {
|
||||||
video_id, preference=-1, f4m_id='hds', fatal=False))
|
'hdcore': '3.1.1',
|
||||||
|
'plugin': 'aasp-3.1.1.69.124'
|
||||||
|
}),
|
||||||
|
video_id, f4m_id='hds', fatal=False))
|
||||||
elif ext == 'm3u8':
|
elif ext == 'm3u8':
|
||||||
formats.extend(self._extract_m3u8_formats(
|
formats.extend(self._extract_m3u8_formats(
|
||||||
stream_url, video_id, 'mp4', preference=1, m3u8_id='hls', fatal=False))
|
stream_url, video_id, 'mp4', m3u8_id='hls', fatal=False))
|
||||||
else:
|
else:
|
||||||
if server and server.startswith('rtmp'):
|
if server and server.startswith('rtmp'):
|
||||||
f = {
|
f = {
|
||||||
@@ -231,7 +250,8 @@ class ARDIE(InfoExtractor):
|
|||||||
'title': 'Die Story im Ersten: Mission unter falscher Flagge',
|
'title': 'Die Story im Ersten: Mission unter falscher Flagge',
|
||||||
'upload_date': '20140804',
|
'upload_date': '20140804',
|
||||||
'thumbnail': 're:^https?://.*\.jpg$',
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
}
|
},
|
||||||
|
'skip': 'HTTP Error 404: Not Found',
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
|
|||||||
115
youtube_dl/extractor/arkena.py
Normal file
115
youtube_dl/extractor/arkena.py
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
determine_ext,
|
||||||
|
float_or_none,
|
||||||
|
int_or_none,
|
||||||
|
mimetype2ext,
|
||||||
|
parse_iso8601,
|
||||||
|
strip_jsonp,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ArkenaIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://play\.arkena\.com/(?:config|embed)/avp/v\d/player/media/(?P<id>[^/]+)/[^/]+/(?P<account_id>\d+)'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://play.arkena.com/embed/avp/v2/player/media/b41dda37-d8e7-4d3f-b1b5-9a9db578bdfe/1/129411',
|
||||||
|
'md5': 'b96f2f71b359a8ecd05ce4e1daa72365',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'b41dda37-d8e7-4d3f-b1b5-9a9db578bdfe',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Big Buck Bunny',
|
||||||
|
'description': 'Royalty free test video',
|
||||||
|
'timestamp': 1432816365,
|
||||||
|
'upload_date': '20150528',
|
||||||
|
'is_live': False,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'https://play.arkena.com/config/avp/v2/player/media/b41dda37-d8e7-4d3f-b1b5-9a9db578bdfe/1/129411/?callbackMethod=jQuery1111023664739129262213_1469227693893',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://play.arkena.com/config/avp/v1/player/media/327336/darkmatter/131064/?callbackMethod=jQuery1111002221189684892677_1469227595972',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://play.arkena.com/embed/avp/v1/player/media/327336/darkmatter/131064/',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _extract_url(webpage):
|
||||||
|
# See https://support.arkena.com/display/PLAY/Ways+to+embed+your+video
|
||||||
|
mobj = re.search(
|
||||||
|
r'<iframe[^>]+src=(["\'])(?P<url>(?:https?:)?//play\.arkena\.com/embed/avp/.+?)\1',
|
||||||
|
webpage)
|
||||||
|
if mobj:
|
||||||
|
return mobj.group('url')
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
account_id = mobj.group('account_id')
|
||||||
|
|
||||||
|
playlist = self._download_json(
|
||||||
|
'https://play.arkena.com/config/avp/v2/player/media/%s/0/%s/?callbackMethod=_'
|
||||||
|
% (video_id, account_id),
|
||||||
|
video_id, transform_source=strip_jsonp)['Playlist'][0]
|
||||||
|
|
||||||
|
media_info = playlist['MediaInfo']
|
||||||
|
title = media_info['Title']
|
||||||
|
media_files = playlist['MediaFiles']
|
||||||
|
|
||||||
|
is_live = False
|
||||||
|
formats = []
|
||||||
|
for kind_case, kind_formats in media_files.items():
|
||||||
|
kind = kind_case.lower()
|
||||||
|
for f in kind_formats:
|
||||||
|
f_url = f.get('Url')
|
||||||
|
if not f_url:
|
||||||
|
continue
|
||||||
|
is_live = f.get('Live') == 'true'
|
||||||
|
exts = (mimetype2ext(f.get('Type')), determine_ext(f_url, None))
|
||||||
|
if kind == 'm3u8' or 'm3u8' in exts:
|
||||||
|
formats.extend(self._extract_m3u8_formats(
|
||||||
|
f_url, video_id, 'mp4',
|
||||||
|
entry_protocol='m3u8' if is_live else 'm3u8_native',
|
||||||
|
m3u8_id=kind, fatal=False, live=is_live))
|
||||||
|
elif kind == 'flash' or 'f4m' in exts:
|
||||||
|
formats.extend(self._extract_f4m_formats(
|
||||||
|
f_url, video_id, f4m_id=kind, fatal=False))
|
||||||
|
elif kind == 'dash' or 'mpd' in exts:
|
||||||
|
formats.extend(self._extract_mpd_formats(
|
||||||
|
f_url, video_id, mpd_id=kind, fatal=False))
|
||||||
|
elif kind == 'silverlight':
|
||||||
|
# TODO: process when ism is supported (see
|
||||||
|
# https://github.com/rg3/youtube-dl/issues/8118)
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
tbr = float_or_none(f.get('Bitrate'), 1000)
|
||||||
|
formats.append({
|
||||||
|
'url': f_url,
|
||||||
|
'format_id': '%s-%d' % (kind, tbr) if tbr else kind,
|
||||||
|
'tbr': tbr,
|
||||||
|
})
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
description = media_info.get('Description')
|
||||||
|
video_id = media_info.get('VideoId') or video_id
|
||||||
|
timestamp = parse_iso8601(media_info.get('PublishDate'))
|
||||||
|
thumbnails = [{
|
||||||
|
'url': thumbnail['Url'],
|
||||||
|
'width': int_or_none(thumbnail.get('Size')),
|
||||||
|
} for thumbnail in (media_info.get('Poster') or []) if thumbnail.get('Url')]
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'timestamp': timestamp,
|
||||||
|
'is_live': is_live,
|
||||||
|
'thumbnails': thumbnails,
|
||||||
|
'formats': formats,
|
||||||
|
}
|
||||||
@@ -12,46 +12,41 @@ from ..compat import (
|
|||||||
from ..utils import (
|
from ..utils import (
|
||||||
int_or_none,
|
int_or_none,
|
||||||
parse_iso8601,
|
parse_iso8601,
|
||||||
sanitized_Request,
|
|
||||||
smuggle_url,
|
smuggle_url,
|
||||||
unsmuggle_url,
|
unsmuggle_url,
|
||||||
urlencode_postdata,
|
urlencode_postdata,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class DCNIE(InfoExtractor):
|
class AWAANIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?(?:awaan|dcndigital)\.ae/(?:#/)?show/(?P<show_id>\d+)/[^/]+(?:/(?P<video_id>\d+)/(?P<season_id>\d+))?'
|
_VALID_URL = r'https?://(?:www\.)?(?:awaan|dcndigital)\.ae/(?:#/)?show/(?P<show_id>\d+)/[^/]+(?:/(?P<video_id>\d+)/(?P<season_id>\d+))?'
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
show_id, video_id, season_id = re.match(self._VALID_URL, url).groups()
|
show_id, video_id, season_id = re.match(self._VALID_URL, url).groups()
|
||||||
if video_id and int(video_id) > 0:
|
if video_id and int(video_id) > 0:
|
||||||
return self.url_result(
|
return self.url_result(
|
||||||
'http://www.dcndigital.ae/media/%s' % video_id, 'DCNVideo')
|
'http://awaan.ae/media/%s' % video_id, 'AWAANVideo')
|
||||||
elif season_id and int(season_id) > 0:
|
elif season_id and int(season_id) > 0:
|
||||||
return self.url_result(smuggle_url(
|
return self.url_result(smuggle_url(
|
||||||
'http://www.dcndigital.ae/program/season/%s' % season_id,
|
'http://awaan.ae/program/season/%s' % season_id,
|
||||||
{'show_id': show_id}), 'DCNSeason')
|
{'show_id': show_id}), 'AWAANSeason')
|
||||||
else:
|
else:
|
||||||
return self.url_result(
|
return self.url_result(
|
||||||
'http://www.dcndigital.ae/program/%s' % show_id, 'DCNSeason')
|
'http://awaan.ae/program/%s' % show_id, 'AWAANSeason')
|
||||||
|
|
||||||
|
|
||||||
class DCNBaseIE(InfoExtractor):
|
class AWAANBaseIE(InfoExtractor):
|
||||||
def _extract_video_info(self, video_data, video_id, is_live):
|
def _parse_video_data(self, video_data, video_id, is_live):
|
||||||
title = video_data.get('title_en') or video_data['title_ar']
|
title = video_data.get('title_en') or video_data['title_ar']
|
||||||
img = video_data.get('img')
|
img = video_data.get('img')
|
||||||
thumbnail = 'http://admin.mangomolo.com/analytics/%s' % img if img else None
|
|
||||||
duration = int_or_none(video_data.get('duration'))
|
|
||||||
description = video_data.get('description_en') or video_data.get('description_ar')
|
|
||||||
timestamp = parse_iso8601(video_data.get('create_time'), ' ')
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': self._live_title(title) if is_live else title,
|
'title': self._live_title(title) if is_live else title,
|
||||||
'description': description,
|
'description': video_data.get('description_en') or video_data.get('description_ar'),
|
||||||
'thumbnail': thumbnail,
|
'thumbnail': 'http://admin.mangomolo.com/analytics/%s' % img if img else None,
|
||||||
'duration': duration,
|
'duration': int_or_none(video_data.get('duration')),
|
||||||
'timestamp': timestamp,
|
'timestamp': parse_iso8601(video_data.get('create_time'), ' '),
|
||||||
'is_live': is_live,
|
'is_live': is_live,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,11 +57,9 @@ class DCNBaseIE(InfoExtractor):
|
|||||||
r'file\s*:\s*"https?(://[^"]+)/playlist.m3u8',
|
r'file\s*:\s*"https?(://[^"]+)/playlist.m3u8',
|
||||||
r'<a[^>]+href="rtsp(://[^"]+)"'
|
r'<a[^>]+href="rtsp(://[^"]+)"'
|
||||||
], webpage, 'format url')
|
], webpage, 'format url')
|
||||||
# TODO: Current DASH formats are broken - $Time$ pattern in
|
formats.extend(self._extract_mpd_formats(
|
||||||
# <SegmentTemplate> not implemented yet
|
format_url_base + '/manifest.mpd',
|
||||||
# formats.extend(self._extract_mpd_formats(
|
video_id, mpd_id='dash', fatal=False))
|
||||||
# format_url_base + '/manifest.mpd',
|
|
||||||
# video_id, mpd_id='dash', fatal=False))
|
|
||||||
formats.extend(self._extract_m3u8_formats(
|
formats.extend(self._extract_m3u8_formats(
|
||||||
format_url_base + '/playlist.m3u8', video_id, 'mp4',
|
format_url_base + '/playlist.m3u8', video_id, 'mp4',
|
||||||
m3u8_entry_protocol, m3u8_id='hls', fatal=False))
|
m3u8_entry_protocol, m3u8_id='hls', fatal=False))
|
||||||
@@ -77,11 +70,12 @@ class DCNBaseIE(InfoExtractor):
|
|||||||
return formats
|
return formats
|
||||||
|
|
||||||
|
|
||||||
class DCNVideoIE(DCNBaseIE):
|
class AWAANVideoIE(AWAANBaseIE):
|
||||||
IE_NAME = 'dcn:video'
|
IE_NAME = 'awaan:video'
|
||||||
_VALID_URL = r'https?://(?:www\.)?(?:awaan|dcndigital)\.ae/(?:#/)?(?:video(?:/[^/]+)?|media|catchup/[^/]+/[^/]+)/(?P<id>\d+)'
|
_VALID_URL = r'https?://(?:www\.)?(?:awaan|dcndigital)\.ae/(?:#/)?(?:video(?:/[^/]+)?|media|catchup/[^/]+/[^/]+)/(?P<id>\d+)'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.dcndigital.ae/#/video/%D8%B1%D8%AD%D9%84%D8%A9-%D8%A7%D9%84%D8%B9%D9%85%D8%B1-%D8%A7%D9%84%D8%AD%D9%84%D9%82%D8%A9-1/17375',
|
'url': 'http://www.dcndigital.ae/#/video/%D8%B1%D8%AD%D9%84%D8%A9-%D8%A7%D9%84%D8%B9%D9%85%D8%B1-%D8%A7%D9%84%D8%AD%D9%84%D9%82%D8%A9-1/17375',
|
||||||
|
'md5': '5f61c33bfc7794315c671a62d43116aa',
|
||||||
'info_dict':
|
'info_dict':
|
||||||
{
|
{
|
||||||
'id': '17375',
|
'id': '17375',
|
||||||
@@ -92,10 +86,6 @@ class DCNVideoIE(DCNBaseIE):
|
|||||||
'timestamp': 1227504126,
|
'timestamp': 1227504126,
|
||||||
'upload_date': '20081124',
|
'upload_date': '20081124',
|
||||||
},
|
},
|
||||||
'params': {
|
|
||||||
# m3u8 download
|
|
||||||
'skip_download': True,
|
|
||||||
},
|
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://awaan.ae/video/26723981/%D8%AF%D8%A7%D8%B1-%D8%A7%D9%84%D8%B3%D9%84%D8%A7%D9%85:-%D8%AE%D9%8A%D8%B1-%D8%AF%D9%88%D8%B1-%D8%A7%D9%84%D8%A3%D9%86%D8%B5%D8%A7%D8%B1',
|
'url': 'http://awaan.ae/video/26723981/%D8%AF%D8%A7%D8%B1-%D8%A7%D9%84%D8%B3%D9%84%D8%A7%D9%85:-%D8%AE%D9%8A%D8%B1-%D8%AF%D9%88%D8%B1-%D8%A7%D9%84%D8%A3%D9%86%D8%B5%D8%A7%D8%B1',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
@@ -104,11 +94,10 @@ class DCNVideoIE(DCNBaseIE):
|
|||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
request = sanitized_Request(
|
video_data = self._download_json(
|
||||||
'http://admin.mangomolo.com/analytics/index.php/plus/video?id=%s' % video_id,
|
'http://admin.mangomolo.com/analytics/index.php/plus/video?id=%s' % video_id,
|
||||||
headers={'Origin': 'http://www.dcndigital.ae'})
|
video_id, headers={'Origin': 'http://awaan.ae'})
|
||||||
video_data = self._download_json(request, video_id)
|
info = self._parse_video_data(video_data, video_id, False)
|
||||||
info = self._extract_video_info(video_data, video_id, False)
|
|
||||||
|
|
||||||
webpage = self._download_webpage(
|
webpage = self._download_webpage(
|
||||||
'http://admin.mangomolo.com/analytics/index.php/customers/embed/video?' +
|
'http://admin.mangomolo.com/analytics/index.php/customers/embed/video?' +
|
||||||
@@ -123,19 +112,31 @@ class DCNVideoIE(DCNBaseIE):
|
|||||||
return info
|
return info
|
||||||
|
|
||||||
|
|
||||||
class DCNLiveIE(DCNBaseIE):
|
class AWAANLiveIE(AWAANBaseIE):
|
||||||
IE_NAME = 'dcn:live'
|
IE_NAME = 'awaan:live'
|
||||||
_VALID_URL = r'https?://(?:www\.)?(?:awaan|dcndigital)\.ae/(?:#/)?live/(?P<id>\d+)'
|
_VALID_URL = r'https?://(?:www\.)?(?:awaan|dcndigital)\.ae/(?:#/)?live/(?P<id>\d+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://awaan.ae/live/6/dubai-tv',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '6',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 're:Dubai Al Oula [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$',
|
||||||
|
'upload_date': '20150107',
|
||||||
|
'timestamp': 1420588800,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# m3u8 download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
channel_id = self._match_id(url)
|
channel_id = self._match_id(url)
|
||||||
|
|
||||||
request = sanitized_Request(
|
channel_data = self._download_json(
|
||||||
'http://admin.mangomolo.com/analytics/index.php/plus/getchanneldetails?channel_id=%s' % channel_id,
|
'http://admin.mangomolo.com/analytics/index.php/plus/getchanneldetails?channel_id=%s' % channel_id,
|
||||||
headers={'Origin': 'http://www.dcndigital.ae'})
|
channel_id, headers={'Origin': 'http://awaan.ae'})
|
||||||
|
info = self._parse_video_data(channel_data, channel_id, True)
|
||||||
channel_data = self._download_json(request, channel_id)
|
|
||||||
info = self._extract_video_info(channel_data, channel_id, True)
|
|
||||||
|
|
||||||
webpage = self._download_webpage(
|
webpage = self._download_webpage(
|
||||||
'http://admin.mangomolo.com/analytics/index.php/customers/embed/index?' +
|
'http://admin.mangomolo.com/analytics/index.php/customers/embed/index?' +
|
||||||
@@ -150,8 +151,8 @@ class DCNLiveIE(DCNBaseIE):
|
|||||||
return info
|
return info
|
||||||
|
|
||||||
|
|
||||||
class DCNSeasonIE(InfoExtractor):
|
class AWAANSeasonIE(InfoExtractor):
|
||||||
IE_NAME = 'dcn:season'
|
IE_NAME = 'awaan:season'
|
||||||
_VALID_URL = r'https?://(?:www\.)?(?:awaan|dcndigital)\.ae/(?:#/)?program/(?:(?P<show_id>\d+)|season/(?P<season_id>\d+))'
|
_VALID_URL = r'https?://(?:www\.)?(?:awaan|dcndigital)\.ae/(?:#/)?program/(?:(?P<show_id>\d+)|season/(?P<season_id>\d+))'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://dcndigital.ae/#/program/205024/%D9%85%D8%AD%D8%A7%D8%B6%D8%B1%D8%A7%D8%AA-%D8%A7%D9%84%D8%B4%D9%8A%D8%AE-%D8%A7%D9%84%D8%B4%D8%B9%D8%B1%D8%A7%D9%88%D9%8A',
|
'url': 'http://dcndigital.ae/#/program/205024/%D9%85%D8%AD%D8%A7%D8%B6%D8%B1%D8%A7%D8%AA-%D8%A7%D9%84%D8%B4%D9%8A%D8%AE-%D8%A7%D9%84%D8%B4%D8%B9%D8%B1%D8%A7%D9%88%D9%8A',
|
||||||
@@ -172,21 +173,17 @@ class DCNSeasonIE(InfoExtractor):
|
|||||||
data['season'] = season_id
|
data['season'] = season_id
|
||||||
show_id = smuggled_data.get('show_id')
|
show_id = smuggled_data.get('show_id')
|
||||||
if show_id is None:
|
if show_id is None:
|
||||||
request = sanitized_Request(
|
season = self._download_json(
|
||||||
'http://admin.mangomolo.com/analytics/index.php/plus/season_info?id=%s' % season_id,
|
'http://admin.mangomolo.com/analytics/index.php/plus/season_info?id=%s' % season_id,
|
||||||
headers={'Origin': 'http://www.dcndigital.ae'})
|
season_id, headers={'Origin': 'http://awaan.ae'})
|
||||||
season = self._download_json(request, season_id)
|
|
||||||
show_id = season['id']
|
show_id = season['id']
|
||||||
data['show_id'] = show_id
|
data['show_id'] = show_id
|
||||||
request = sanitized_Request(
|
show = self._download_json(
|
||||||
'http://admin.mangomolo.com/analytics/index.php/plus/show',
|
'http://admin.mangomolo.com/analytics/index.php/plus/show',
|
||||||
urlencode_postdata(data),
|
show_id, data=urlencode_postdata(data), headers={
|
||||||
{
|
'Origin': 'http://awaan.ae',
|
||||||
'Origin': 'http://www.dcndigital.ae',
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded'
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
})
|
})
|
||||||
|
|
||||||
show = self._download_json(request, show_id)
|
|
||||||
if not season_id:
|
if not season_id:
|
||||||
season_id = show['default_season']
|
season_id = show['default_season']
|
||||||
for season in show['seasons']:
|
for season in show['seasons']:
|
||||||
@@ -197,6 +194,6 @@ class DCNSeasonIE(InfoExtractor):
|
|||||||
for video in show['videos']:
|
for video in show['videos']:
|
||||||
video_id = compat_str(video['id'])
|
video_id = compat_str(video['id'])
|
||||||
entries.append(self.url_result(
|
entries.append(self.url_result(
|
||||||
'http://www.dcndigital.ae/media/%s' % video_id, 'DCNVideo', video_id))
|
'http://awaan.ae/media/%s' % video_id, 'AWAANVideo', video_id))
|
||||||
|
|
||||||
return self.playlist_result(entries, season_id, title)
|
return self.playlist_result(entries, season_id, title)
|
||||||
@@ -2,19 +2,23 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import itertools
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
|
dict_get,
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
float_or_none,
|
float_or_none,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
parse_duration,
|
parse_duration,
|
||||||
parse_iso8601,
|
parse_iso8601,
|
||||||
|
try_get,
|
||||||
unescapeHTML,
|
unescapeHTML,
|
||||||
)
|
)
|
||||||
from ..compat import (
|
from ..compat import (
|
||||||
compat_etree_fromstring,
|
compat_etree_fromstring,
|
||||||
compat_HTTPError,
|
compat_HTTPError,
|
||||||
|
compat_urlparse,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -229,51 +233,6 @@ class BBCCoUkIE(InfoExtractor):
|
|||||||
asx = self._download_xml(connection.get('href'), programme_id, 'Downloading ASX playlist')
|
asx = self._download_xml(connection.get('href'), programme_id, 'Downloading ASX playlist')
|
||||||
return [ref.get('href') for ref in asx.findall('./Entry/ref')]
|
return [ref.get('href') for ref in asx.findall('./Entry/ref')]
|
||||||
|
|
||||||
def _extract_connection(self, connection, programme_id):
|
|
||||||
formats = []
|
|
||||||
kind = connection.get('kind')
|
|
||||||
protocol = connection.get('protocol')
|
|
||||||
supplier = connection.get('supplier')
|
|
||||||
if protocol == 'http':
|
|
||||||
href = connection.get('href')
|
|
||||||
transfer_format = connection.get('transferFormat')
|
|
||||||
# ASX playlist
|
|
||||||
if supplier == 'asx':
|
|
||||||
for i, ref in enumerate(self._extract_asx_playlist(connection, programme_id)):
|
|
||||||
formats.append({
|
|
||||||
'url': ref,
|
|
||||||
'format_id': 'ref%s_%s' % (i, supplier),
|
|
||||||
})
|
|
||||||
# Skip DASH until supported
|
|
||||||
elif transfer_format == 'dash':
|
|
||||||
pass
|
|
||||||
elif transfer_format == 'hls':
|
|
||||||
formats.extend(self._extract_m3u8_formats(
|
|
||||||
href, programme_id, ext='mp4', entry_protocol='m3u8_native',
|
|
||||||
m3u8_id=supplier, fatal=False))
|
|
||||||
# Direct link
|
|
||||||
else:
|
|
||||||
formats.append({
|
|
||||||
'url': href,
|
|
||||||
'format_id': supplier or kind or protocol,
|
|
||||||
})
|
|
||||||
elif protocol == 'rtmp':
|
|
||||||
application = connection.get('application', 'ondemand')
|
|
||||||
auth_string = connection.get('authString')
|
|
||||||
identifier = connection.get('identifier')
|
|
||||||
server = connection.get('server')
|
|
||||||
formats.append({
|
|
||||||
'url': '%s://%s/%s?%s' % (protocol, server, application, auth_string),
|
|
||||||
'play_path': identifier,
|
|
||||||
'app': '%s?%s' % (application, auth_string),
|
|
||||||
'page_url': 'http://www.bbc.co.uk',
|
|
||||||
'player_url': 'http://www.bbc.co.uk/emp/releases/iplayer/revisions/617463_618125_4/617463_618125_4_emp.swf',
|
|
||||||
'rtmp_live': False,
|
|
||||||
'ext': 'flv',
|
|
||||||
'format_id': supplier,
|
|
||||||
})
|
|
||||||
return formats
|
|
||||||
|
|
||||||
def _extract_items(self, playlist):
|
def _extract_items(self, playlist):
|
||||||
return playlist.findall('./{%s}item' % self._EMP_PLAYLIST_NS)
|
return playlist.findall('./{%s}item' % self._EMP_PLAYLIST_NS)
|
||||||
|
|
||||||
@@ -294,46 +253,6 @@ class BBCCoUkIE(InfoExtractor):
|
|||||||
def _extract_connections(self, media):
|
def _extract_connections(self, media):
|
||||||
return self._findall_ns(media, './{%s}connection')
|
return self._findall_ns(media, './{%s}connection')
|
||||||
|
|
||||||
def _extract_video(self, media, programme_id):
|
|
||||||
formats = []
|
|
||||||
vbr = int_or_none(media.get('bitrate'))
|
|
||||||
vcodec = media.get('encoding')
|
|
||||||
service = media.get('service')
|
|
||||||
width = int_or_none(media.get('width'))
|
|
||||||
height = int_or_none(media.get('height'))
|
|
||||||
file_size = int_or_none(media.get('media_file_size'))
|
|
||||||
for connection in self._extract_connections(media):
|
|
||||||
conn_formats = self._extract_connection(connection, programme_id)
|
|
||||||
for format in conn_formats:
|
|
||||||
format.update({
|
|
||||||
'width': width,
|
|
||||||
'height': height,
|
|
||||||
'vbr': vbr,
|
|
||||||
'vcodec': vcodec,
|
|
||||||
'filesize': file_size,
|
|
||||||
})
|
|
||||||
if service:
|
|
||||||
format['format_id'] = '%s_%s' % (service, format['format_id'])
|
|
||||||
formats.extend(conn_formats)
|
|
||||||
return formats
|
|
||||||
|
|
||||||
def _extract_audio(self, media, programme_id):
|
|
||||||
formats = []
|
|
||||||
abr = int_or_none(media.get('bitrate'))
|
|
||||||
acodec = media.get('encoding')
|
|
||||||
service = media.get('service')
|
|
||||||
for connection in self._extract_connections(media):
|
|
||||||
conn_formats = self._extract_connection(connection, programme_id)
|
|
||||||
for format in conn_formats:
|
|
||||||
format.update({
|
|
||||||
'format_id': '%s_%s' % (service, format['format_id']),
|
|
||||||
'abr': abr,
|
|
||||||
'acodec': acodec,
|
|
||||||
'vcodec': 'none',
|
|
||||||
})
|
|
||||||
formats.extend(conn_formats)
|
|
||||||
return formats
|
|
||||||
|
|
||||||
def _get_subtitles(self, media, programme_id):
|
def _get_subtitles(self, media, programme_id):
|
||||||
subtitles = {}
|
subtitles = {}
|
||||||
for connection in self._extract_connections(media):
|
for connection in self._extract_connections(media):
|
||||||
@@ -379,13 +298,87 @@ class BBCCoUkIE(InfoExtractor):
|
|||||||
def _process_media_selector(self, media_selection, programme_id):
|
def _process_media_selector(self, media_selection, programme_id):
|
||||||
formats = []
|
formats = []
|
||||||
subtitles = None
|
subtitles = None
|
||||||
|
urls = []
|
||||||
|
|
||||||
for media in self._extract_medias(media_selection):
|
for media in self._extract_medias(media_selection):
|
||||||
kind = media.get('kind')
|
kind = media.get('kind')
|
||||||
if kind == 'audio':
|
if kind in ('video', 'audio'):
|
||||||
formats.extend(self._extract_audio(media, programme_id))
|
bitrate = int_or_none(media.get('bitrate'))
|
||||||
elif kind == 'video':
|
encoding = media.get('encoding')
|
||||||
formats.extend(self._extract_video(media, programme_id))
|
service = media.get('service')
|
||||||
|
width = int_or_none(media.get('width'))
|
||||||
|
height = int_or_none(media.get('height'))
|
||||||
|
file_size = int_or_none(media.get('media_file_size'))
|
||||||
|
for connection in self._extract_connections(media):
|
||||||
|
href = connection.get('href')
|
||||||
|
if href in urls:
|
||||||
|
continue
|
||||||
|
if href:
|
||||||
|
urls.append(href)
|
||||||
|
conn_kind = connection.get('kind')
|
||||||
|
protocol = connection.get('protocol')
|
||||||
|
supplier = connection.get('supplier')
|
||||||
|
transfer_format = connection.get('transferFormat')
|
||||||
|
format_id = supplier or conn_kind or protocol
|
||||||
|
if service:
|
||||||
|
format_id = '%s_%s' % (service, format_id)
|
||||||
|
# ASX playlist
|
||||||
|
if supplier == 'asx':
|
||||||
|
for i, ref in enumerate(self._extract_asx_playlist(connection, programme_id)):
|
||||||
|
formats.append({
|
||||||
|
'url': ref,
|
||||||
|
'format_id': 'ref%s_%s' % (i, format_id),
|
||||||
|
})
|
||||||
|
elif transfer_format == 'dash':
|
||||||
|
formats.extend(self._extract_mpd_formats(
|
||||||
|
href, programme_id, mpd_id=format_id, fatal=False))
|
||||||
|
elif transfer_format == 'hls':
|
||||||
|
formats.extend(self._extract_m3u8_formats(
|
||||||
|
href, programme_id, ext='mp4', entry_protocol='m3u8_native',
|
||||||
|
m3u8_id=format_id, fatal=False))
|
||||||
|
elif transfer_format == 'hds':
|
||||||
|
formats.extend(self._extract_f4m_formats(
|
||||||
|
href, programme_id, f4m_id=format_id, fatal=False))
|
||||||
|
else:
|
||||||
|
if not service and not supplier and bitrate:
|
||||||
|
format_id += '-%d' % bitrate
|
||||||
|
fmt = {
|
||||||
|
'format_id': format_id,
|
||||||
|
'filesize': file_size,
|
||||||
|
}
|
||||||
|
if kind == 'video':
|
||||||
|
fmt.update({
|
||||||
|
'width': width,
|
||||||
|
'height': height,
|
||||||
|
'vbr': bitrate,
|
||||||
|
'vcodec': encoding,
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
fmt.update({
|
||||||
|
'abr': bitrate,
|
||||||
|
'acodec': encoding,
|
||||||
|
'vcodec': 'none',
|
||||||
|
})
|
||||||
|
if protocol == 'http':
|
||||||
|
# Direct link
|
||||||
|
fmt.update({
|
||||||
|
'url': href,
|
||||||
|
})
|
||||||
|
elif protocol == 'rtmp':
|
||||||
|
application = connection.get('application', 'ondemand')
|
||||||
|
auth_string = connection.get('authString')
|
||||||
|
identifier = connection.get('identifier')
|
||||||
|
server = connection.get('server')
|
||||||
|
fmt.update({
|
||||||
|
'url': '%s://%s/%s?%s' % (protocol, server, application, auth_string),
|
||||||
|
'play_path': identifier,
|
||||||
|
'app': '%s?%s' % (application, auth_string),
|
||||||
|
'page_url': 'http://www.bbc.co.uk',
|
||||||
|
'player_url': 'http://www.bbc.co.uk/emp/releases/iplayer/revisions/617463_618125_4/617463_618125_4_emp.swf',
|
||||||
|
'rtmp_live': False,
|
||||||
|
'ext': 'flv',
|
||||||
|
})
|
||||||
|
formats.append(fmt)
|
||||||
elif kind == 'captions':
|
elif kind == 'captions':
|
||||||
subtitles = self.extract_subtitles(media, programme_id)
|
subtitles = self.extract_subtitles(media, programme_id)
|
||||||
return formats, subtitles
|
return formats, subtitles
|
||||||
@@ -590,6 +583,7 @@ class BBCIE(BBCCoUkIE):
|
|||||||
'id': '150615_telabyad_kentin_cogu',
|
'id': '150615_telabyad_kentin_cogu',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': "YPG: Tel Abyad'ın tamamı kontrolümüzde",
|
'title': "YPG: Tel Abyad'ın tamamı kontrolümüzde",
|
||||||
|
'description': 'md5:33a4805a855c9baf7115fcbde57e7025',
|
||||||
'timestamp': 1434397334,
|
'timestamp': 1434397334,
|
||||||
'upload_date': '20150615',
|
'upload_date': '20150615',
|
||||||
},
|
},
|
||||||
@@ -603,6 +597,7 @@ class BBCIE(BBCCoUkIE):
|
|||||||
'id': '150619_video_honduras_militares_hospitales_corrupcion_aw',
|
'id': '150619_video_honduras_militares_hospitales_corrupcion_aw',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Honduras militariza sus hospitales por nuevo escándalo de corrupción',
|
'title': 'Honduras militariza sus hospitales por nuevo escándalo de corrupción',
|
||||||
|
'description': 'md5:1525f17448c4ee262b64b8f0c9ce66c8',
|
||||||
'timestamp': 1434713142,
|
'timestamp': 1434713142,
|
||||||
'upload_date': '20150619',
|
'upload_date': '20150619',
|
||||||
},
|
},
|
||||||
@@ -652,6 +647,23 @@ class BBCIE(BBCCoUkIE):
|
|||||||
# rtmp download
|
# rtmp download
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
# single video embedded with Morph
|
||||||
|
'url': 'http://www.bbc.co.uk/sport/live/olympics/36895975',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'p041vhd0',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': "Nigeria v Japan - Men's First Round",
|
||||||
|
'description': 'Live coverage of the first round from Group B at the Amazonia Arena.',
|
||||||
|
'duration': 7980,
|
||||||
|
'uploader': 'BBC Sport',
|
||||||
|
'uploader_id': 'bbc_sport',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# m3u8 download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
'skip': 'Georestricted to UK',
|
||||||
}, {
|
}, {
|
||||||
# single video with playlist.sxml URL in playlist param
|
# single video with playlist.sxml URL in playlist param
|
||||||
'url': 'http://www.bbc.com/sport/0/football/33653409',
|
'url': 'http://www.bbc.com/sport/0/football/33653409',
|
||||||
@@ -749,7 +761,7 @@ class BBCIE(BBCCoUkIE):
|
|||||||
|
|
||||||
webpage = self._download_webpage(url, playlist_id)
|
webpage = self._download_webpage(url, playlist_id)
|
||||||
|
|
||||||
json_ld_info = self._search_json_ld(webpage, playlist_id, default=None)
|
json_ld_info = self._search_json_ld(webpage, playlist_id, default={})
|
||||||
timestamp = json_ld_info.get('timestamp')
|
timestamp = json_ld_info.get('timestamp')
|
||||||
|
|
||||||
playlist_title = json_ld_info.get('title')
|
playlist_title = json_ld_info.get('title')
|
||||||
@@ -818,8 +830,29 @@ class BBCIE(BBCCoUkIE):
|
|||||||
# http://www.bbc.com/turkce/multimedya/2015/10/151010_vid_ankara_patlama_ani)
|
# http://www.bbc.com/turkce/multimedya/2015/10/151010_vid_ankara_patlama_ani)
|
||||||
playlist = data_playable.get('otherSettings', {}).get('playlist', {})
|
playlist = data_playable.get('otherSettings', {}).get('playlist', {})
|
||||||
if playlist:
|
if playlist:
|
||||||
entries.append(self._extract_from_playlist_sxml(
|
entry = None
|
||||||
playlist.get('progressiveDownloadUrl'), playlist_id, timestamp))
|
for key in ('streaming', 'progressiveDownload'):
|
||||||
|
playlist_url = playlist.get('%sUrl' % key)
|
||||||
|
if not playlist_url:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
info = self._extract_from_playlist_sxml(
|
||||||
|
playlist_url, playlist_id, timestamp)
|
||||||
|
if not entry:
|
||||||
|
entry = info
|
||||||
|
else:
|
||||||
|
entry['title'] = info['title']
|
||||||
|
entry['formats'].extend(info['formats'])
|
||||||
|
except Exception as e:
|
||||||
|
# Some playlist URL may fail with 500, at the same time
|
||||||
|
# the other one may work fine (e.g.
|
||||||
|
# http://www.bbc.com/turkce/haberler/2015/06/150615_telabyad_kentin_cogu)
|
||||||
|
if isinstance(e.cause, compat_HTTPError) and e.cause.code == 500:
|
||||||
|
continue
|
||||||
|
raise
|
||||||
|
if entry:
|
||||||
|
self._sort_formats(entry['formats'])
|
||||||
|
entries.append(entry)
|
||||||
|
|
||||||
if entries:
|
if entries:
|
||||||
return self.playlist_result(entries, playlist_id, playlist_title, playlist_description)
|
return self.playlist_result(entries, playlist_id, playlist_title, playlist_description)
|
||||||
@@ -852,6 +885,50 @@ class BBCIE(BBCCoUkIE):
|
|||||||
'subtitles': subtitles,
|
'subtitles': subtitles,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Morph based embed (e.g. http://www.bbc.co.uk/sport/live/olympics/36895975)
|
||||||
|
# There are several setPayload calls may be present but the video
|
||||||
|
# seems to be always related to the first one
|
||||||
|
morph_payload = self._parse_json(
|
||||||
|
self._search_regex(
|
||||||
|
r'Morph\.setPayload\([^,]+,\s*({.+?})\);',
|
||||||
|
webpage, 'morph payload', default='{}'),
|
||||||
|
playlist_id, fatal=False)
|
||||||
|
if morph_payload:
|
||||||
|
components = try_get(morph_payload, lambda x: x['body']['components'], list) or []
|
||||||
|
for component in components:
|
||||||
|
if not isinstance(component, dict):
|
||||||
|
continue
|
||||||
|
lead_media = try_get(component, lambda x: x['props']['leadMedia'], dict)
|
||||||
|
if not lead_media:
|
||||||
|
continue
|
||||||
|
identifiers = lead_media.get('identifiers')
|
||||||
|
if not identifiers or not isinstance(identifiers, dict):
|
||||||
|
continue
|
||||||
|
programme_id = identifiers.get('vpid') or identifiers.get('playablePid')
|
||||||
|
if not programme_id:
|
||||||
|
continue
|
||||||
|
title = lead_media.get('title') or self._og_search_title(webpage)
|
||||||
|
formats, subtitles = self._download_media_selector(programme_id)
|
||||||
|
self._sort_formats(formats)
|
||||||
|
description = lead_media.get('summary')
|
||||||
|
uploader = lead_media.get('masterBrand')
|
||||||
|
uploader_id = lead_media.get('mid')
|
||||||
|
duration = None
|
||||||
|
duration_d = lead_media.get('duration')
|
||||||
|
if isinstance(duration_d, dict):
|
||||||
|
duration = parse_duration(dict_get(
|
||||||
|
duration_d, ('rawDuration', 'formattedDuration', 'spokenDuration')))
|
||||||
|
return {
|
||||||
|
'id': programme_id,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'duration': duration,
|
||||||
|
'uploader': uploader,
|
||||||
|
'uploader_id': uploader_id,
|
||||||
|
'formats': formats,
|
||||||
|
'subtitles': subtitles,
|
||||||
|
}
|
||||||
|
|
||||||
def extract_all(pattern):
|
def extract_all(pattern):
|
||||||
return list(filter(None, map(
|
return list(filter(None, map(
|
||||||
lambda s: self._parse_json(s, playlist_id, fatal=False),
|
lambda s: self._parse_json(s, playlist_id, fatal=False),
|
||||||
@@ -869,7 +946,7 @@ class BBCIE(BBCCoUkIE):
|
|||||||
r'setPlaylist\("(%s)"\)' % EMBED_URL, webpage))
|
r'setPlaylist\("(%s)"\)' % EMBED_URL, webpage))
|
||||||
if entries:
|
if entries:
|
||||||
return self.playlist_result(
|
return self.playlist_result(
|
||||||
[self.url_result(entry, 'BBCCoUk') for entry in entries],
|
[self.url_result(entry_, 'BBCCoUk') for entry_ in entries],
|
||||||
playlist_id, playlist_title, playlist_description)
|
playlist_id, playlist_title, playlist_description)
|
||||||
|
|
||||||
# Multiple video article (e.g. http://www.bbc.com/news/world-europe-32668511)
|
# Multiple video article (e.g. http://www.bbc.com/news/world-europe-32668511)
|
||||||
@@ -981,27 +1058,43 @@ class BBCCoUkArticleIE(InfoExtractor):
|
|||||||
|
|
||||||
|
|
||||||
class BBCCoUkPlaylistBaseIE(InfoExtractor):
|
class BBCCoUkPlaylistBaseIE(InfoExtractor):
|
||||||
|
def _entries(self, webpage, url, playlist_id):
|
||||||
|
single_page = 'page' in compat_urlparse.parse_qs(
|
||||||
|
compat_urlparse.urlparse(url).query)
|
||||||
|
for page_num in itertools.count(2):
|
||||||
|
for video_id in re.findall(
|
||||||
|
self._VIDEO_ID_TEMPLATE % BBCCoUkIE._ID_REGEX, webpage):
|
||||||
|
yield self.url_result(
|
||||||
|
self._URL_TEMPLATE % video_id, BBCCoUkIE.ie_key())
|
||||||
|
if single_page:
|
||||||
|
return
|
||||||
|
next_page = self._search_regex(
|
||||||
|
r'<li[^>]+class=(["\'])pagination_+next\1[^>]*><a[^>]+href=(["\'])(?P<url>(?:(?!\2).)+)\2',
|
||||||
|
webpage, 'next page url', default=None, group='url')
|
||||||
|
if not next_page:
|
||||||
|
break
|
||||||
|
webpage = self._download_webpage(
|
||||||
|
compat_urlparse.urljoin(url, next_page), playlist_id,
|
||||||
|
'Downloading page %d' % page_num, page_num)
|
||||||
|
|
||||||
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)
|
webpage = self._download_webpage(url, playlist_id)
|
||||||
|
|
||||||
entries = [
|
|
||||||
self.url_result(self._URL_TEMPLATE % video_id, BBCCoUkIE.ie_key())
|
|
||||||
for video_id in re.findall(
|
|
||||||
self._VIDEO_ID_TEMPLATE % BBCCoUkIE._ID_REGEX, webpage)]
|
|
||||||
|
|
||||||
title, description = self._extract_title_and_description(webpage)
|
title, description = self._extract_title_and_description(webpage)
|
||||||
|
|
||||||
return self.playlist_result(entries, playlist_id, title, description)
|
return self.playlist_result(
|
||||||
|
self._entries(webpage, url, playlist_id),
|
||||||
|
playlist_id, title, description)
|
||||||
|
|
||||||
|
|
||||||
class BBCCoUkIPlayerPlaylistIE(BBCCoUkPlaylistBaseIE):
|
class BBCCoUkIPlayerPlaylistIE(BBCCoUkPlaylistBaseIE):
|
||||||
IE_NAME = 'bbc.co.uk:iplayer:playlist'
|
IE_NAME = 'bbc.co.uk:iplayer:playlist'
|
||||||
_VALID_URL = r'https?://(?:www\.)?bbc\.co\.uk/iplayer/episodes/(?P<id>%s)' % BBCCoUkIE._ID_REGEX
|
_VALID_URL = r'https?://(?:www\.)?bbc\.co\.uk/iplayer/(?:episodes|group)/(?P<id>%s)' % BBCCoUkIE._ID_REGEX
|
||||||
_URL_TEMPLATE = 'http://www.bbc.co.uk/iplayer/episode/%s'
|
_URL_TEMPLATE = 'http://www.bbc.co.uk/iplayer/episode/%s'
|
||||||
_VIDEO_ID_TEMPLATE = r'data-ip-id=["\'](%s)'
|
_VIDEO_ID_TEMPLATE = r'data-ip-id=["\'](%s)'
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
'url': 'http://www.bbc.co.uk/iplayer/episodes/b05rcz9v',
|
'url': 'http://www.bbc.co.uk/iplayer/episodes/b05rcz9v',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'b05rcz9v',
|
'id': 'b05rcz9v',
|
||||||
@@ -1009,7 +1102,17 @@ class BBCCoUkIPlayerPlaylistIE(BBCCoUkPlaylistBaseIE):
|
|||||||
'description': 'French thriller serial about a missing teenager.',
|
'description': 'French thriller serial about a missing teenager.',
|
||||||
},
|
},
|
||||||
'playlist_mincount': 6,
|
'playlist_mincount': 6,
|
||||||
}
|
'skip': 'This programme is not currently available on BBC iPlayer',
|
||||||
|
}, {
|
||||||
|
# Available for over a year unlike 30 days for most other programmes
|
||||||
|
'url': 'http://www.bbc.co.uk/iplayer/group/p02tcc32',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'p02tcc32',
|
||||||
|
'title': 'Bohemian Icons',
|
||||||
|
'description': 'md5:683e901041b2fe9ba596f2ab04c4dbe7',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 10,
|
||||||
|
}]
|
||||||
|
|
||||||
def _extract_title_and_description(self, webpage):
|
def _extract_title_and_description(self, webpage):
|
||||||
title = self._search_regex(r'<h1>([^<]+)</h1>', webpage, 'title', fatal=False)
|
title = self._search_regex(r'<h1>([^<]+)</h1>', webpage, 'title', fatal=False)
|
||||||
@@ -1032,6 +1135,24 @@ class BBCCoUkPlaylistIE(BBCCoUkPlaylistBaseIE):
|
|||||||
'description': 'French thriller serial about a missing teenager.',
|
'description': 'French thriller serial about a missing teenager.',
|
||||||
},
|
},
|
||||||
'playlist_mincount': 7,
|
'playlist_mincount': 7,
|
||||||
|
}, {
|
||||||
|
# multipage playlist, explicit page
|
||||||
|
'url': 'http://www.bbc.co.uk/programmes/b00mfl7n/clips?page=1',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'b00mfl7n',
|
||||||
|
'title': 'Frozen Planet - Clips - BBC One',
|
||||||
|
'description': 'md5:65dcbf591ae628dafe32aa6c4a4a0d8c',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 24,
|
||||||
|
}, {
|
||||||
|
# multipage playlist, all pages
|
||||||
|
'url': 'http://www.bbc.co.uk/programmes/b00mfl7n/clips',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'b00mfl7n',
|
||||||
|
'title': 'Frozen Planet - Clips - BBC One',
|
||||||
|
'description': 'md5:65dcbf591ae628dafe32aa6c4a4a0d8c',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 142,
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.bbc.co.uk/programmes/b05rcz9v/broadcasts/2016/06',
|
'url': 'http://www.bbc.co.uk/programmes/b05rcz9v/broadcasts/2016/06',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
|||||||
@@ -11,22 +11,13 @@ from ..compat import compat_urllib_parse_unquote
|
|||||||
class BigflixIE(InfoExtractor):
|
class BigflixIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?bigflix\.com/.+/(?P<id>[0-9]+)'
|
_VALID_URL = r'https?://(?:www\.)?bigflix\.com/.+/(?P<id>[0-9]+)'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.bigflix.com/Hindi-movies/Action-movies/Singham-Returns/16537',
|
|
||||||
'md5': 'ec76aa9b1129e2e5b301a474e54fab74',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '16537',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': 'Singham Returns',
|
|
||||||
'description': 'md5:3d2ba5815f14911d5cc6a501ae0cf65d',
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
# 2 formats
|
# 2 formats
|
||||||
'url': 'http://www.bigflix.com/Tamil-movies/Drama-movies/Madarasapatinam/16070',
|
'url': 'http://www.bigflix.com/Tamil-movies/Drama-movies/Madarasapatinam/16070',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '16070',
|
'id': '16070',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Madarasapatinam',
|
'title': 'Madarasapatinam',
|
||||||
'description': 'md5:63b9b8ed79189c6f0418c26d9a3452ca',
|
'description': 'md5:9f0470b26a4ba8e824c823b5d95c2f6b',
|
||||||
'formats': 'mincount:2',
|
'formats': 'mincount:2',
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
|
|||||||
@@ -25,13 +25,13 @@ class BiliBiliIE(InfoExtractor):
|
|||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.bilibili.tv/video/av1074402/',
|
'url': 'http://www.bilibili.tv/video/av1074402/',
|
||||||
'md5': '5f7d29e1a2872f3df0cf76b1f87d3788',
|
'md5': '9fa226fe2b8a9a4d5a69b4c6a183417e',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '1554319',
|
'id': '1554319',
|
||||||
'ext': 'flv',
|
'ext': 'mp4',
|
||||||
'title': '【金坷垃】金泡沫',
|
'title': '【金坷垃】金泡沫',
|
||||||
'description': 'md5:ce18c2a2d2193f0df2917d270f2e5923',
|
'description': 'md5:ce18c2a2d2193f0df2917d270f2e5923',
|
||||||
'duration': 308.067,
|
'duration': 308.315,
|
||||||
'timestamp': 1398012660,
|
'timestamp': 1398012660,
|
||||||
'upload_date': '20140420',
|
'upload_date': '20140420',
|
||||||
'thumbnail': 're:^https?://.+\.jpg',
|
'thumbnail': 're:^https?://.+\.jpg',
|
||||||
@@ -41,73 +41,33 @@ class BiliBiliIE(InfoExtractor):
|
|||||||
}, {
|
}, {
|
||||||
'url': 'http://www.bilibili.com/video/av1041170/',
|
'url': 'http://www.bilibili.com/video/av1041170/',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '1041170',
|
'id': '1507019',
|
||||||
|
'ext': 'mp4',
|
||||||
'title': '【BD1080P】刀语【诸神&异域】',
|
'title': '【BD1080P】刀语【诸神&异域】',
|
||||||
'description': '这是个神奇的故事~每个人不留弹幕不给走哦~切利哦!~',
|
'description': '这是个神奇的故事~每个人不留弹幕不给走哦~切利哦!~',
|
||||||
|
'timestamp': 1396530060,
|
||||||
|
'upload_date': '20140403',
|
||||||
|
'uploader': '枫叶逝去',
|
||||||
|
'uploader_id': '520116',
|
||||||
},
|
},
|
||||||
'playlist_count': 9,
|
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.bilibili.com/video/av4808130/',
|
'url': 'http://www.bilibili.com/video/av4808130/',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '4808130',
|
'id': '7802182',
|
||||||
|
'ext': 'mp4',
|
||||||
'title': '【长篇】哆啦A梦443【钉铛】',
|
'title': '【长篇】哆啦A梦443【钉铛】',
|
||||||
'description': '(2016.05.27)来组合客人的脸吧&amp;寻母六千里锭 抱歉,又轮到周日上班现在才到家 封面www.pixiv.net/member_illust.php?mode=medium&amp;illust_id=56912929',
|
'description': '(2016.05.27)来组合客人的脸吧&amp;寻母六千里锭 抱歉,又轮到周日上班现在才到家 封面www.pixiv.net/member_illust.php?mode=medium&amp;illust_id=56912929',
|
||||||
|
'timestamp': 1464564180,
|
||||||
|
'upload_date': '20160529',
|
||||||
|
'uploader': '喜欢拉面',
|
||||||
|
'uploader_id': '151066',
|
||||||
},
|
},
|
||||||
'playlist': [{
|
|
||||||
'md5': '55cdadedf3254caaa0d5d27cf20a8f9c',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '4808130_part1',
|
|
||||||
'ext': 'flv',
|
|
||||||
'title': '【长篇】哆啦A梦443【钉铛】',
|
|
||||||
'description': '(2016.05.27)来组合客人的脸吧&amp;寻母六千里锭 抱歉,又轮到周日上班现在才到家 封面www.pixiv.net/member_illust.php?mode=medium&amp;illust_id=56912929',
|
|
||||||
'timestamp': 1464564180,
|
|
||||||
'upload_date': '20160529',
|
|
||||||
'uploader': '喜欢拉面',
|
|
||||||
'uploader_id': '151066',
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
'md5': '926f9f67d0c482091872fbd8eca7ea3d',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '4808130_part2',
|
|
||||||
'ext': 'flv',
|
|
||||||
'title': '【长篇】哆啦A梦443【钉铛】',
|
|
||||||
'description': '(2016.05.27)来组合客人的脸吧&amp;寻母六千里锭 抱歉,又轮到周日上班现在才到家 封面www.pixiv.net/member_illust.php?mode=medium&amp;illust_id=56912929',
|
|
||||||
'timestamp': 1464564180,
|
|
||||||
'upload_date': '20160529',
|
|
||||||
'uploader': '喜欢拉面',
|
|
||||||
'uploader_id': '151066',
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
'md5': '4b7b225b968402d7c32348c646f1fd83',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '4808130_part3',
|
|
||||||
'ext': 'flv',
|
|
||||||
'title': '【长篇】哆啦A梦443【钉铛】',
|
|
||||||
'description': '(2016.05.27)来组合客人的脸吧&amp;寻母六千里锭 抱歉,又轮到周日上班现在才到家 封面www.pixiv.net/member_illust.php?mode=medium&amp;illust_id=56912929',
|
|
||||||
'timestamp': 1464564180,
|
|
||||||
'upload_date': '20160529',
|
|
||||||
'uploader': '喜欢拉面',
|
|
||||||
'uploader_id': '151066',
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
'md5': '7b795e214166501e9141139eea236e91',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '4808130_part4',
|
|
||||||
'ext': 'flv',
|
|
||||||
'title': '【长篇】哆啦A梦443【钉铛】',
|
|
||||||
'description': '(2016.05.27)来组合客人的脸吧&amp;寻母六千里锭 抱歉,又轮到周日上班现在才到家 封面www.pixiv.net/member_illust.php?mode=medium&amp;illust_id=56912929',
|
|
||||||
'timestamp': 1464564180,
|
|
||||||
'upload_date': '20160529',
|
|
||||||
'uploader': '喜欢拉面',
|
|
||||||
'uploader_id': '151066',
|
|
||||||
},
|
|
||||||
}],
|
|
||||||
}, {
|
}, {
|
||||||
# Missing upload time
|
# Missing upload time
|
||||||
'url': 'http://www.bilibili.com/video/av1867637/',
|
'url': 'http://www.bilibili.com/video/av1867637/',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '2880301',
|
'id': '2880301',
|
||||||
'ext': 'flv',
|
'ext': 'mp4',
|
||||||
'title': '【HDTV】【喜剧】岳父岳母真难当 (2014)【法国票房冠军】',
|
'title': '【HDTV】【喜剧】岳父岳母真难当 (2014)【法国票房冠军】',
|
||||||
'description': '一个信奉天主教的法国旧式传统资产阶级家庭中有四个女儿。三个女儿却分别找了阿拉伯、犹太、中国丈夫,老夫老妻唯独期盼剩下未嫁的小女儿能找一个信奉天主教的法国白人,结果没想到小女儿找了一位非裔黑人……【这次应该不会跳帧了】',
|
'description': '一个信奉天主教的法国旧式传统资产阶级家庭中有四个女儿。三个女儿却分别找了阿拉伯、犹太、中国丈夫,老夫老妻唯独期盼剩下未嫁的小女儿能找一个信奉天主教的法国白人,结果没想到小女儿找了一位非裔黑人……【这次应该不会跳帧了】',
|
||||||
'uploader': '黑夜为猫',
|
'uploader': '黑夜为猫',
|
||||||
|
|||||||
@@ -2,11 +2,15 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import remove_end
|
from ..utils import (
|
||||||
|
ExtractorError,
|
||||||
|
remove_end,
|
||||||
|
)
|
||||||
|
from .rudo import RudoIE
|
||||||
|
|
||||||
|
|
||||||
class BioBioChileTVIE(InfoExtractor):
|
class BioBioChileTVIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://tv\.biobiochile\.cl/notas/(?:[^/]+/)+(?P<id>[^/]+)\.shtml'
|
_VALID_URL = r'https?://(?:tv|www)\.biobiochile\.cl/(?:notas|noticias)/(?:[^/]+/)+(?P<id>[^/]+)\.shtml'
|
||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://tv.biobiochile.cl/notas/2015/10/21/sobre-camaras-y-camarillas-parlamentarias.shtml',
|
'url': 'http://tv.biobiochile.cl/notas/2015/10/21/sobre-camaras-y-camarillas-parlamentarias.shtml',
|
||||||
@@ -18,6 +22,7 @@ class BioBioChileTVIE(InfoExtractor):
|
|||||||
'thumbnail': 're:^https?://.*\.jpg$',
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
'uploader': 'Fernando Atria',
|
'uploader': 'Fernando Atria',
|
||||||
},
|
},
|
||||||
|
'skip': 'URL expired and redirected to http://www.biobiochile.cl/portada/bbtv/index.html',
|
||||||
}, {
|
}, {
|
||||||
# different uploader layout
|
# different uploader layout
|
||||||
'url': 'http://tv.biobiochile.cl/notas/2016/03/18/natalia-valdebenito-repasa-a-diputado-hasbun-paso-a-la-categoria-de-hablar-brutalidades.shtml',
|
'url': 'http://tv.biobiochile.cl/notas/2016/03/18/natalia-valdebenito-repasa-a-diputado-hasbun-paso-a-la-categoria-de-hablar-brutalidades.shtml',
|
||||||
@@ -32,6 +37,16 @@ class BioBioChileTVIE(InfoExtractor):
|
|||||||
'params': {
|
'params': {
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
},
|
},
|
||||||
|
'skip': 'URL expired and redirected to http://www.biobiochile.cl/portada/bbtv/index.html',
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.biobiochile.cl/noticias/bbtv/comentarios-bio-bio/2016/07/08/edecanes-del-congreso-figuras-decorativas-que-le-cuestan-muy-caro-a-los-chilenos.shtml',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'edecanes-del-congreso-figuras-decorativas-que-le-cuestan-muy-caro-a-los-chilenos',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'uploader': '(none)',
|
||||||
|
'upload_date': '20160708',
|
||||||
|
'title': 'Edecanes del Congreso: Figuras decorativas que le cuestan muy caro a los chilenos',
|
||||||
|
},
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://tv.biobiochile.cl/notas/2015/10/22/ninos-transexuales-de-quien-es-la-decision.shtml',
|
'url': 'http://tv.biobiochile.cl/notas/2015/10/22/ninos-transexuales-de-quien-es-la-decision.shtml',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
@@ -45,42 +60,22 @@ class BioBioChileTVIE(InfoExtractor):
|
|||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
rudo_url = RudoIE._extract_url(webpage)
|
||||||
|
if not rudo_url:
|
||||||
|
raise ExtractorError('No videos found')
|
||||||
|
|
||||||
title = remove_end(self._og_search_title(webpage), ' - BioBioChile TV')
|
title = remove_end(self._og_search_title(webpage), ' - BioBioChile TV')
|
||||||
|
|
||||||
file_url = self._search_regex(
|
|
||||||
r'loadFWPlayerVideo\([^,]+,\s*(["\'])(?P<url>.+?)\1',
|
|
||||||
webpage, 'file url', group='url')
|
|
||||||
|
|
||||||
base_url = self._search_regex(
|
|
||||||
r'file\s*:\s*(["\'])(?P<url>.+?)\1\s*\+\s*fileURL', webpage,
|
|
||||||
'base url', default='http://unlimited2-cl.digitalproserver.com/bbtv/',
|
|
||||||
group='url')
|
|
||||||
|
|
||||||
formats = self._extract_m3u8_formats(
|
|
||||||
'%s%s/playlist.m3u8' % (base_url, file_url), video_id, 'mp4',
|
|
||||||
entry_protocol='m3u8_native', m3u8_id='hls', fatal=False)
|
|
||||||
f = {
|
|
||||||
'url': '%s%s' % (base_url, file_url),
|
|
||||||
'format_id': 'http',
|
|
||||||
'protocol': 'http',
|
|
||||||
'preference': 1,
|
|
||||||
}
|
|
||||||
if formats:
|
|
||||||
f_copy = formats[-1].copy()
|
|
||||||
f_copy.update(f)
|
|
||||||
f = f_copy
|
|
||||||
formats.append(f)
|
|
||||||
self._sort_formats(formats)
|
|
||||||
|
|
||||||
thumbnail = self._og_search_thumbnail(webpage)
|
thumbnail = self._og_search_thumbnail(webpage)
|
||||||
uploader = self._html_search_regex(
|
uploader = self._html_search_regex(
|
||||||
r'<a[^>]+href=["\']https?://busca\.biobiochile\.cl/author[^>]+>(.+?)</a>',
|
r'<a[^>]+href=["\']https?://(?:busca|www)\.biobiochile\.cl/(?:lista/)?(?:author|autor)[^>]+>(.+?)</a>',
|
||||||
webpage, 'uploader', fatal=False)
|
webpage, 'uploader', fatal=False)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
'_type': 'url_transparent',
|
||||||
|
'url': rudo_url,
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': title,
|
'title': title,
|
||||||
'thumbnail': thumbnail,
|
'thumbnail': thumbnail,
|
||||||
'uploader': uploader,
|
'uploader': uploader,
|
||||||
'formats': formats,
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ class BIQLEIE(InfoExtractor):
|
|||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Ребенок в шоке от автоматической мойки',
|
'title': 'Ребенок в шоке от автоматической мойки',
|
||||||
'uploader': 'Dmitry Kotov',
|
'uploader': 'Dmitry Kotov',
|
||||||
}
|
},
|
||||||
|
'skip': ' This video was marked as adult. Embedding adult videos on external sites is prohibited.',
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
# coding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
@@ -20,6 +21,18 @@ class BloombergIE(InfoExtractor):
|
|||||||
'params': {
|
'params': {
|
||||||
'format': 'best[format_id^=hds]',
|
'format': 'best[format_id^=hds]',
|
||||||
},
|
},
|
||||||
|
}, {
|
||||||
|
# video ID in BPlayer(...)
|
||||||
|
'url': 'http://www.bloomberg.com/features/2016-hello-world-new-zealand/',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '938c7e72-3f25-4ddb-8b85-a9be731baa74',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Meet the Real-Life Tech Wizards of Middle Earth',
|
||||||
|
'description': 'Hello World, Episode 1: New Zealand’s freaky AI babies, robot exoskeletons, and a virtual you.',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'format': 'best[format_id^=hds]',
|
||||||
|
},
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.bloomberg.com/news/articles/2015-11-12/five-strange-things-that-have-been-happening-in-financial-markets',
|
'url': 'http://www.bloomberg.com/news/articles/2015-11-12/five-strange-things-that-have-been-happening-in-financial-markets',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
@@ -33,7 +46,11 @@ class BloombergIE(InfoExtractor):
|
|||||||
webpage = self._download_webpage(url, name)
|
webpage = self._download_webpage(url, name)
|
||||||
video_id = self._search_regex(
|
video_id = self._search_regex(
|
||||||
r'["\']bmmrId["\']\s*:\s*(["\'])(?P<url>.+?)\1',
|
r'["\']bmmrId["\']\s*:\s*(["\'])(?P<url>.+?)\1',
|
||||||
webpage, 'id', group='url')
|
webpage, 'id', group='url', default=None)
|
||||||
|
if not video_id:
|
||||||
|
bplayer_data = self._parse_json(self._search_regex(
|
||||||
|
r'BPlayer\(null,\s*({[^;]+})\);', webpage, 'id'), name)
|
||||||
|
video_id = bplayer_data['id']
|
||||||
title = re.sub(': Video$', '', self._og_search_title(webpage))
|
title = re.sub(': Video$', '', self._og_search_title(webpage))
|
||||||
|
|
||||||
embed_info = self._download_json(
|
embed_info = self._download_json(
|
||||||
|
|||||||
@@ -1,31 +1,74 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .adobepass import AdobePassIE
|
||||||
from ..utils import smuggle_url
|
from ..utils import (
|
||||||
|
smuggle_url,
|
||||||
|
update_url_query,
|
||||||
|
int_or_none,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class BravoTVIE(InfoExtractor):
|
class BravoTVIE(AdobePassIE):
|
||||||
_VALID_URL = r'https?://(?:www\.)?bravotv\.com/(?:[^/]+/)+videos/(?P<id>[^/?]+)'
|
_VALID_URL = r'https?://(?:www\.)?bravotv\.com/(?:[^/]+/)+(?P<id>[^/?#]+)'
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
'url': 'http://www.bravotv.com/last-chance-kitchen/season-5/videos/lck-ep-12-fishy-finale',
|
'url': 'http://www.bravotv.com/last-chance-kitchen/season-5/videos/lck-ep-12-fishy-finale',
|
||||||
'md5': 'd60cdf68904e854fac669bd26cccf801',
|
'md5': '9086d0b7ef0ea2aabc4781d75f4e5863',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'LitrBdX64qLn',
|
'id': 'zHyk1_HU_mPy',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Last Chance Kitchen Returns',
|
'title': 'LCK Ep 12: Fishy Finale',
|
||||||
'description': 'S13: Last Chance Kitchen Returns for Top Chef Season 13',
|
'description': 'S13/E12: Two eliminated chefs have just 12 minutes to cook up a delicious fish dish.',
|
||||||
'timestamp': 1448926740,
|
|
||||||
'upload_date': '20151130',
|
|
||||||
'uploader': 'NBCU-BRAV',
|
'uploader': 'NBCU-BRAV',
|
||||||
|
'upload_date': '20160302',
|
||||||
|
'timestamp': 1456945320,
|
||||||
}
|
}
|
||||||
}
|
}, {
|
||||||
|
'url': 'http://www.bravotv.com/below-deck/season-3/ep-14-reunion-part-1',
|
||||||
|
'only_matching': 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)
|
webpage = self._download_webpage(url, display_id)
|
||||||
account_pid = self._search_regex(r'"account_pid"\s*:\s*"([^"]+)"', webpage, 'account pid')
|
settings = self._parse_json(self._search_regex(
|
||||||
release_pid = self._search_regex(r'"release_pid"\s*:\s*"([^"]+)"', webpage, 'release pid')
|
r'jQuery\.extend\(Drupal\.settings\s*,\s*({.+?})\);', webpage, 'drupal settings'),
|
||||||
return self.url_result(smuggle_url(
|
display_id)
|
||||||
'http://link.theplatform.com/s/%s/%s?mbr=true&switch=progressive' % (account_pid, release_pid),
|
info = {}
|
||||||
{'force_smil_url': True}), 'ThePlatform', release_pid)
|
query = {
|
||||||
|
'mbr': 'true',
|
||||||
|
}
|
||||||
|
account_pid, release_pid = [None] * 2
|
||||||
|
tve = settings.get('sharedTVE')
|
||||||
|
if tve:
|
||||||
|
query['manifest'] = 'm3u'
|
||||||
|
account_pid = 'HNK2IC'
|
||||||
|
release_pid = tve['release_pid']
|
||||||
|
if tve.get('entitlement') == 'auth':
|
||||||
|
adobe_pass = settings.get('adobePass', {})
|
||||||
|
resource = self._get_mvpd_resource(
|
||||||
|
adobe_pass.get('adobePassResourceId', 'bravo'),
|
||||||
|
tve['title'], release_pid, tve.get('rating'))
|
||||||
|
query['auth'] = self._extract_mvpd_auth(
|
||||||
|
url, release_pid, adobe_pass.get('adobePassRequestorId', 'bravo'), resource)
|
||||||
|
else:
|
||||||
|
shared_playlist = settings['shared_playlist']
|
||||||
|
account_pid = shared_playlist['account_pid']
|
||||||
|
metadata = shared_playlist['video_metadata'][shared_playlist['default_clip']]
|
||||||
|
release_pid = metadata['release_pid']
|
||||||
|
info.update({
|
||||||
|
'title': metadata['title'],
|
||||||
|
'description': metadata.get('description'),
|
||||||
|
'season_number': int_or_none(metadata.get('season_num')),
|
||||||
|
'episode_number': int_or_none(metadata.get('episode_num')),
|
||||||
|
})
|
||||||
|
query['switch'] = 'progressive'
|
||||||
|
info.update({
|
||||||
|
'_type': 'url_transparent',
|
||||||
|
'id': release_pid,
|
||||||
|
'url': smuggle_url(update_url_query(
|
||||||
|
'http://link.theplatform.com/s/%s/%s' % (account_pid, release_pid),
|
||||||
|
query), {'force_smil_url': True}),
|
||||||
|
'ie_key': 'ThePlatform',
|
||||||
|
})
|
||||||
|
return info
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ from ..utils import (
|
|||||||
unescapeHTML,
|
unescapeHTML,
|
||||||
unsmuggle_url,
|
unsmuggle_url,
|
||||||
update_url_query,
|
update_url_query,
|
||||||
|
clean_html,
|
||||||
|
mimetype2ext,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -90,6 +92,7 @@ class BrightcoveLegacyIE(InfoExtractor):
|
|||||||
'description': 'md5:363109c02998fee92ec02211bd8000df',
|
'description': 'md5:363109c02998fee92ec02211bd8000df',
|
||||||
'uploader': 'National Ballet of Canada',
|
'uploader': 'National Ballet of Canada',
|
||||||
},
|
},
|
||||||
|
'skip': 'Video gone',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
# test flv videos served by akamaihd.net
|
# test flv videos served by akamaihd.net
|
||||||
@@ -108,7 +111,7 @@ class BrightcoveLegacyIE(InfoExtractor):
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
# playlist test
|
# playlist with 'videoList'
|
||||||
# from http://support.brightcove.com/en/video-cloud/docs/playlist-support-single-video-players
|
# from http://support.brightcove.com/en/video-cloud/docs/playlist-support-single-video-players
|
||||||
'url': 'http://c.brightcove.com/services/viewer/htmlFederated?playerID=3550052898001&playerKey=AQ%7E%7E%2CAAABmA9XpXk%7E%2C-Kp7jNgisre1fG5OdqpAFUTcs0lP_ZoL',
|
'url': 'http://c.brightcove.com/services/viewer/htmlFederated?playerID=3550052898001&playerKey=AQ%7E%7E%2CAAABmA9XpXk%7E%2C-Kp7jNgisre1fG5OdqpAFUTcs0lP_ZoL',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@@ -117,6 +120,15 @@ class BrightcoveLegacyIE(InfoExtractor):
|
|||||||
},
|
},
|
||||||
'playlist_mincount': 7,
|
'playlist_mincount': 7,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
# playlist with 'playlistTab' (https://github.com/rg3/youtube-dl/issues/9965)
|
||||||
|
'url': 'http://c.brightcove.com/services/json/experience/runtime/?command=get_programming_for_experience&playerKey=AQ%7E%7E,AAABXlLMdok%7E,NJ4EoMlZ4rZdx9eU1rkMVd8EaYPBBUlg',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '1522758701001',
|
||||||
|
'title': 'Lesson 08',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 10,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
FLV_VCODECS = {
|
FLV_VCODECS = {
|
||||||
1: 'SORENSON',
|
1: 'SORENSON',
|
||||||
@@ -298,13 +310,19 @@ class BrightcoveLegacyIE(InfoExtractor):
|
|||||||
info_url, player_key, 'Downloading playlist information')
|
info_url, player_key, 'Downloading playlist information')
|
||||||
|
|
||||||
json_data = json.loads(playlist_info)
|
json_data = json.loads(playlist_info)
|
||||||
if 'videoList' not in json_data:
|
if 'videoList' in json_data:
|
||||||
|
playlist_info = json_data['videoList']
|
||||||
|
playlist_dto = playlist_info['mediaCollectionDTO']
|
||||||
|
elif 'playlistTabs' in json_data:
|
||||||
|
playlist_info = json_data['playlistTabs']
|
||||||
|
playlist_dto = playlist_info['lineupListDTO']['playlistDTOs'][0]
|
||||||
|
else:
|
||||||
raise ExtractorError('Empty playlist')
|
raise ExtractorError('Empty playlist')
|
||||||
playlist_info = json_data['videoList']
|
|
||||||
videos = [self._extract_video_info(video_info) for video_info in playlist_info['mediaCollectionDTO']['videoDTOs']]
|
videos = [self._extract_video_info(video_info) for video_info in playlist_dto['videoDTOs']]
|
||||||
|
|
||||||
return self.playlist_result(videos, playlist_id='%s' % playlist_info['id'],
|
return self.playlist_result(videos, playlist_id='%s' % playlist_info['id'],
|
||||||
playlist_title=playlist_info['mediaCollectionDTO']['displayName'])
|
playlist_title=playlist_dto['displayName'])
|
||||||
|
|
||||||
def _extract_video_info(self, video_info):
|
def _extract_video_info(self, video_info):
|
||||||
video_id = compat_str(video_info['id'])
|
video_id = compat_str(video_info['id'])
|
||||||
@@ -528,14 +546,16 @@ class BrightcoveNewIE(InfoExtractor):
|
|||||||
formats = []
|
formats = []
|
||||||
for source in json_data.get('sources', []):
|
for source in json_data.get('sources', []):
|
||||||
container = source.get('container')
|
container = source.get('container')
|
||||||
source_type = source.get('type')
|
ext = mimetype2ext(source.get('type'))
|
||||||
src = source.get('src')
|
src = source.get('src')
|
||||||
if source_type == 'application/x-mpegURL' or container == 'M2TS':
|
if ext == 'ism':
|
||||||
|
continue
|
||||||
|
elif ext == 'm3u8' or container == 'M2TS':
|
||||||
if not src:
|
if not src:
|
||||||
continue
|
continue
|
||||||
formats.extend(self._extract_m3u8_formats(
|
formats.extend(self._extract_m3u8_formats(
|
||||||
src, video_id, 'mp4', 'm3u8_native', m3u8_id='hls', fatal=False))
|
src, video_id, 'mp4', 'm3u8_native', m3u8_id='hls', fatal=False))
|
||||||
elif source_type == 'application/dash+xml':
|
elif ext == 'mpd':
|
||||||
if not src:
|
if not src:
|
||||||
continue
|
continue
|
||||||
formats.extend(self._extract_mpd_formats(src, video_id, 'dash', fatal=False))
|
formats.extend(self._extract_mpd_formats(src, video_id, 'dash', fatal=False))
|
||||||
@@ -551,7 +571,7 @@ class BrightcoveNewIE(InfoExtractor):
|
|||||||
'tbr': tbr,
|
'tbr': tbr,
|
||||||
'filesize': int_or_none(source.get('size')),
|
'filesize': int_or_none(source.get('size')),
|
||||||
'container': container,
|
'container': container,
|
||||||
'ext': container.lower(),
|
'ext': ext or container.lower(),
|
||||||
}
|
}
|
||||||
if width == 0 and height == 0:
|
if width == 0 and height == 0:
|
||||||
f.update({
|
f.update({
|
||||||
@@ -585,6 +605,13 @@ class BrightcoveNewIE(InfoExtractor):
|
|||||||
'format_id': build_format_id('rtmp'),
|
'format_id': build_format_id('rtmp'),
|
||||||
})
|
})
|
||||||
formats.append(f)
|
formats.append(f)
|
||||||
|
|
||||||
|
errors = json_data.get('errors')
|
||||||
|
if not formats and errors:
|
||||||
|
error = errors[0]
|
||||||
|
raise ExtractorError(
|
||||||
|
error.get('message') or error.get('error_subcode') or error['error_code'], expected=True)
|
||||||
|
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
subtitles = {}
|
subtitles = {}
|
||||||
@@ -597,7 +624,7 @@ class BrightcoveNewIE(InfoExtractor):
|
|||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': title,
|
'title': title,
|
||||||
'description': json_data.get('description'),
|
'description': clean_html(json_data.get('description')),
|
||||||
'thumbnail': json_data.get('thumbnail') or json_data.get('poster'),
|
'thumbnail': json_data.get('thumbnail') or json_data.get('poster'),
|
||||||
'duration': float_or_none(json_data.get('duration'), 1000),
|
'duration': float_or_none(json_data.get('duration'), 1000),
|
||||||
'timestamp': parse_iso8601(json_data.get('published_at')),
|
'timestamp': parse_iso8601(json_data.get('published_at')),
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import json
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from .facebook import FacebookIE
|
||||||
|
|
||||||
|
|
||||||
class BuzzFeedIE(InfoExtractor):
|
class BuzzFeedIE(InfoExtractor):
|
||||||
@@ -20,11 +21,11 @@ class BuzzFeedIE(InfoExtractor):
|
|||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'aVCR29aE_OQ',
|
'id': 'aVCR29aE_OQ',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
|
'title': 'Angry Ram destroys a punching bag..',
|
||||||
|
'description': 'md5:c59533190ef23fd4458a5e8c8c872345',
|
||||||
'upload_date': '20141024',
|
'upload_date': '20141024',
|
||||||
'uploader_id': 'Buddhanz1',
|
'uploader_id': 'Buddhanz1',
|
||||||
'description': 'He likes to stay in shape with his heavy bag, he wont stop until its on the ground\n\nFollow Angry Ram on Facebook for regular updates -\nhttps://www.facebook.com/pages/Angry-Ram/1436897249899558?ref=hl',
|
'uploader': 'Angry Ram',
|
||||||
'uploader': 'Buddhanz',
|
|
||||||
'title': 'Angry Ram destroys a punching bag',
|
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
}, {
|
}, {
|
||||||
@@ -41,13 +42,30 @@ class BuzzFeedIE(InfoExtractor):
|
|||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'mVmBL8B-In0',
|
'id': 'mVmBL8B-In0',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
|
'title': 're:Munchkin the Teddy Bear gets her exercise',
|
||||||
|
'description': 'md5:28faab95cda6e361bcff06ec12fc21d8',
|
||||||
'upload_date': '20141124',
|
'upload_date': '20141124',
|
||||||
'uploader_id': 'CindysMunchkin',
|
'uploader_id': 'CindysMunchkin',
|
||||||
'description': 're:© 2014 Munchkin the',
|
|
||||||
'uploader': 're:^Munchkin the',
|
'uploader': 're:^Munchkin the',
|
||||||
'title': 're:Munchkin the Teddy Bear gets her exercise',
|
|
||||||
},
|
},
|
||||||
}]
|
}]
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.buzzfeed.com/craigsilverman/the-most-adorable-crash-landing-ever#.eq7pX0BAmK',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'the-most-adorable-crash-landing-ever',
|
||||||
|
'title': 'Watch This Baby Goose Make The Most Adorable Crash Landing',
|
||||||
|
'description': 'This gosling knows how to stick a landing.',
|
||||||
|
},
|
||||||
|
'playlist': [{
|
||||||
|
'md5': '763ca415512f91ca62e4621086900a23',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '971793786185728',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'We set up crash pads so that the goslings on our roof would have a safe landi...',
|
||||||
|
'uploader': 'Calgary Outdoor Centre-University of Calgary',
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
'add_ie': ['Facebook'],
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
@@ -66,6 +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)
|
||||||
|
if facebook_url:
|
||||||
|
entries.append(self.url_result(facebook_url))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'_type': 'playlist',
|
'_type': 'playlist',
|
||||||
'id': playlist_id,
|
'id': playlist_id,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import datetime
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
@@ -10,8 +9,10 @@ from ..compat import (
|
|||||||
compat_urlparse,
|
compat_urlparse,
|
||||||
)
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
parse_iso8601,
|
clean_html,
|
||||||
|
parse_duration,
|
||||||
str_to_int,
|
str_to_int,
|
||||||
|
unified_strdate,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -26,14 +27,14 @@ class CamdemyIE(InfoExtractor):
|
|||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Ch1-1 Introduction, Signals (02-23-2012)',
|
'title': 'Ch1-1 Introduction, Signals (02-23-2012)',
|
||||||
'thumbnail': 're:^https?://.*\.jpg$',
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
'description': '',
|
|
||||||
'creator': 'ss11spring',
|
'creator': 'ss11spring',
|
||||||
|
'duration': 1591,
|
||||||
'upload_date': '20130114',
|
'upload_date': '20130114',
|
||||||
'timestamp': 1358154556,
|
|
||||||
'view_count': int,
|
'view_count': int,
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
# With non-empty description
|
# With non-empty description
|
||||||
|
# webpage returns "No permission or not login"
|
||||||
'url': 'http://www.camdemy.com/media/13885',
|
'url': 'http://www.camdemy.com/media/13885',
|
||||||
'md5': '4576a3bb2581f86c61044822adbd1249',
|
'md5': '4576a3bb2581f86c61044822adbd1249',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@@ -41,64 +42,71 @@ class CamdemyIE(InfoExtractor):
|
|||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'EverCam + Camdemy QuickStart',
|
'title': 'EverCam + Camdemy QuickStart',
|
||||||
'thumbnail': 're:^https?://.*\.jpg$',
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
'description': 'md5:050b62f71ed62928f8a35f1a41e186c9',
|
'description': 'md5:2a9f989c2b153a2342acee579c6e7db6',
|
||||||
'creator': 'evercam',
|
'creator': 'evercam',
|
||||||
'upload_date': '20140620',
|
'duration': 318,
|
||||||
'timestamp': 1403271569,
|
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
# External source
|
# External source (YouTube)
|
||||||
'url': 'http://www.camdemy.com/media/14842',
|
'url': 'http://www.camdemy.com/media/14842',
|
||||||
'md5': '50e1c3c3aa233d3d7b7daa2fa10b1cf7',
|
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '2vsYQzNIsJo',
|
'id': '2vsYQzNIsJo',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
|
'title': 'Excel 2013 Tutorial - How to add Password Protection',
|
||||||
|
'description': 'Excel 2013 Tutorial for Beginners - How to add Password Protection',
|
||||||
'upload_date': '20130211',
|
'upload_date': '20130211',
|
||||||
'uploader': 'Hun Kim',
|
'uploader': 'Hun Kim',
|
||||||
'description': 'Excel 2013 Tutorial for Beginners - How to add Password Protection',
|
|
||||||
'uploader_id': 'hunkimtutorials',
|
'uploader_id': 'hunkimtutorials',
|
||||||
'title': 'Excel 2013 Tutorial - How to add Password Protection',
|
},
|
||||||
}
|
'params': {
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
page = self._download_webpage(url, video_id)
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
src_from = self._html_search_regex(
|
src_from = self._html_search_regex(
|
||||||
r"<div class='srcFrom'>Source: <a title='([^']+)'", page,
|
r"class=['\"]srcFrom['\"][^>]*>Sources?(?:\s+from)?\s*:\s*<a[^>]+(?:href|title)=(['\"])(?P<url>(?:(?!\1).)+)\1",
|
||||||
'external source', default=None)
|
webpage, 'external source', default=None, group='url')
|
||||||
if src_from:
|
if src_from:
|
||||||
return self.url_result(src_from)
|
return self.url_result(src_from)
|
||||||
|
|
||||||
oembed_obj = self._download_json(
|
oembed_obj = self._download_json(
|
||||||
'http://www.camdemy.com/oembed/?format=json&url=' + url, video_id)
|
'http://www.camdemy.com/oembed/?format=json&url=' + url, video_id)
|
||||||
|
|
||||||
|
title = oembed_obj['title']
|
||||||
thumb_url = oembed_obj['thumbnail_url']
|
thumb_url = oembed_obj['thumbnail_url']
|
||||||
video_folder = compat_urlparse.urljoin(thumb_url, 'video/')
|
video_folder = compat_urlparse.urljoin(thumb_url, 'video/')
|
||||||
file_list_doc = self._download_xml(
|
file_list_doc = self._download_xml(
|
||||||
compat_urlparse.urljoin(video_folder, 'fileList.xml'),
|
compat_urlparse.urljoin(video_folder, 'fileList.xml'),
|
||||||
video_id, 'Filelist XML')
|
video_id, 'Downloading filelist XML')
|
||||||
file_name = file_list_doc.find('./video/item/fileName').text
|
file_name = file_list_doc.find('./video/item/fileName').text
|
||||||
video_url = compat_urlparse.urljoin(video_folder, file_name)
|
video_url = compat_urlparse.urljoin(video_folder, file_name)
|
||||||
|
|
||||||
timestamp = parse_iso8601(self._html_search_regex(
|
# Some URLs return "No permission or not login" in a webpage despite being
|
||||||
r"<div class='title'>Posted\s*:</div>\s*<div class='value'>([^<>]+)<",
|
# freely available via oembed JSON URL (e.g. http://www.camdemy.com/media/13885)
|
||||||
page, 'creation time', fatal=False),
|
upload_date = unified_strdate(self._search_regex(
|
||||||
delimiter=' ', timezone=datetime.timedelta(hours=8))
|
r'>published on ([^<]+)<', webpage,
|
||||||
view_count = str_to_int(self._html_search_regex(
|
'upload date', default=None))
|
||||||
r"<div class='title'>Views\s*:</div>\s*<div class='value'>([^<>]+)<",
|
view_count = str_to_int(self._search_regex(
|
||||||
page, 'view count', fatal=False))
|
r'role=["\']viewCnt["\'][^>]*>([\d,.]+) views',
|
||||||
|
webpage, 'view count', default=None))
|
||||||
|
description = self._html_search_meta(
|
||||||
|
'description', webpage, default=None) or clean_html(
|
||||||
|
oembed_obj.get('description'))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'url': video_url,
|
'url': video_url,
|
||||||
'title': oembed_obj['title'],
|
'title': title,
|
||||||
'thumbnail': thumb_url,
|
'thumbnail': thumb_url,
|
||||||
'description': self._html_search_meta('description', page),
|
'description': description,
|
||||||
'creator': oembed_obj['author_name'],
|
'creator': oembed_obj.get('author_name'),
|
||||||
'duration': oembed_obj['duration'],
|
'duration': parse_duration(oembed_obj.get('duration')),
|
||||||
'timestamp': timestamp,
|
'upload_date': upload_date,
|
||||||
'view_count': view_count,
|
'view_count': view_count,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ 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 (
|
||||||
js_to_json,
|
js_to_json,
|
||||||
smuggle_url,
|
smuggle_url,
|
||||||
|
try_get,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -25,8 +27,22 @@ class CBCIE(InfoExtractor):
|
|||||||
'upload_date': '20160203',
|
'upload_date': '20160203',
|
||||||
'uploader': 'CBCC-NEW',
|
'uploader': 'CBCC-NEW',
|
||||||
},
|
},
|
||||||
|
'skip': 'Geo-restricted to Canada',
|
||||||
}, {
|
}, {
|
||||||
# with clipId
|
# with clipId, feed available via tpfeed.cbc.ca and feed.theplatform.com
|
||||||
|
'url': 'http://www.cbc.ca/22minutes/videos/22-minutes-update/22-minutes-update-episode-4',
|
||||||
|
'md5': '162adfa070274b144f4fdc3c3b8207db',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '2414435309',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': '22 Minutes Update: What Not To Wear Quebec',
|
||||||
|
'description': "This week's latest Canadian top political story is What Not To Wear Quebec.",
|
||||||
|
'upload_date': '20131025',
|
||||||
|
'uploader': 'CBCC-NEW',
|
||||||
|
'timestamp': 1382717907,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
# with clipId, feed only available via tpfeed.cbc.ca
|
||||||
'url': 'http://www.cbc.ca/archives/entry/1978-robin-williams-freestyles-on-90-minutes-live',
|
'url': 'http://www.cbc.ca/archives/entry/1978-robin-williams-freestyles-on-90-minutes-live',
|
||||||
'md5': '0274a90b51a9b4971fe005c63f592f12',
|
'md5': '0274a90b51a9b4971fe005c63f592f12',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@@ -64,6 +80,7 @@ class CBCIE(InfoExtractor):
|
|||||||
'uploader': 'CBCC-NEW',
|
'uploader': 'CBCC-NEW',
|
||||||
},
|
},
|
||||||
}],
|
}],
|
||||||
|
'skip': 'Geo-restricted to Canada',
|
||||||
}]
|
}]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -81,9 +98,15 @@ class CBCIE(InfoExtractor):
|
|||||||
media_id = player_info.get('mediaId')
|
media_id = player_info.get('mediaId')
|
||||||
if not media_id:
|
if not media_id:
|
||||||
clip_id = player_info['clipId']
|
clip_id = player_info['clipId']
|
||||||
media_id = self._download_json(
|
feed = self._download_json(
|
||||||
'http://feed.theplatform.com/f/h9dtGB/punlNGjMlc1F?fields=id&byContent=byReleases%3DbyId%253D' + clip_id,
|
'http://tpfeed.cbc.ca/f/ExhSPC/vms_5akSXx4Ng_Zn?byCustomValue={:mpsReleases}{%s}' % clip_id,
|
||||||
clip_id)['entries'][0]['id'].split('/')[-1]
|
clip_id, fatal=False)
|
||||||
|
if feed:
|
||||||
|
media_id = try_get(feed, lambda x: x['entries'][0]['guid'], compat_str)
|
||||||
|
if not media_id:
|
||||||
|
media_id = self._download_json(
|
||||||
|
'http://feed.theplatform.com/f/h9dtGB/punlNGjMlc1F?fields=id&byContent=byReleases%3DbyId%253D' + clip_id,
|
||||||
|
clip_id)['entries'][0]['id'].split('/')[-1]
|
||||||
return self.url_result('cbcplayer:%s' % media_id, 'CBCPlayer', media_id)
|
return self.url_result('cbcplayer:%s' % media_id, 'CBCPlayer', media_id)
|
||||||
else:
|
else:
|
||||||
entries = [self.url_result('cbcplayer:%s' % media_id, 'CBCPlayer', media_id) for media_id in re.findall(r'<iframe[^>]+src="[^"]+?mediaId=(\d+)"', webpage)]
|
entries = [self.url_result('cbcplayer:%s' % media_id, 'CBCPlayer', media_id) for media_id in re.findall(r'<iframe[^>]+src="[^"]+?mediaId=(\d+)"', webpage)]
|
||||||
@@ -104,6 +127,7 @@ class CBCPlayerIE(InfoExtractor):
|
|||||||
'upload_date': '20160210',
|
'upload_date': '20160210',
|
||||||
'uploader': 'CBCC-NEW',
|
'uploader': 'CBCC-NEW',
|
||||||
},
|
},
|
||||||
|
'skip': 'Geo-restricted to Canada',
|
||||||
}, {
|
}, {
|
||||||
# Redirected from http://www.cbc.ca/player/AudioMobile/All%20in%20a%20Weekend%20Montreal/ID/2657632011/
|
# Redirected from http://www.cbc.ca/player/AudioMobile/All%20in%20a%20Weekend%20Montreal/ID/2657632011/
|
||||||
'url': 'http://www.cbc.ca/player/play/2657631896',
|
'url': 'http://www.cbc.ca/player/play/2657631896',
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from .theplatform import ThePlatformFeedIE
|
|||||||
from ..utils import (
|
from ..utils import (
|
||||||
int_or_none,
|
int_or_none,
|
||||||
find_xpath_attr,
|
find_xpath_attr,
|
||||||
|
ExtractorError,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -17,19 +18,6 @@ class CBSBaseIE(ThePlatformFeedIE):
|
|||||||
}]
|
}]
|
||||||
} if closed_caption_e is not None and closed_caption_e.attrib.get('value') else []
|
} if closed_caption_e is not None and closed_caption_e.attrib.get('value') else []
|
||||||
|
|
||||||
def _extract_video_info(self, filter_query, video_id):
|
|
||||||
return self._extract_feed_info(
|
|
||||||
'dJ5BDC', 'VxxJg8Ymh8sE', filter_query, video_id, lambda entry: {
|
|
||||||
'series': entry.get('cbs$SeriesTitle'),
|
|
||||||
'season_number': int_or_none(entry.get('cbs$SeasonNumber')),
|
|
||||||
'episode': entry.get('cbs$EpisodeTitle'),
|
|
||||||
'episode_number': int_or_none(entry.get('cbs$EpisodeNumber')),
|
|
||||||
}, {
|
|
||||||
'StreamPack': {
|
|
||||||
'manifest': 'm3u',
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
class CBSIE(CBSBaseIE):
|
class CBSIE(CBSBaseIE):
|
||||||
_VALID_URL = r'(?:cbs:|https?://(?:www\.)?(?:cbs\.com/shows/[^/]+/video|colbertlateshow\.com/(?:video|podcasts))/)(?P<id>[\w-]+)'
|
_VALID_URL = r'(?:cbs:|https?://(?:www\.)?(?:cbs\.com/shows/[^/]+/video|colbertlateshow\.com/(?:video|podcasts))/)(?P<id>[\w-]+)'
|
||||||
@@ -38,7 +26,6 @@ class CBSIE(CBSBaseIE):
|
|||||||
'url': 'http://www.cbs.com/shows/garth-brooks/video/_u7W953k6la293J7EPTd9oHkSPs6Xn6_/connect-chat-feat-garth-brooks/',
|
'url': 'http://www.cbs.com/shows/garth-brooks/video/_u7W953k6la293J7EPTd9oHkSPs6Xn6_/connect-chat-feat-garth-brooks/',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '_u7W953k6la293J7EPTd9oHkSPs6Xn6_',
|
'id': '_u7W953k6la293J7EPTd9oHkSPs6Xn6_',
|
||||||
'display_id': 'connect-chat-feat-garth-brooks',
|
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Connect Chat feat. Garth Brooks',
|
'title': 'Connect Chat feat. Garth Brooks',
|
||||||
'description': 'Connect with country music singer Garth Brooks, as he chats with fans on Wednesday November 27, 2013. Be sure to tune in to Garth Brooks: Live from Las Vegas, Friday November 29, at 9/8c on CBS!',
|
'description': 'Connect with country music singer Garth Brooks, as he chats with fans on Wednesday November 27, 2013. Be sure to tune in to Garth Brooks: Live from Las Vegas, Friday November 29, at 9/8c on CBS!',
|
||||||
@@ -47,7 +34,10 @@ class CBSIE(CBSBaseIE):
|
|||||||
'upload_date': '20131127',
|
'upload_date': '20131127',
|
||||||
'uploader': 'CBSI-NEW',
|
'uploader': 'CBSI-NEW',
|
||||||
},
|
},
|
||||||
'expected_warnings': ['Failed to download m3u8 information'],
|
'params': {
|
||||||
|
# m3u8 download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
'_skip': 'Blocked outside the US',
|
'_skip': 'Blocked outside the US',
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://colbertlateshow.com/video/8GmB0oY0McANFvp2aEffk9jZZZ2YyXxy/the-colbeard/',
|
'url': 'http://colbertlateshow.com/video/8GmB0oY0McANFvp2aEffk9jZZZ2YyXxy/the-colbeard/',
|
||||||
@@ -56,8 +46,31 @@ class CBSIE(CBSBaseIE):
|
|||||||
'url': 'http://www.colbertlateshow.com/podcasts/dYSwjqPs_X1tvbV_P2FcPWRa_qT6akTC/in-the-bad-room-with-stephen/',
|
'url': 'http://www.colbertlateshow.com/podcasts/dYSwjqPs_X1tvbV_P2FcPWRa_qT6akTC/in-the-bad-room-with-stephen/',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
TP_RELEASE_URL_TEMPLATE = 'http://link.theplatform.com/s/dJ5BDC/%s?mbr=true'
|
|
||||||
|
def _extract_video_info(self, guid):
|
||||||
|
path = 'dJ5BDC/media/guid/2198311517/' + guid
|
||||||
|
smil_url = 'http://link.theplatform.com/s/%s?mbr=true' % path
|
||||||
|
formats, subtitles = self._extract_theplatform_smil(smil_url + '&manifest=m3u', guid)
|
||||||
|
for r in ('HLS&formats=M3U', 'RTMP', 'WIFI', '3G'):
|
||||||
|
try:
|
||||||
|
tp_formats, _ = self._extract_theplatform_smil(smil_url + '&assetTypes=' + r, guid, 'Downloading %s SMIL data' % r.split('&')[0])
|
||||||
|
formats.extend(tp_formats)
|
||||||
|
except ExtractorError:
|
||||||
|
continue
|
||||||
|
self._sort_formats(formats)
|
||||||
|
metadata = self._download_theplatform_metadata(path, guid)
|
||||||
|
info = self._parse_theplatform_metadata(metadata)
|
||||||
|
info.update({
|
||||||
|
'id': guid,
|
||||||
|
'formats': formats,
|
||||||
|
'subtitles': subtitles,
|
||||||
|
'series': metadata.get('cbs$SeriesTitle'),
|
||||||
|
'season_number': int_or_none(metadata.get('cbs$SeasonNumber')),
|
||||||
|
'episode': metadata.get('cbs$EpisodeTitle'),
|
||||||
|
'episode_number': int_or_none(metadata.get('cbs$EpisodeNumber')),
|
||||||
|
})
|
||||||
|
return info
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
content_id = self._match_id(url)
|
content_id = self._match_id(url)
|
||||||
return self._extract_video_info('byGuid=%s' % content_id, content_id)
|
return self._extract_video_info(content_id)
|
||||||
|
|||||||
@@ -80,9 +80,6 @@ class CBSInteractiveIE(ThePlatformIE):
|
|||||||
|
|
||||||
media_guid_path = 'media/guid/%d/%s' % (self.MPX_ACCOUNTS[site], vdata['mpxRefId'])
|
media_guid_path = 'media/guid/%d/%s' % (self.MPX_ACCOUNTS[site], vdata['mpxRefId'])
|
||||||
formats, subtitles = [], {}
|
formats, subtitles = [], {}
|
||||||
if site == 'cnet':
|
|
||||||
formats, subtitles = self._extract_theplatform_smil(
|
|
||||||
self.TP_RELEASE_URL_TEMPLATE % media_guid_path, video_id)
|
|
||||||
for (fkey, vid) in vdata['files'].items():
|
for (fkey, vid) in vdata['files'].items():
|
||||||
if fkey == 'hls_phone' and 'hls_tablet' in vdata['files']:
|
if fkey == 'hls_phone' and 'hls_tablet' in vdata['files']:
|
||||||
continue
|
continue
|
||||||
@@ -94,7 +91,7 @@ class CBSInteractiveIE(ThePlatformIE):
|
|||||||
subtitles = self._merge_subtitles(subtitles, tp_subtitles)
|
subtitles = self._merge_subtitles(subtitles, tp_subtitles)
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
info = self.get_metadata('kYEXFC/%s' % media_guid_path, video_id)
|
info = self._extract_theplatform_metadata('kYEXFC/%s' % media_guid_path, video_id)
|
||||||
info.update({
|
info.update({
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'display_id': display_id,
|
'display_id': display_id,
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import calendar
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
from .anvato import AnvatoIE
|
from .anvato import AnvatoIE
|
||||||
from .sendtonews import SendtoNewsIE
|
from .sendtonews import SendtoNewsIE
|
||||||
from ..compat import compat_urlparse
|
from ..compat import compat_urlparse
|
||||||
|
from ..utils import unified_timestamp
|
||||||
|
|
||||||
|
|
||||||
class CBSLocalIE(AnvatoIE):
|
class CBSLocalIE(AnvatoIE):
|
||||||
@@ -43,13 +41,8 @@ class CBSLocalIE(AnvatoIE):
|
|||||||
'url': 'http://cleveland.cbslocal.com/2016/05/16/indians-score-season-high-15-runs-in-blowout-win-over-reds-rapid-reaction/',
|
'url': 'http://cleveland.cbslocal.com/2016/05/16/indians-score-season-high-15-runs-in-blowout-win-over-reds-rapid-reaction/',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'GxfCe0Zo7D-175909-5588',
|
'id': 'GxfCe0Zo7D-175909-5588',
|
||||||
'ext': 'mp4',
|
|
||||||
'title': 'Recap: CLE 15, CIN 6',
|
|
||||||
'description': '5/16/16: Indians\' bats explode for 15 runs in a win',
|
|
||||||
'upload_date': '20160516',
|
|
||||||
'timestamp': 1463433840,
|
|
||||||
'duration': 49,
|
|
||||||
},
|
},
|
||||||
|
'playlist_count': 9,
|
||||||
'params': {
|
'params': {
|
||||||
# m3u8 download
|
# m3u8 download
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
@@ -62,19 +55,15 @@ class CBSLocalIE(AnvatoIE):
|
|||||||
|
|
||||||
sendtonews_url = SendtoNewsIE._extract_url(webpage)
|
sendtonews_url = SendtoNewsIE._extract_url(webpage)
|
||||||
if sendtonews_url:
|
if sendtonews_url:
|
||||||
info_dict = {
|
return self.url_result(
|
||||||
'_type': 'url_transparent',
|
compat_urlparse.urljoin(url, sendtonews_url),
|
||||||
'url': compat_urlparse.urljoin(url, sendtonews_url),
|
ie=SendtoNewsIE.ie_key())
|
||||||
}
|
|
||||||
else:
|
info_dict = self._extract_anvato_videos(webpage, display_id)
|
||||||
info_dict = self._extract_anvato_videos(webpage, display_id)
|
|
||||||
|
|
||||||
time_str = self._html_search_regex(
|
time_str = self._html_search_regex(
|
||||||
r'class="entry-date">([^<]+)<', webpage, 'released date', fatal=False)
|
r'class="entry-date">([^<]+)<', webpage, 'released date', fatal=False)
|
||||||
timestamp = None
|
timestamp = unified_timestamp(time_str)
|
||||||
if time_str:
|
|
||||||
timestamp = calendar.timegm(datetime.datetime.strptime(
|
|
||||||
time_str, '%b %d, %Y %I:%M %p').timetuple())
|
|
||||||
|
|
||||||
info_dict.update({
|
info_dict.update({
|
||||||
'display_id': display_id,
|
'display_id': display_id,
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from .cbs import CBSBaseIE
|
from .cbs import CBSIE
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
parse_duration,
|
parse_duration,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class CBSNewsIE(CBSBaseIE):
|
class CBSNewsIE(CBSIE):
|
||||||
IE_DESC = 'CBS News'
|
IE_DESC = 'CBS News'
|
||||||
_VALID_URL = r'https?://(?:www\.)?cbsnews\.com/(?:news|videos)/(?P<id>[\da-z_-]+)'
|
_VALID_URL = r'https?://(?:www\.)?cbsnews\.com/(?:news|videos)/(?P<id>[\da-z_-]+)'
|
||||||
|
|
||||||
@@ -26,6 +26,7 @@ class CBSNewsIE(CBSBaseIE):
|
|||||||
# rtmp download
|
# rtmp 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/',
|
||||||
@@ -34,7 +35,8 @@ class CBSNewsIE(CBSBaseIE):
|
|||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Fort Hood shooting: Army downplays mental illness as cause of attack',
|
'title': 'Fort Hood shooting: Army downplays mental illness as cause of attack',
|
||||||
'description': 'md5:4a6983e480542d8b333a947bfc64ddc7',
|
'description': 'md5:4a6983e480542d8b333a947bfc64ddc7',
|
||||||
'upload_date': '19700101',
|
'upload_date': '20140404',
|
||||||
|
'timestamp': 1396650660,
|
||||||
'uploader': 'CBSI-NEW',
|
'uploader': 'CBSI-NEW',
|
||||||
'thumbnail': 're:^https?://.*\.jpg$',
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
'duration': 205,
|
'duration': 205,
|
||||||
@@ -62,13 +64,14 @@ class CBSNewsIE(CBSBaseIE):
|
|||||||
|
|
||||||
item = video_info['item'] if 'item' in video_info else video_info
|
item = video_info['item'] if 'item' in video_info else video_info
|
||||||
guid = item['mpxRefId']
|
guid = item['mpxRefId']
|
||||||
return self._extract_video_info('byGuid=%s' % guid, guid)
|
return self._extract_video_info(guid)
|
||||||
|
|
||||||
|
|
||||||
class CBSNewsLiveVideoIE(InfoExtractor):
|
class CBSNewsLiveVideoIE(InfoExtractor):
|
||||||
IE_DESC = 'CBS News Live Videos'
|
IE_DESC = 'CBS News Live Videos'
|
||||||
_VALID_URL = r'https?://(?:www\.)?cbsnews\.com/live/video/(?P<id>[\da-z_-]+)'
|
_VALID_URL = r'https?://(?:www\.)?cbsnews\.com/live/video/(?P<id>[\da-z_-]+)'
|
||||||
|
|
||||||
|
# Live videos get deleted soon. See http://www.cbsnews.com/live/ for the latest examples
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.cbsnews.com/live/video/clinton-sanders-prepare-to-face-off-in-nh/',
|
'url': 'http://www.cbsnews.com/live/video/clinton-sanders-prepare-to-face-off-in-nh/',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@@ -77,6 +80,7 @@ class CBSNewsLiveVideoIE(InfoExtractor):
|
|||||||
'title': 'Clinton, Sanders Prepare To Face Off In NH',
|
'title': 'Clinton, Sanders Prepare To Face Off In NH',
|
||||||
'duration': 334,
|
'duration': 334,
|
||||||
},
|
},
|
||||||
|
'skip': 'Video gone',
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ class CBSSportsIE(CBSBaseIE):
|
|||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
|
|
||||||
|
def _extract_video_info(self, filter_query, video_id):
|
||||||
|
return self._extract_feed_info('dJ5BDC', 'VxxJg8Ymh8sE', filter_query, video_id)
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
return self._extract_video_info('byId=%s' % video_id, video_id)
|
return self._extract_video_info('byId=%s' % video_id, video_id)
|
||||||
|
|||||||
51
youtube_dl/extractor/charlierose.py
Normal file
51
youtube_dl/extractor/charlierose.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import remove_end
|
||||||
|
|
||||||
|
|
||||||
|
class CharlieRoseIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?charlierose\.com/video(?:s|/player)/(?P<id>\d+)'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://charlierose.com/videos/27996',
|
||||||
|
'md5': 'fda41d49e67d4ce7c2411fd2c4702e09',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '27996',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Remembering Zaha Hadid',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg\?\d+',
|
||||||
|
'description': 'We revisit past conversations with Zaha Hadid, in memory of the world renowned Iraqi architect.',
|
||||||
|
'subtitles': {
|
||||||
|
'en': [{
|
||||||
|
'ext': 'vtt',
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'https://charlierose.com/videos/27996',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
_PLAYER_BASE = 'https://charlierose.com/video/player/%s'
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
webpage = self._download_webpage(self._PLAYER_BASE % video_id, video_id)
|
||||||
|
|
||||||
|
title = remove_end(self._og_search_title(webpage), ' - Charlie Rose')
|
||||||
|
|
||||||
|
info_dict = self._parse_html5_media_entries(
|
||||||
|
self._PLAYER_BASE % video_id, webpage, video_id,
|
||||||
|
m3u8_entry_protocol='m3u8_native')[0]
|
||||||
|
|
||||||
|
self._sort_formats(info_dict['formats'])
|
||||||
|
self._remove_duplicate_formats(info_dict['formats'])
|
||||||
|
|
||||||
|
info_dict.update({
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'thumbnail': self._og_search_thumbnail(webpage),
|
||||||
|
'description': self._og_search_description(webpage),
|
||||||
|
})
|
||||||
|
|
||||||
|
return info_dict
|
||||||
@@ -17,7 +17,8 @@ class ChaturbateIE(InfoExtractor):
|
|||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
}
|
},
|
||||||
|
'skip': 'Room is offline',
|
||||||
}, {
|
}, {
|
||||||
'url': 'https://en.chaturbate.com/siswet19/',
|
'url': 'https://en.chaturbate.com/siswet19/',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
|||||||
@@ -1,30 +1,33 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import base64
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import parse_duration
|
||||||
parse_duration,
|
|
||||||
int_or_none,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ChirbitIE(InfoExtractor):
|
class ChirbitIE(InfoExtractor):
|
||||||
IE_NAME = 'chirbit'
|
IE_NAME = 'chirbit'
|
||||||
_VALID_URL = r'https?://(?:www\.)?chirb\.it/(?:(?:wp|pl)/|fb_chirbit_player\.swf\?key=)?(?P<id>[\da-zA-Z]+)'
|
_VALID_URL = r'https?://(?:www\.)?chirb\.it/(?:(?:wp|pl)/|fb_chirbit_player\.swf\?key=)?(?P<id>[\da-zA-Z]+)'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://chirb.it/PrIPv5',
|
'url': 'http://chirb.it/be2abG',
|
||||||
'md5': '9847b0dad6ac3e074568bf2cfb197de8',
|
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'PrIPv5',
|
'id': 'be2abG',
|
||||||
'ext': 'mp3',
|
'ext': 'mp3',
|
||||||
'title': 'Фасадстрой',
|
'title': 'md5:f542ea253f5255240be4da375c6a5d7e',
|
||||||
'duration': 52,
|
'description': 'md5:f24a4e22a71763e32da5fed59e47c770',
|
||||||
'view_count': int,
|
'duration': 306,
|
||||||
'comment_count': int,
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True,
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
'url': 'https://chirb.it/fb_chirbit_player.swf?key=PrIPv5',
|
'url': 'https://chirb.it/fb_chirbit_player.swf?key=PrIPv5',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://chirb.it/wp/MN58c2',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
@@ -33,27 +36,30 @@ class ChirbitIE(InfoExtractor):
|
|||||||
webpage = self._download_webpage(
|
webpage = self._download_webpage(
|
||||||
'http://chirb.it/%s' % audio_id, audio_id)
|
'http://chirb.it/%s' % audio_id, audio_id)
|
||||||
|
|
||||||
audio_url = self._search_regex(
|
data_fd = self._search_regex(
|
||||||
r'"setFile"\s*,\s*"([^"]+)"', webpage, 'audio url')
|
r'data-fd=(["\'])(?P<url>(?:(?!\1).)+)\1',
|
||||||
|
webpage, 'data fd', group='url')
|
||||||
|
|
||||||
|
# Reverse engineered from https://chirb.it/js/chirbit.player.js (look
|
||||||
|
# for soundURL)
|
||||||
|
audio_url = base64.b64decode(
|
||||||
|
data_fd[::-1].encode('ascii')).decode('utf-8')
|
||||||
|
|
||||||
title = self._search_regex(
|
title = self._search_regex(
|
||||||
r'itemprop="name">([^<]+)', webpage, 'title')
|
r'class=["\']chirbit-title["\'][^>]*>([^<]+)', webpage, 'title')
|
||||||
duration = parse_duration(self._html_search_meta(
|
description = self._search_regex(
|
||||||
'duration', webpage, 'duration', fatal=False))
|
r'<h3>Description</h3>\s*<pre[^>]*>([^<]+)</pre>',
|
||||||
view_count = int_or_none(self._search_regex(
|
webpage, 'description', default=None)
|
||||||
r'itemprop="playCount"\s*>(\d+)', webpage,
|
duration = parse_duration(self._search_regex(
|
||||||
'listen count', fatal=False))
|
r'class=["\']c-length["\'][^>]*>([^<]+)',
|
||||||
comment_count = int_or_none(self._search_regex(
|
webpage, 'duration', fatal=False))
|
||||||
r'>(\d+) Comments?:', webpage,
|
|
||||||
'comment count', fatal=False))
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': audio_id,
|
'id': audio_id,
|
||||||
'url': audio_url,
|
'url': audio_url,
|
||||||
'title': title,
|
'title': title,
|
||||||
|
'description': description,
|
||||||
'duration': duration,
|
'duration': duration,
|
||||||
'view_count': view_count,
|
|
||||||
'comment_count': comment_count,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class CliphunterIE(InfoExtractor):
|
|||||||
(?P<id>[0-9]+)/
|
(?P<id>[0-9]+)/
|
||||||
(?P<seo>.+?)(?:$|[#\?])
|
(?P<seo>.+?)(?:$|[#\?])
|
||||||
'''
|
'''
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
'url': 'http://www.cliphunter.com/w/1012420/Fun_Jynx_Maze_solo',
|
'url': 'http://www.cliphunter.com/w/1012420/Fun_Jynx_Maze_solo',
|
||||||
'md5': 'b7c9bbd4eb3a226ab91093714dcaa480',
|
'md5': 'b7c9bbd4eb3a226ab91093714dcaa480',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@@ -32,8 +32,19 @@ class CliphunterIE(InfoExtractor):
|
|||||||
'title': 'Fun Jynx Maze solo',
|
'title': 'Fun Jynx Maze solo',
|
||||||
'thumbnail': 're:^https?://.*\.jpg$',
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
'age_limit': 18,
|
'age_limit': 18,
|
||||||
}
|
},
|
||||||
}
|
'skip': 'Video gone',
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.cliphunter.com/w/2019449/ShesNew__My_booty_girlfriend_Victoria_Paradices_pussy_filled_with_jizz',
|
||||||
|
'md5': '55a723c67bfc6da6b0cfa00d55da8a27',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '2019449',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'ShesNew - My booty girlfriend, Victoria Paradice\'s pussy filled with jizz',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
'age_limit': 18,
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
|
|||||||
@@ -1,16 +1,10 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .onet import OnetBaseIE
|
||||||
from ..utils import (
|
|
||||||
ExtractorError,
|
|
||||||
float_or_none,
|
|
||||||
int_or_none,
|
|
||||||
parse_iso8601,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ClipRsIE(InfoExtractor):
|
class ClipRsIE(OnetBaseIE):
|
||||||
_VALID_URL = r'https?://(?:www\.)?clip\.rs/(?P<id>[^/]+)/\d+'
|
_VALID_URL = r'https?://(?:www\.)?clip\.rs/(?P<id>[^/]+)/\d+'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.clip.rs/premijera-frajle-predstavljaju-novi-spot-za-pesmu-moli-me-moli/3732',
|
'url': 'http://www.clip.rs/premijera-frajle-predstavljaju-novi-spot-za-pesmu-moli-me-moli/3732',
|
||||||
@@ -27,64 +21,13 @@ class ClipRsIE(InfoExtractor):
|
|||||||
}
|
}
|
||||||
|
|
||||||
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)
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
|
||||||
video_id = self._search_regex(
|
mvp_id = self._search_mvp_id(webpage)
|
||||||
r'id=(["\'])mvp:(?P<id>.+?)\1', webpage, 'mvp id', group='id')
|
|
||||||
|
|
||||||
response = self._download_json(
|
info_dict = self._extract_from_id(mvp_id, webpage)
|
||||||
'http://qi.ckm.onetapi.pl/', video_id,
|
info_dict['display_id'] = display_id
|
||||||
query={
|
|
||||||
'body[id]': video_id,
|
|
||||||
'body[jsonrpc]': '2.0',
|
|
||||||
'body[method]': 'get_asset_detail',
|
|
||||||
'body[params][ID_Publikacji]': video_id,
|
|
||||||
'body[params][Service]': 'www.onet.pl',
|
|
||||||
'content-type': 'application/jsonp',
|
|
||||||
'x-onet-app': 'player.front.onetapi.pl',
|
|
||||||
})
|
|
||||||
|
|
||||||
error = response.get('error')
|
return info_dict
|
||||||
if error:
|
|
||||||
raise ExtractorError(
|
|
||||||
'%s said: %s' % (self.IE_NAME, error['message']), expected=True)
|
|
||||||
|
|
||||||
video = response['result'].get('0')
|
|
||||||
|
|
||||||
formats = []
|
|
||||||
for _, formats_dict in video['formats'].items():
|
|
||||||
if not isinstance(formats_dict, dict):
|
|
||||||
continue
|
|
||||||
for format_id, format_list in formats_dict.items():
|
|
||||||
if not isinstance(format_list, list):
|
|
||||||
continue
|
|
||||||
for f in format_list:
|
|
||||||
if not f.get('url'):
|
|
||||||
continue
|
|
||||||
formats.append({
|
|
||||||
'url': f['url'],
|
|
||||||
'format_id': format_id,
|
|
||||||
'height': int_or_none(f.get('vertical_resolution')),
|
|
||||||
'width': int_or_none(f.get('horizontal_resolution')),
|
|
||||||
'abr': float_or_none(f.get('audio_bitrate')),
|
|
||||||
'vbr': float_or_none(f.get('video_bitrate')),
|
|
||||||
})
|
|
||||||
self._sort_formats(formats)
|
|
||||||
|
|
||||||
meta = video.get('meta', {})
|
|
||||||
|
|
||||||
title = self._og_search_title(webpage, default=None) or meta['title']
|
|
||||||
description = self._og_search_description(webpage, default=None) or meta.get('description')
|
|
||||||
duration = meta.get('length') or meta.get('lenght')
|
|
||||||
timestamp = parse_iso8601(meta.get('addDate'), ' ')
|
|
||||||
|
|
||||||
return {
|
|
||||||
'id': video_id,
|
|
||||||
'title': title,
|
|
||||||
'description': description,
|
|
||||||
'duration': duration,
|
|
||||||
'timestamp': timestamp,
|
|
||||||
'formats': formats,
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import re
|
|||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import (
|
||||||
compat_parse_qs,
|
compat_parse_qs,
|
||||||
compat_urllib_parse_urlencode,
|
|
||||||
compat_HTTPError,
|
compat_HTTPError,
|
||||||
)
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
@@ -17,37 +16,26 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class CloudyIE(InfoExtractor):
|
class CloudyIE(InfoExtractor):
|
||||||
_IE_DESC = 'cloudy.ec and videoraj.ch'
|
_IE_DESC = 'cloudy.ec'
|
||||||
_VALID_URL = r'''(?x)
|
_VALID_URL = r'''(?x)
|
||||||
https?://(?:www\.)?(?P<host>cloudy\.ec|videoraj\.(?:ch|to))/
|
https?://(?:www\.)?cloudy\.ec/
|
||||||
(?:v/|embed\.php\?id=)
|
(?:v/|embed\.php\?id=)
|
||||||
(?P<id>[A-Za-z0-9]+)
|
(?P<id>[A-Za-z0-9]+)
|
||||||
'''
|
'''
|
||||||
_EMBED_URL = 'http://www.%s/embed.php?id=%s'
|
_EMBED_URL = 'http://www.cloudy.ec/embed.php?id=%s'
|
||||||
_API_URL = 'http://www.%s/api/player.api.php?%s'
|
_API_URL = 'http://www.cloudy.ec/api/player.api.php'
|
||||||
_MAX_TRIES = 2
|
_MAX_TRIES = 2
|
||||||
_TESTS = [
|
_TEST = {
|
||||||
{
|
'url': 'https://www.cloudy.ec/v/af511e2527aac',
|
||||||
'url': 'https://www.cloudy.ec/v/af511e2527aac',
|
'md5': '5cb253ace826a42f35b4740539bedf07',
|
||||||
'md5': '5cb253ace826a42f35b4740539bedf07',
|
'info_dict': {
|
||||||
'info_dict': {
|
'id': 'af511e2527aac',
|
||||||
'id': 'af511e2527aac',
|
'ext': 'flv',
|
||||||
'ext': 'flv',
|
'title': 'Funny Cats and Animals Compilation june 2013',
|
||||||
'title': 'Funny Cats and Animals Compilation june 2013',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'url': 'http://www.videoraj.to/v/47f399fd8bb60',
|
|
||||||
'md5': '7d0f8799d91efd4eda26587421c3c3b0',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '47f399fd8bb60',
|
|
||||||
'ext': 'flv',
|
|
||||||
'title': 'Burning a New iPhone 5 with Gasoline - Will it Survive?',
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
|
|
||||||
def _extract_video(self, video_host, video_id, file_key, error_url=None, try_num=0):
|
def _extract_video(self, video_id, file_key, error_url=None, try_num=0):
|
||||||
|
|
||||||
if try_num > self._MAX_TRIES - 1:
|
if try_num > self._MAX_TRIES - 1:
|
||||||
raise ExtractorError('Unable to extract video URL', expected=True)
|
raise ExtractorError('Unable to extract video URL', expected=True)
|
||||||
@@ -64,9 +52,8 @@ class CloudyIE(InfoExtractor):
|
|||||||
'errorUrl': error_url,
|
'errorUrl': error_url,
|
||||||
})
|
})
|
||||||
|
|
||||||
data_url = self._API_URL % (video_host, compat_urllib_parse_urlencode(form))
|
|
||||||
player_data = self._download_webpage(
|
player_data = self._download_webpage(
|
||||||
data_url, video_id, 'Downloading player data')
|
self._API_URL, video_id, 'Downloading player data', query=form)
|
||||||
data = compat_parse_qs(player_data)
|
data = compat_parse_qs(player_data)
|
||||||
|
|
||||||
try_num += 1
|
try_num += 1
|
||||||
@@ -88,7 +75,7 @@ class CloudyIE(InfoExtractor):
|
|||||||
except ExtractorError as e:
|
except ExtractorError as e:
|
||||||
if isinstance(e.cause, compat_HTTPError) and e.cause.code in [404, 410]:
|
if isinstance(e.cause, compat_HTTPError) and e.cause.code in [404, 410]:
|
||||||
self.report_warning('Invalid video URL, requesting another', video_id)
|
self.report_warning('Invalid video URL, requesting another', video_id)
|
||||||
return self._extract_video(video_host, video_id, file_key, video_url, try_num)
|
return self._extract_video(video_id, file_key, video_url, try_num)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
@@ -98,14 +85,13 @@ class CloudyIE(InfoExtractor):
|
|||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
video_host = mobj.group('host')
|
|
||||||
video_id = mobj.group('id')
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
url = self._EMBED_URL % (video_host, video_id)
|
url = self._EMBED_URL % video_id
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
file_key = self._search_regex(
|
file_key = self._search_regex(
|
||||||
[r'key\s*:\s*"([^"]+)"', r'filekey\s*=\s*"([^"]+)"'],
|
[r'key\s*:\s*"([^"]+)"', r'filekey\s*=\s*"([^"]+)"'],
|
||||||
webpage, 'file_key')
|
webpage, 'file_key')
|
||||||
|
|
||||||
return self._extract_video(video_host, video_id, file_key)
|
return self._extract_video(video_id, file_key)
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .mtv import MTVIE
|
from .mtv import MTVIE
|
||||||
|
from ..utils import ExtractorError
|
||||||
|
|
||||||
|
|
||||||
class CMTIE(MTVIE):
|
class CMTIE(MTVIE):
|
||||||
@@ -16,7 +18,27 @@ class CMTIE(MTVIE):
|
|||||||
'title': 'Garth Brooks - "The Call (featuring Trisha Yearwood)"',
|
'title': 'Garth Brooks - "The Call (featuring Trisha Yearwood)"',
|
||||||
'description': 'Blame It All On My Roots',
|
'description': 'Blame It All On My Roots',
|
||||||
},
|
},
|
||||||
|
'skip': 'Video not available',
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.cmt.com/videos/misc/1504699/still-the-king-ep-109-in-3-minutes.jhtml#id=1739908',
|
||||||
|
'md5': 'e61a801ca4a183a466c08bd98dccbb1c',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '1504699',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Still The King Ep. 109 in 3 Minutes',
|
||||||
|
'description': 'Relive or catch up with Still The King by watching this recap of season 1, episode 9. New episodes Sundays 9/8c.',
|
||||||
|
'timestamp': 1469421000.0,
|
||||||
|
'upload_date': '20160725',
|
||||||
|
},
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.cmt.com/shows/party-down-south/party-down-south-ep-407-gone-girl/1738172/playlist/#id=1738172',
|
'url': 'http://www.cmt.com/shows/party-down-south/party-down-south-ep-407-gone-girl/1738172/playlist/#id=1738172',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _transform_rtmp_url(cls, rtmp_video_url):
|
||||||
|
if 'error_not_available.swf' in rtmp_video_url:
|
||||||
|
raise ExtractorError(
|
||||||
|
'%s said: video is not available' % cls.IE_NAME, expected=True)
|
||||||
|
|
||||||
|
return super(CMTIE, cls)._transform_rtmp_url(rtmp_video_url)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class CNNIE(InfoExtractor):
|
class CNNIE(InfoExtractor):
|
||||||
_VALID_URL = r'''(?x)https?://(?:(?:edition|www)\.)?cnn\.com/video/(?:data/.+?|\?)/
|
_VALID_URL = r'''(?x)https?://(?:(?P<sub_domain>edition|www|money)\.)?cnn\.com/(?:video/(?:data/.+?|\?)/)?videos?/
|
||||||
(?P<path>.+?/(?P<title>[^/]+?)(?:\.(?:[a-z\-]+)|(?=&)))'''
|
(?P<path>.+?/(?P<title>[^/]+?)(?:\.(?:[a-z\-]+)|(?=&)))'''
|
||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
@@ -45,19 +45,46 @@ class CNNIE(InfoExtractor):
|
|||||||
'description': 'md5:e7223a503315c9f150acac52e76de086',
|
'description': 'md5:e7223a503315c9f150acac52e76de086',
|
||||||
'upload_date': '20141222',
|
'upload_date': '20141222',
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
'url': 'http://money.cnn.com/video/news/2016/08/19/netflix-stunning-stats.cnnmoney/index.html',
|
||||||
|
'md5': '52a515dc1b0f001cd82e4ceda32be9d1',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '/video/news/2016/08/19/netflix-stunning-stats.cnnmoney',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': '5 stunning stats about Netflix',
|
||||||
|
'description': 'Did you know that Netflix has more than 80 million members? Here are five facts about the online video distributor that you probably didn\'t know.',
|
||||||
|
'upload_date': '20160819',
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://cnn.com/video/?/video/politics/2015/03/27/pkg-arizona-senator-church-attendance-mandatory.ktvk',
|
'url': 'http://cnn.com/video/?/video/politics/2015/03/27/pkg-arizona-senator-church-attendance-mandatory.ktvk',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://cnn.com/video/?/video/us/2015/04/06/dnt-baker-refuses-anti-gay-order.wkmg',
|
'url': 'http://cnn.com/video/?/video/us/2015/04/06/dnt-baker-refuses-anti-gay-order.wkmg',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://edition.cnn.com/videos/arts/2016/04/21/olympic-games-cultural-a-z-brazil.cnn',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
|
_CONFIG = {
|
||||||
|
# http://edition.cnn.com/.element/apps/cvp/3.0/cfg/spider/cnn/expansion/config.xml
|
||||||
|
'edition': {
|
||||||
|
'data_src': 'http://edition.cnn.com/video/data/3.0/video/%s/index.xml',
|
||||||
|
'media_src': 'http://pmd.cdn.turner.com/cnn/big',
|
||||||
|
},
|
||||||
|
# http://money.cnn.com/.element/apps/cvp2/cfg/config.xml
|
||||||
|
'money': {
|
||||||
|
'data_src': 'http://money.cnn.com/video/data/4.0/video/%s.xml',
|
||||||
|
'media_src': 'http://ht3.cdn.turner.com/money/big',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
sub_domain, path, page_title = re.match(self._VALID_URL, url).groups()
|
||||||
path = mobj.group('path')
|
if sub_domain not in ('money', 'edition'):
|
||||||
page_title = mobj.group('title')
|
sub_domain = 'edition'
|
||||||
info_url = 'http://edition.cnn.com/video/data/3.0/%s/index.xml' % path
|
config = self._CONFIG[sub_domain]
|
||||||
|
info_url = config['data_src'] % path
|
||||||
info = self._download_xml(info_url, page_title)
|
info = self._download_xml(info_url, page_title)
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
@@ -66,7 +93,7 @@ class CNNIE(InfoExtractor):
|
|||||||
(?:_(?P<bitrate>[0-9]+)k)?
|
(?:_(?P<bitrate>[0-9]+)k)?
|
||||||
''')
|
''')
|
||||||
for f in info.findall('files/file'):
|
for f in info.findall('files/file'):
|
||||||
video_url = 'http://ht.cdn.turner.com/cnn/big%s' % (f.text.strip())
|
video_url = config['media_src'] + f.text.strip()
|
||||||
fdct = {
|
fdct = {
|
||||||
'format_id': f.attrib['bitrate'],
|
'format_id': f.attrib['bitrate'],
|
||||||
'url': video_url,
|
'url': video_url,
|
||||||
@@ -146,7 +173,7 @@ class CNNBlogsIE(InfoExtractor):
|
|||||||
|
|
||||||
|
|
||||||
class CNNArticleIE(InfoExtractor):
|
class CNNArticleIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:(?:edition|www)\.)?cnn\.com/(?!video/)'
|
_VALID_URL = r'https?://(?:(?:edition|www)\.)?cnn\.com/(?!videos?/)'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.cnn.com/2014/12/21/politics/obama-north-koreas-hack-not-war-but-cyber-vandalism/',
|
'url': 'http://www.cnn.com/2014/12/21/politics/obama-north-koreas-hack-not-war-but-cyber-vandalism/',
|
||||||
'md5': '689034c2a3d9c6dc4aa72d65a81efd01',
|
'md5': '689034c2a3d9c6dc4aa72d65a81efd01',
|
||||||
|
|||||||
@@ -1,17 +1,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .mtv import MTVServicesInfoExtractor
|
from .mtv import MTVServicesInfoExtractor
|
||||||
from ..compat import (
|
from .common import InfoExtractor
|
||||||
compat_str,
|
|
||||||
compat_urllib_parse_urlencode,
|
|
||||||
)
|
|
||||||
from ..utils import (
|
|
||||||
ExtractorError,
|
|
||||||
float_or_none,
|
|
||||||
unified_strdate,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ComedyCentralIE(MTVServicesInfoExtractor):
|
class ComedyCentralIE(MTVServicesInfoExtractor):
|
||||||
@@ -26,8 +16,10 @@ class ComedyCentralIE(MTVServicesInfoExtractor):
|
|||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'cef0cbb3-e776-4bc9-b62e-8016deccb354',
|
'id': 'cef0cbb3-e776-4bc9-b62e-8016deccb354',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'CC:Stand-Up|Greg Fitzsimmons: Life on Stage|Uncensored - Too Good of a Mother',
|
'title': 'CC:Stand-Up|August 18, 2013|1|0101|Uncensored - Too Good of a Mother',
|
||||||
'description': 'After a certain point, breastfeeding becomes c**kblocking.',
|
'description': 'After a certain point, breastfeeding becomes c**kblocking.',
|
||||||
|
'timestamp': 1376798400,
|
||||||
|
'upload_date': '20130818',
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.cc.com/shows/the-daily-show-with-trevor-noah/interviews/6yx39d/exclusive-rand-paul-extended-interview',
|
'url': 'http://www.cc.com/shows/the-daily-show-with-trevor-noah/interviews/6yx39d/exclusive-rand-paul-extended-interview',
|
||||||
@@ -35,241 +27,92 @@ class ComedyCentralIE(MTVServicesInfoExtractor):
|
|||||||
}]
|
}]
|
||||||
|
|
||||||
|
|
||||||
class ComedyCentralShowsIE(MTVServicesInfoExtractor):
|
class ToshIE(MTVServicesInfoExtractor):
|
||||||
IE_DESC = 'The Daily Show / The Colbert Report'
|
IE_DESC = 'Tosh.0'
|
||||||
# urls can be abbreviations like :thedailyshow
|
_VALID_URL = r'^https?://tosh\.cc\.com/video-(?:clips|collections)/[^/]+/(?P<videotitle>[^/?#]+)'
|
||||||
# urls for episodes like:
|
_FEED_URL = 'http://tosh.cc.com/feeds/mrss'
|
||||||
# or urls for clips like: http://www.thedailyshow.com/watch/mon-december-10-2012/any-given-gun-day
|
|
||||||
# or: http://www.colbertnation.com/the-colbert-report-videos/421667/november-29-2012/moon-shattering-news
|
|
||||||
# or: http://www.colbertnation.com/the-colbert-report-collections/422008/festival-of-lights/79524
|
|
||||||
_VALID_URL = r'''(?x)^(:(?P<shortname>tds|thedailyshow)
|
|
||||||
|https?://(:www\.)?
|
|
||||||
(?P<showname>thedailyshow|thecolbertreport|tosh)\.(?:cc\.)?com/
|
|
||||||
((?:full-)?episodes/(?:[0-9a-z]{6}/)?(?P<episode>.*)|
|
|
||||||
(?P<clip>
|
|
||||||
(?:(?:guests/[^/]+|videos|video-(?:clips|playlists)|special-editions|news-team/[^/]+)/[^/]+/(?P<videotitle>[^/?#]+))
|
|
||||||
|(the-colbert-report-(videos|collections)/(?P<clipID>[0-9]+)/[^/]*/(?P<cntitle>.*?))
|
|
||||||
|(watch/(?P<date>[^/]*)/(?P<tdstitle>.*))
|
|
||||||
)|
|
|
||||||
(?P<interview>
|
|
||||||
extended-interviews/(?P<interID>[0-9a-z]+)/
|
|
||||||
(?:playlist_tds_extended_)?(?P<interview_title>[^/?#]*?)
|
|
||||||
(?:/[^/?#]?|[?#]|$))))
|
|
||||||
'''
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://thedailyshow.cc.com/watch/thu-december-13-2012/kristen-stewart',
|
|
||||||
'md5': '4e2f5cb088a83cd8cdb7756132f9739d',
|
|
||||||
'info_dict': {
|
|
||||||
'id': 'ab9ab3e7-5a98-4dbe-8b21-551dc0523d55',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'upload_date': '20121213',
|
|
||||||
'description': 'Kristen Stewart learns to let loose in "On the Road."',
|
|
||||||
'uploader': 'thedailyshow',
|
|
||||||
'title': 'thedailyshow kristen-stewart part 1',
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
'url': 'http://thedailyshow.cc.com/extended-interviews/b6364d/sarah-chayes-extended-interview',
|
|
||||||
'info_dict': {
|
|
||||||
'id': 'sarah-chayes-extended-interview',
|
|
||||||
'description': 'Carnegie Endowment Senior Associate Sarah Chayes discusses how corrupt institutions function throughout the world in her book "Thieves of State: Why Corruption Threatens Global Security."',
|
|
||||||
'title': 'thedailyshow Sarah Chayes Extended Interview',
|
|
||||||
},
|
|
||||||
'playlist': [
|
|
||||||
{
|
|
||||||
'info_dict': {
|
|
||||||
'id': '0baad492-cbec-4ec1-9e50-ad91c291127f',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'upload_date': '20150129',
|
|
||||||
'description': 'Carnegie Endowment Senior Associate Sarah Chayes discusses how corrupt institutions function throughout the world in her book "Thieves of State: Why Corruption Threatens Global Security."',
|
|
||||||
'uploader': 'thedailyshow',
|
|
||||||
'title': 'thedailyshow sarah-chayes-extended-interview part 1',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'info_dict': {
|
|
||||||
'id': '1e4fb91b-8ce7-4277-bd7c-98c9f1bbd283',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'upload_date': '20150129',
|
|
||||||
'description': 'Carnegie Endowment Senior Associate Sarah Chayes discusses how corrupt institutions function throughout the world in her book "Thieves of State: Why Corruption Threatens Global Security."',
|
|
||||||
'uploader': 'thedailyshow',
|
|
||||||
'title': 'thedailyshow sarah-chayes-extended-interview part 2',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'params': {
|
|
||||||
'skip_download': True,
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
'url': 'http://thedailyshow.cc.com/extended-interviews/xm3fnq/andrew-napolitano-extended-interview',
|
|
||||||
'only_matching': True,
|
|
||||||
}, {
|
|
||||||
'url': 'http://thecolbertreport.cc.com/videos/29w6fx/-realhumanpraise-for-fox-news',
|
|
||||||
'only_matching': True,
|
|
||||||
}, {
|
|
||||||
'url': 'http://thecolbertreport.cc.com/videos/gh6urb/neil-degrasse-tyson-pt--1?xrs=eml_col_031114',
|
|
||||||
'only_matching': True,
|
|
||||||
}, {
|
|
||||||
'url': 'http://thedailyshow.cc.com/guests/michael-lewis/3efna8/exclusive---michael-lewis-extended-interview-pt--3',
|
|
||||||
'only_matching': True,
|
|
||||||
}, {
|
|
||||||
'url': 'http://thedailyshow.cc.com/episodes/sy7yv0/april-8--2014---denis-leary',
|
|
||||||
'only_matching': True,
|
|
||||||
}, {
|
|
||||||
'url': 'http://thecolbertreport.cc.com/episodes/8ase07/april-8--2014---jane-goodall',
|
|
||||||
'only_matching': True,
|
|
||||||
}, {
|
|
||||||
'url': 'http://thedailyshow.cc.com/video-playlists/npde3s/the-daily-show-19088-highlights',
|
|
||||||
'only_matching': True,
|
|
||||||
}, {
|
|
||||||
'url': 'http://thedailyshow.cc.com/video-playlists/t6d9sg/the-daily-show-20038-highlights/be3cwo',
|
|
||||||
'only_matching': True,
|
|
||||||
}, {
|
|
||||||
'url': 'http://thedailyshow.cc.com/special-editions/2l8fdb/special-edition---a-look-back-at-food',
|
|
||||||
'only_matching': True,
|
|
||||||
}, {
|
|
||||||
'url': 'http://thedailyshow.cc.com/news-team/michael-che/7wnfel/we-need-to-talk-about-israel',
|
|
||||||
'only_matching': True,
|
|
||||||
}, {
|
|
||||||
'url': 'http://tosh.cc.com/video-clips/68g93d/twitter-users-share-summer-plans',
|
'url': 'http://tosh.cc.com/video-clips/68g93d/twitter-users-share-summer-plans',
|
||||||
|
'info_dict': {
|
||||||
|
'description': 'Tosh asked fans to share their summer plans.',
|
||||||
|
'title': 'Twitter Users Share Summer Plans',
|
||||||
|
},
|
||||||
|
'playlist': [{
|
||||||
|
'md5': 'f269e88114c1805bb6d7653fecea9e06',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '90498ec2-ed00-11e0-aca6-0026b9414f30',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Tosh.0|June 9, 2077|2|211|Twitter Users Share Summer Plans',
|
||||||
|
'description': 'Tosh asked fans to share their summer plans.',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg',
|
||||||
|
# It's really reported to be published on year 2077
|
||||||
|
'upload_date': '20770610',
|
||||||
|
'timestamp': 3390510600,
|
||||||
|
'subtitles': {
|
||||||
|
'en': 'mincount:3',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
'url': 'http://tosh.cc.com/video-collections/x2iz7k/just-plain-foul/m5q4fp',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
_available_formats = ['3500', '2200', '1700', '1200', '750', '400']
|
@classmethod
|
||||||
|
def _transform_rtmp_url(cls, rtmp_video_url):
|
||||||
|
new_urls = super(ToshIE, cls)._transform_rtmp_url(rtmp_video_url)
|
||||||
|
new_urls['rtmp'] = rtmp_video_url.replace('viacomccstrm', 'viacommtvstrm')
|
||||||
|
return new_urls
|
||||||
|
|
||||||
_video_extensions = {
|
|
||||||
'3500': 'mp4',
|
class ComedyCentralTVIE(MTVServicesInfoExtractor):
|
||||||
'2200': 'mp4',
|
_VALID_URL = r'https?://(?:www\.)?comedycentral\.tv/(?:staffeln|shows)/(?P<id>[^/?#&]+)'
|
||||||
'1700': 'mp4',
|
_TESTS = [{
|
||||||
'1200': 'mp4',
|
'url': 'http://www.comedycentral.tv/staffeln/7436-the-mindy-project-staffel-4',
|
||||||
'750': 'mp4',
|
'info_dict': {
|
||||||
'400': 'mp4',
|
'id': 'local_playlist-f99b626bdfe13568579a',
|
||||||
}
|
'ext': 'flv',
|
||||||
_video_dimensions = {
|
'title': 'Episode_the-mindy-project_shows_season-4_episode-3_full-episode_part1',
|
||||||
'3500': (1280, 720),
|
},
|
||||||
'2200': (960, 540),
|
'params': {
|
||||||
'1700': (768, 432),
|
# rtmp download
|
||||||
'1200': (640, 360),
|
'skip_download': True,
|
||||||
'750': (512, 288),
|
},
|
||||||
'400': (384, 216),
|
}, {
|
||||||
}
|
'url': 'http://www.comedycentral.tv/shows/1074-workaholics',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.comedycentral.tv/shows/1727-the-mindy-project/bonus',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
if mobj.group('shortname'):
|
webpage = self._download_webpage(url, video_id)
|
||||||
return self.url_result('http://www.cc.com/shows/the-daily-show-with-trevor-noah/full-episodes')
|
|
||||||
|
|
||||||
if mobj.group('clip'):
|
mrss_url = self._search_regex(
|
||||||
if mobj.group('videotitle'):
|
r'data-mrss=(["\'])(?P<url>(?:(?!\1).)+)\1',
|
||||||
epTitle = mobj.group('videotitle')
|
webpage, 'mrss url', group='url')
|
||||||
elif mobj.group('showname') == 'thedailyshow':
|
|
||||||
epTitle = mobj.group('tdstitle')
|
|
||||||
else:
|
|
||||||
epTitle = mobj.group('cntitle')
|
|
||||||
dlNewest = False
|
|
||||||
elif mobj.group('interview'):
|
|
||||||
epTitle = mobj.group('interview_title')
|
|
||||||
dlNewest = False
|
|
||||||
else:
|
|
||||||
dlNewest = not mobj.group('episode')
|
|
||||||
if dlNewest:
|
|
||||||
epTitle = mobj.group('showname')
|
|
||||||
else:
|
|
||||||
epTitle = mobj.group('episode')
|
|
||||||
show_name = mobj.group('showname')
|
|
||||||
|
|
||||||
webpage, htmlHandle = self._download_webpage_handle(url, epTitle)
|
return self._get_videos_info_from_url(mrss_url, video_id)
|
||||||
if dlNewest:
|
|
||||||
url = htmlHandle.geturl()
|
|
||||||
mobj = re.match(self._VALID_URL, url, re.VERBOSE)
|
|
||||||
if mobj is None:
|
|
||||||
raise ExtractorError('Invalid redirected URL: ' + url)
|
|
||||||
if mobj.group('episode') == '':
|
|
||||||
raise ExtractorError('Redirected URL is still not specific: ' + url)
|
|
||||||
epTitle = (mobj.group('episode') or mobj.group('videotitle')).rpartition('/')[-1]
|
|
||||||
|
|
||||||
mMovieParams = re.findall('(?:<param name="movie" value="|var url = ")(http://media.mtvnservices.com/([^"]*(?:episode|video).*?:.*?))"', webpage)
|
|
||||||
if len(mMovieParams) == 0:
|
|
||||||
# The Colbert Report embeds the information in a without
|
|
||||||
# a URL prefix; so extract the alternate reference
|
|
||||||
# and then add the URL prefix manually.
|
|
||||||
|
|
||||||
altMovieParams = re.findall('data-mgid="([^"]*(?:episode|video|playlist).*?:.*?)"', webpage)
|
class ComedyCentralShortnameIE(InfoExtractor):
|
||||||
if len(altMovieParams) == 0:
|
_VALID_URL = r'^:(?P<id>tds|thedailyshow)$'
|
||||||
raise ExtractorError('unable to find Flash URL in webpage ' + url)
|
_TESTS = [{
|
||||||
else:
|
'url': ':tds',
|
||||||
mMovieParams = [('http://media.mtvnservices.com/' + altMovieParams[0], altMovieParams[0])]
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': ':thedailyshow',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
uri = mMovieParams[0][1]
|
def _real_extract(self, url):
|
||||||
# Correct cc.com in uri
|
video_id = self._match_id(url)
|
||||||
uri = re.sub(r'(episode:[^.]+)(\.cc)?\.com', r'\1.com', uri)
|
shortcut_map = {
|
||||||
|
'tds': 'http://www.cc.com/shows/the-daily-show-with-trevor-noah/full-episodes',
|
||||||
index_url = 'http://%s.cc.com/feeds/mrss?%s' % (show_name, compat_urllib_parse_urlencode({'uri': uri}))
|
'thedailyshow': 'http://www.cc.com/shows/the-daily-show-with-trevor-noah/full-episodes',
|
||||||
idoc = self._download_xml(
|
|
||||||
index_url, epTitle,
|
|
||||||
'Downloading show index', 'Unable to download episode index')
|
|
||||||
|
|
||||||
title = idoc.find('./channel/title').text
|
|
||||||
description = idoc.find('./channel/description').text
|
|
||||||
|
|
||||||
entries = []
|
|
||||||
item_els = idoc.findall('.//item')
|
|
||||||
for part_num, itemEl in enumerate(item_els):
|
|
||||||
upload_date = unified_strdate(itemEl.findall('./pubDate')[0].text)
|
|
||||||
thumbnail = itemEl.find('.//{http://search.yahoo.com/mrss/}thumbnail').attrib.get('url')
|
|
||||||
|
|
||||||
content = itemEl.find('.//{http://search.yahoo.com/mrss/}content')
|
|
||||||
duration = float_or_none(content.attrib.get('duration'))
|
|
||||||
mediagen_url = content.attrib['url']
|
|
||||||
guid = itemEl.find('./guid').text.rpartition(':')[-1]
|
|
||||||
|
|
||||||
cdoc = self._download_xml(
|
|
||||||
mediagen_url, epTitle,
|
|
||||||
'Downloading configuration for segment %d / %d' % (part_num + 1, len(item_els)))
|
|
||||||
|
|
||||||
turls = []
|
|
||||||
for rendition in cdoc.findall('.//rendition'):
|
|
||||||
finfo = (rendition.attrib['bitrate'], rendition.findall('./src')[0].text)
|
|
||||||
turls.append(finfo)
|
|
||||||
|
|
||||||
formats = []
|
|
||||||
for format, rtmp_video_url in turls:
|
|
||||||
w, h = self._video_dimensions.get(format, (None, None))
|
|
||||||
formats.append({
|
|
||||||
'format_id': 'vhttp-%s' % format,
|
|
||||||
'url': self._transform_rtmp_url(rtmp_video_url),
|
|
||||||
'ext': self._video_extensions.get(format, 'mp4'),
|
|
||||||
'height': h,
|
|
||||||
'width': w,
|
|
||||||
})
|
|
||||||
formats.append({
|
|
||||||
'format_id': 'rtmp-%s' % format,
|
|
||||||
'url': rtmp_video_url.replace('viacomccstrm', 'viacommtvstrm'),
|
|
||||||
'ext': self._video_extensions.get(format, 'mp4'),
|
|
||||||
'height': h,
|
|
||||||
'width': w,
|
|
||||||
})
|
|
||||||
self._sort_formats(formats)
|
|
||||||
|
|
||||||
subtitles = self._extract_subtitles(cdoc, guid)
|
|
||||||
|
|
||||||
virtual_id = show_name + ' ' + epTitle + ' part ' + compat_str(part_num + 1)
|
|
||||||
entries.append({
|
|
||||||
'id': guid,
|
|
||||||
'title': virtual_id,
|
|
||||||
'formats': formats,
|
|
||||||
'uploader': show_name,
|
|
||||||
'upload_date': upload_date,
|
|
||||||
'duration': duration,
|
|
||||||
'thumbnail': thumbnail,
|
|
||||||
'description': description,
|
|
||||||
'subtitles': subtitles,
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
'_type': 'playlist',
|
|
||||||
'id': epTitle,
|
|
||||||
'entries': entries,
|
|
||||||
'title': show_name + ' ' + title,
|
|
||||||
'description': description,
|
|
||||||
}
|
}
|
||||||
|
return self.url_result(shortcut_map[video_id])
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ from ..utils import (
|
|||||||
sanitized_Request,
|
sanitized_Request,
|
||||||
unescapeHTML,
|
unescapeHTML,
|
||||||
unified_strdate,
|
unified_strdate,
|
||||||
|
unified_timestamp,
|
||||||
url_basename,
|
url_basename,
|
||||||
xpath_element,
|
xpath_element,
|
||||||
xpath_text,
|
xpath_text,
|
||||||
@@ -54,6 +55,8 @@ from ..utils import (
|
|||||||
update_Request,
|
update_Request,
|
||||||
update_url_query,
|
update_url_query,
|
||||||
parse_m3u8_attributes,
|
parse_m3u8_attributes,
|
||||||
|
extract_attributes,
|
||||||
|
parse_codecs,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -161,6 +164,7 @@ class InfoExtractor(object):
|
|||||||
* "height" (optional, int)
|
* "height" (optional, int)
|
||||||
* "resolution" (optional, string "{width}x{height"},
|
* "resolution" (optional, string "{width}x{height"},
|
||||||
deprecated)
|
deprecated)
|
||||||
|
* "filesize" (optional, int)
|
||||||
thumbnail: Full URL to a video thumbnail image.
|
thumbnail: Full URL to a video thumbnail image.
|
||||||
description: Full video description.
|
description: Full video description.
|
||||||
uploader: Full name of the video uploader.
|
uploader: Full name of the video uploader.
|
||||||
@@ -658,6 +662,24 @@ class InfoExtractor(object):
|
|||||||
else:
|
else:
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
def _get_netrc_login_info(self, netrc_machine=None):
|
||||||
|
username = None
|
||||||
|
password = None
|
||||||
|
netrc_machine = netrc_machine or self._NETRC_MACHINE
|
||||||
|
|
||||||
|
if self._downloader.params.get('usenetrc', False):
|
||||||
|
try:
|
||||||
|
info = netrc.netrc().authenticators(netrc_machine)
|
||||||
|
if info is not None:
|
||||||
|
username = info[0]
|
||||||
|
password = info[2]
|
||||||
|
else:
|
||||||
|
raise netrc.NetrcParseError('No authenticators for %s' % netrc_machine)
|
||||||
|
except (IOError, netrc.NetrcParseError) as err:
|
||||||
|
self._downloader.report_warning('parsing .netrc: %s' % error_to_compat_str(err))
|
||||||
|
|
||||||
|
return (username, password)
|
||||||
|
|
||||||
def _get_login_info(self):
|
def _get_login_info(self):
|
||||||
"""
|
"""
|
||||||
Get the login info as (username, password)
|
Get the login info as (username, password)
|
||||||
@@ -675,16 +697,8 @@ class InfoExtractor(object):
|
|||||||
if downloader_params.get('username') is not None:
|
if downloader_params.get('username') is not None:
|
||||||
username = downloader_params['username']
|
username = downloader_params['username']
|
||||||
password = downloader_params['password']
|
password = downloader_params['password']
|
||||||
elif downloader_params.get('usenetrc', False):
|
else:
|
||||||
try:
|
username, password = self._get_netrc_login_info()
|
||||||
info = netrc.netrc().authenticators(self._NETRC_MACHINE)
|
|
||||||
if info is not None:
|
|
||||||
username = info[0]
|
|
||||||
password = info[2]
|
|
||||||
else:
|
|
||||||
raise netrc.NetrcParseError('No authenticators for %s' % self._NETRC_MACHINE)
|
|
||||||
except (IOError, netrc.NetrcParseError) as err:
|
|
||||||
self._downloader.report_warning('parsing .netrc: %s' % error_to_compat_str(err))
|
|
||||||
|
|
||||||
return (username, password)
|
return (username, password)
|
||||||
|
|
||||||
@@ -723,9 +737,14 @@ class InfoExtractor(object):
|
|||||||
[^>]+?content=(["\'])(?P<content>.*?)\2''' % re.escape(prop)
|
[^>]+?content=(["\'])(?P<content>.*?)\2''' % re.escape(prop)
|
||||||
|
|
||||||
def _og_search_property(self, prop, html, name=None, **kargs):
|
def _og_search_property(self, prop, html, name=None, **kargs):
|
||||||
|
if not isinstance(prop, (list, tuple)):
|
||||||
|
prop = [prop]
|
||||||
if name is None:
|
if name is None:
|
||||||
name = 'OpenGraph %s' % prop
|
name = 'OpenGraph %s' % prop[0]
|
||||||
escaped = self._search_regex(self._og_regexes(prop), html, name, flags=re.DOTALL, **kargs)
|
og_regexes = []
|
||||||
|
for p in prop:
|
||||||
|
og_regexes.extend(self._og_regexes(p))
|
||||||
|
escaped = self._search_regex(og_regexes, html, name, flags=re.DOTALL, **kargs)
|
||||||
if escaped is None:
|
if escaped is None:
|
||||||
return None
|
return None
|
||||||
return unescapeHTML(escaped)
|
return unescapeHTML(escaped)
|
||||||
@@ -803,40 +822,66 @@ class InfoExtractor(object):
|
|||||||
return self._html_search_meta('twitter:player', html,
|
return self._html_search_meta('twitter:player', html,
|
||||||
'twitter card player')
|
'twitter card player')
|
||||||
|
|
||||||
def _search_json_ld(self, html, video_id, **kwargs):
|
def _search_json_ld(self, html, video_id, expected_type=None, **kwargs):
|
||||||
json_ld = self._search_regex(
|
json_ld = self._search_regex(
|
||||||
r'(?s)<script[^>]+type=(["\'])application/ld\+json\1[^>]*>(?P<json_ld>.+?)</script>',
|
r'(?s)<script[^>]+type=(["\'])application/ld\+json\1[^>]*>(?P<json_ld>.+?)</script>',
|
||||||
html, 'JSON-LD', group='json_ld', **kwargs)
|
html, 'JSON-LD', group='json_ld', **kwargs)
|
||||||
|
default = kwargs.get('default', NO_DEFAULT)
|
||||||
if not json_ld:
|
if not json_ld:
|
||||||
return {}
|
return default if default is not NO_DEFAULT else {}
|
||||||
return self._json_ld(json_ld, video_id, fatal=kwargs.get('fatal', True))
|
# JSON-LD may be malformed and thus `fatal` should be respected.
|
||||||
|
# At the same time `default` may be passed that assumes `fatal=False`
|
||||||
|
# for _search_regex. Let's simulate the same behavior here as well.
|
||||||
|
fatal = kwargs.get('fatal', True) if default == NO_DEFAULT else False
|
||||||
|
return self._json_ld(json_ld, video_id, fatal=fatal, expected_type=expected_type)
|
||||||
|
|
||||||
def _json_ld(self, json_ld, video_id, fatal=True):
|
def _json_ld(self, json_ld, video_id, fatal=True, expected_type=None):
|
||||||
if isinstance(json_ld, compat_str):
|
if isinstance(json_ld, compat_str):
|
||||||
json_ld = self._parse_json(json_ld, video_id, fatal=fatal)
|
json_ld = self._parse_json(json_ld, video_id, fatal=fatal)
|
||||||
if not json_ld:
|
if not json_ld:
|
||||||
return {}
|
return {}
|
||||||
info = {}
|
info = {}
|
||||||
if json_ld.get('@context') == 'http://schema.org':
|
if not isinstance(json_ld, (list, tuple, dict)):
|
||||||
item_type = json_ld.get('@type')
|
return info
|
||||||
if item_type == 'TVEpisode':
|
if isinstance(json_ld, dict):
|
||||||
info.update({
|
json_ld = [json_ld]
|
||||||
'episode': unescapeHTML(json_ld.get('name')),
|
for e in json_ld:
|
||||||
'episode_number': int_or_none(json_ld.get('episodeNumber')),
|
if e.get('@context') == 'http://schema.org':
|
||||||
'description': unescapeHTML(json_ld.get('description')),
|
item_type = e.get('@type')
|
||||||
})
|
if expected_type is not None and expected_type != item_type:
|
||||||
part_of_season = json_ld.get('partOfSeason')
|
return info
|
||||||
if isinstance(part_of_season, dict) and part_of_season.get('@type') == 'TVSeason':
|
if item_type == 'TVEpisode':
|
||||||
info['season_number'] = int_or_none(part_of_season.get('seasonNumber'))
|
info.update({
|
||||||
part_of_series = json_ld.get('partOfSeries')
|
'episode': unescapeHTML(e.get('name')),
|
||||||
if isinstance(part_of_series, dict) and part_of_series.get('@type') == 'TVSeries':
|
'episode_number': int_or_none(e.get('episodeNumber')),
|
||||||
info['series'] = unescapeHTML(part_of_series.get('name'))
|
'description': unescapeHTML(e.get('description')),
|
||||||
elif item_type == 'Article':
|
})
|
||||||
info.update({
|
part_of_season = e.get('partOfSeason')
|
||||||
'timestamp': parse_iso8601(json_ld.get('datePublished')),
|
if isinstance(part_of_season, dict) and part_of_season.get('@type') == 'TVSeason':
|
||||||
'title': unescapeHTML(json_ld.get('headline')),
|
info['season_number'] = int_or_none(part_of_season.get('seasonNumber'))
|
||||||
'description': unescapeHTML(json_ld.get('articleBody')),
|
part_of_series = e.get('partOfSeries') or e.get('partOfTVSeries')
|
||||||
})
|
if isinstance(part_of_series, dict) and part_of_series.get('@type') == 'TVSeries':
|
||||||
|
info['series'] = unescapeHTML(part_of_series.get('name'))
|
||||||
|
elif item_type == 'Article':
|
||||||
|
info.update({
|
||||||
|
'timestamp': parse_iso8601(e.get('datePublished')),
|
||||||
|
'title': unescapeHTML(e.get('headline')),
|
||||||
|
'description': unescapeHTML(e.get('articleBody')),
|
||||||
|
})
|
||||||
|
elif item_type == 'VideoObject':
|
||||||
|
info.update({
|
||||||
|
'url': e.get('contentUrl'),
|
||||||
|
'title': unescapeHTML(e.get('name')),
|
||||||
|
'description': unescapeHTML(e.get('description')),
|
||||||
|
'thumbnail': e.get('thumbnailUrl'),
|
||||||
|
'duration': parse_duration(e.get('duration')),
|
||||||
|
'timestamp': unified_timestamp(e.get('uploadDate')),
|
||||||
|
'filesize': float_or_none(e.get('contentSize')),
|
||||||
|
'tbr': int_or_none(e.get('bitrate')),
|
||||||
|
'width': int_or_none(e.get('width')),
|
||||||
|
'height': int_or_none(e.get('height')),
|
||||||
|
})
|
||||||
|
break
|
||||||
return dict((k, v) for k, v in info.items() if v is not None)
|
return dict((k, v) for k, v in info.items() if v is not None)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -890,7 +935,8 @@ class InfoExtractor(object):
|
|||||||
if f.get('ext') in ['f4f', 'f4m']: # Not yet supported
|
if f.get('ext') in ['f4f', 'f4m']: # Not yet supported
|
||||||
preference -= 0.5
|
preference -= 0.5
|
||||||
|
|
||||||
proto_preference = 0 if determine_protocol(f) in ['http', 'https'] else -0.1
|
protocol = f.get('protocol') or determine_protocol(f)
|
||||||
|
proto_preference = 0 if protocol in ['http', 'https'] else (-0.5 if protocol == 'rtsp' else -0.1)
|
||||||
|
|
||||||
if f.get('vcodec') == 'none': # audio only
|
if f.get('vcodec') == 'none': # audio only
|
||||||
preference -= 50
|
preference -= 50
|
||||||
@@ -1107,7 +1153,7 @@ class InfoExtractor(object):
|
|||||||
'url': m3u8_url,
|
'url': m3u8_url,
|
||||||
'ext': ext,
|
'ext': ext,
|
||||||
'protocol': 'm3u8',
|
'protocol': 'm3u8',
|
||||||
'preference': preference - 1 if preference else -1,
|
'preference': preference - 100 if preference else -100,
|
||||||
'resolution': 'multiple',
|
'resolution': 'multiple',
|
||||||
'format_note': 'Quality selection URL',
|
'format_note': 'Quality selection URL',
|
||||||
}
|
}
|
||||||
@@ -1186,6 +1232,7 @@ class InfoExtractor(object):
|
|||||||
'url': format_url(line.strip()),
|
'url': format_url(line.strip()),
|
||||||
'tbr': tbr,
|
'tbr': tbr,
|
||||||
'ext': ext,
|
'ext': ext,
|
||||||
|
'fps': float_or_none(last_info.get('FRAME-RATE')),
|
||||||
'protocol': entry_protocol,
|
'protocol': entry_protocol,
|
||||||
'preference': preference,
|
'preference': preference,
|
||||||
}
|
}
|
||||||
@@ -1194,24 +1241,17 @@ class InfoExtractor(object):
|
|||||||
width_str, height_str = resolution.split('x')
|
width_str, height_str = resolution.split('x')
|
||||||
f['width'] = int(width_str)
|
f['width'] = int(width_str)
|
||||||
f['height'] = int(height_str)
|
f['height'] = int(height_str)
|
||||||
codecs = last_info.get('CODECS')
|
# Unified Streaming Platform
|
||||||
if codecs:
|
mobj = re.search(
|
||||||
vcodec, acodec = [None] * 2
|
r'audio.*?(?:%3D|=)(\d+)(?:-video.*?(?:%3D|=)(\d+))?', f['url'])
|
||||||
va_codecs = codecs.split(',')
|
if mobj:
|
||||||
if len(va_codecs) == 1:
|
abr, vbr = mobj.groups()
|
||||||
# Audio only entries usually come with single codec and
|
abr, vbr = float_or_none(abr, 1000), float_or_none(vbr, 1000)
|
||||||
# no resolution. For more robustness we also check it to
|
|
||||||
# be mp4 audio.
|
|
||||||
if not resolution and va_codecs[0].startswith('mp4a'):
|
|
||||||
vcodec, acodec = 'none', va_codecs[0]
|
|
||||||
else:
|
|
||||||
vcodec = va_codecs[0]
|
|
||||||
else:
|
|
||||||
vcodec, acodec = va_codecs[:2]
|
|
||||||
f.update({
|
f.update({
|
||||||
'acodec': acodec,
|
'vbr': vbr,
|
||||||
'vcodec': vcodec,
|
'abr': abr,
|
||||||
})
|
})
|
||||||
|
f.update(parse_codecs(last_info.get('CODECS')))
|
||||||
if last_media is not None:
|
if last_media is not None:
|
||||||
f['m3u8_media'] = last_media
|
f['m3u8_media'] = last_media
|
||||||
last_media = None
|
last_media = None
|
||||||
@@ -1466,6 +1506,13 @@ class InfoExtractor(object):
|
|||||||
compat_etree_fromstring(mpd.encode('utf-8')), mpd_id, mpd_base_url, formats_dict=formats_dict)
|
compat_etree_fromstring(mpd.encode('utf-8')), mpd_id, mpd_base_url, formats_dict=formats_dict)
|
||||||
|
|
||||||
def _parse_mpd_formats(self, mpd_doc, mpd_id=None, mpd_base_url='', formats_dict={}):
|
def _parse_mpd_formats(self, mpd_doc, mpd_id=None, mpd_base_url='', formats_dict={}):
|
||||||
|
"""
|
||||||
|
Parse formats from MPD manifest.
|
||||||
|
References:
|
||||||
|
1. MPEG-DASH Standard, ISO/IEC 23009-1:2014(E),
|
||||||
|
http://standards.iso.org/ittf/PubliclyAvailableStandards/c065274_ISO_IEC_23009-1_2014.zip
|
||||||
|
2. https://en.wikipedia.org/wiki/Dynamic_Adaptive_Streaming_over_HTTP
|
||||||
|
"""
|
||||||
if mpd_doc.get('type') == 'dynamic':
|
if mpd_doc.get('type') == 'dynamic':
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -1498,8 +1545,16 @@ class InfoExtractor(object):
|
|||||||
s_e = segment_timeline.findall(_add_ns('S'))
|
s_e = segment_timeline.findall(_add_ns('S'))
|
||||||
if s_e:
|
if s_e:
|
||||||
ms_info['total_number'] = 0
|
ms_info['total_number'] = 0
|
||||||
|
ms_info['s'] = []
|
||||||
for s in s_e:
|
for s in s_e:
|
||||||
ms_info['total_number'] += 1 + int(s.get('r', '0'))
|
r = int(s.get('r', 0))
|
||||||
|
ms_info['total_number'] += 1 + r
|
||||||
|
ms_info['s'].append({
|
||||||
|
't': int(s.get('t', 0)),
|
||||||
|
# @d is mandatory (see [1, 5.3.9.6.2, Table 17, page 60])
|
||||||
|
'd': int(s.attrib['d']),
|
||||||
|
'r': r,
|
||||||
|
})
|
||||||
else:
|
else:
|
||||||
timescale = segment_template.get('timescale')
|
timescale = segment_template.get('timescale')
|
||||||
if timescale:
|
if timescale:
|
||||||
@@ -1536,7 +1591,7 @@ class InfoExtractor(object):
|
|||||||
continue
|
continue
|
||||||
representation_attrib = adaptation_set.attrib.copy()
|
representation_attrib = adaptation_set.attrib.copy()
|
||||||
representation_attrib.update(representation.attrib)
|
representation_attrib.update(representation.attrib)
|
||||||
# According to page 41 of ISO/IEC 29001-1:2014, @mimeType is mandatory
|
# According to [1, 5.3.7.2, Table 9, page 41], @mimeType is mandatory
|
||||||
mime_type = representation_attrib['mimeType']
|
mime_type = representation_attrib['mimeType']
|
||||||
content_type = mime_type.split('/')[0]
|
content_type = mime_type.split('/')[0]
|
||||||
if content_type == 'text':
|
if content_type == 'text':
|
||||||
@@ -1580,16 +1635,40 @@ class InfoExtractor(object):
|
|||||||
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))
|
||||||
media_template = representation_ms_info['media_template']
|
media_template = representation_ms_info['media_template']
|
||||||
media_template = media_template.replace('$RepresentationID$', representation_id)
|
media_template = media_template.replace('$RepresentationID$', representation_id)
|
||||||
media_template = re.sub(r'\$(Number|Bandwidth)\$', r'%(\1)d', media_template)
|
media_template = re.sub(r'\$(Number|Bandwidth|Time)\$', r'%(\1)d', media_template)
|
||||||
media_template = re.sub(r'\$(Number|Bandwidth)%([^$]+)\$', r'%(\1)\2', media_template)
|
media_template = re.sub(r'\$(Number|Bandwidth|Time)%([^$]+)\$', r'%(\1)\2', media_template)
|
||||||
media_template.replace('$$', '$')
|
media_template.replace('$$', '$')
|
||||||
representation_ms_info['segment_urls'] = [
|
|
||||||
media_template % {
|
# As per [1, 5.3.9.4.4, Table 16, page 55] $Number$ and $Time$
|
||||||
'Number': segment_number,
|
# can't be used at the same time
|
||||||
'Bandwidth': representation_attrib.get('bandwidth')}
|
if '%(Number' in media_template:
|
||||||
for segment_number in range(
|
representation_ms_info['segment_urls'] = [
|
||||||
representation_ms_info['start_number'],
|
media_template % {
|
||||||
representation_ms_info['total_number'] + representation_ms_info['start_number'])]
|
'Number': segment_number,
|
||||||
|
'Bandwidth': representation_attrib.get('bandwidth'),
|
||||||
|
}
|
||||||
|
for segment_number in range(
|
||||||
|
representation_ms_info['start_number'],
|
||||||
|
representation_ms_info['total_number'] + representation_ms_info['start_number'])]
|
||||||
|
else:
|
||||||
|
representation_ms_info['segment_urls'] = []
|
||||||
|
segment_time = 0
|
||||||
|
|
||||||
|
def add_segment_url():
|
||||||
|
representation_ms_info['segment_urls'].append(
|
||||||
|
media_template % {
|
||||||
|
'Time': segment_time,
|
||||||
|
'Bandwidth': representation_attrib.get('bandwidth'),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
for num, s in enumerate(representation_ms_info['s']):
|
||||||
|
segment_time = s.get('t') or segment_time
|
||||||
|
add_segment_url()
|
||||||
|
for r in range(s.get('r', 0)):
|
||||||
|
segment_time += s['d']
|
||||||
|
add_segment_url()
|
||||||
|
segment_time += s['d']
|
||||||
if 'segment_urls' in representation_ms_info:
|
if 'segment_urls' in representation_ms_info:
|
||||||
f.update({
|
f.update({
|
||||||
'segment_urls': representation_ms_info['segment_urls'],
|
'segment_urls': representation_ms_info['segment_urls'],
|
||||||
@@ -1616,6 +1695,88 @@ class InfoExtractor(object):
|
|||||||
self.report_warning('Unknown MIME type %s in DASH manifest' % mime_type)
|
self.report_warning('Unknown MIME type %s in DASH manifest' % mime_type)
|
||||||
return formats
|
return formats
|
||||||
|
|
||||||
|
def _parse_html5_media_entries(self, base_url, webpage, video_id, m3u8_id=None, m3u8_entry_protocol='m3u8'):
|
||||||
|
def absolute_url(video_url):
|
||||||
|
return compat_urlparse.urljoin(base_url, video_url)
|
||||||
|
|
||||||
|
def parse_content_type(content_type):
|
||||||
|
if not content_type:
|
||||||
|
return {}
|
||||||
|
ctr = re.search(r'(?P<mimetype>[^/]+/[^;]+)(?:;\s*codecs="?(?P<codecs>[^"]+))?', content_type)
|
||||||
|
if ctr:
|
||||||
|
mimetype, codecs = ctr.groups()
|
||||||
|
f = parse_codecs(codecs)
|
||||||
|
f['ext'] = mimetype2ext(mimetype)
|
||||||
|
return f
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def _media_formats(src, cur_media_type):
|
||||||
|
full_url = absolute_url(src)
|
||||||
|
if determine_ext(full_url) == 'm3u8':
|
||||||
|
is_plain_url = False
|
||||||
|
formats = self._extract_m3u8_formats(
|
||||||
|
full_url, video_id, ext='mp4',
|
||||||
|
entry_protocol=m3u8_entry_protocol, m3u8_id=m3u8_id)
|
||||||
|
else:
|
||||||
|
is_plain_url = True
|
||||||
|
formats = [{
|
||||||
|
'url': full_url,
|
||||||
|
'vcodec': 'none' if cur_media_type == 'audio' else None,
|
||||||
|
}]
|
||||||
|
return is_plain_url, formats
|
||||||
|
|
||||||
|
entries = []
|
||||||
|
for media_tag, media_type, media_content in re.findall(r'(?s)(<(?P<tag>video|audio)[^>]*>)(.*?)</(?P=tag)>', webpage):
|
||||||
|
media_info = {
|
||||||
|
'formats': [],
|
||||||
|
'subtitles': {},
|
||||||
|
}
|
||||||
|
media_attributes = extract_attributes(media_tag)
|
||||||
|
src = media_attributes.get('src')
|
||||||
|
if src:
|
||||||
|
_, formats = _media_formats(src)
|
||||||
|
media_info['formats'].extend(formats)
|
||||||
|
media_info['thumbnail'] = media_attributes.get('poster')
|
||||||
|
if media_content:
|
||||||
|
for source_tag in re.findall(r'<source[^>]+>', media_content):
|
||||||
|
source_attributes = extract_attributes(source_tag)
|
||||||
|
src = source_attributes.get('src')
|
||||||
|
if not src:
|
||||||
|
continue
|
||||||
|
is_plain_url, formats = _media_formats(src, media_type)
|
||||||
|
if is_plain_url:
|
||||||
|
f = parse_content_type(source_attributes.get('type'))
|
||||||
|
f.update(formats[0])
|
||||||
|
media_info['formats'].append(f)
|
||||||
|
else:
|
||||||
|
media_info['formats'].extend(formats)
|
||||||
|
for track_tag in re.findall(r'<track[^>]+>', media_content):
|
||||||
|
track_attributes = extract_attributes(track_tag)
|
||||||
|
kind = track_attributes.get('kind')
|
||||||
|
if not kind or kind == 'subtitles':
|
||||||
|
src = track_attributes.get('src')
|
||||||
|
if not src:
|
||||||
|
continue
|
||||||
|
lang = track_attributes.get('srclang') or track_attributes.get('lang') or track_attributes.get('label')
|
||||||
|
media_info['subtitles'].setdefault(lang, []).append({
|
||||||
|
'url': absolute_url(src),
|
||||||
|
})
|
||||||
|
if media_info['formats']:
|
||||||
|
entries.append(media_info)
|
||||||
|
return entries
|
||||||
|
|
||||||
|
def _extract_akamai_formats(self, manifest_url, video_id):
|
||||||
|
formats = []
|
||||||
|
f4m_url = re.sub(r'(https?://.+?)/i/', r'\1/z/', manifest_url).replace('/master.m3u8', '/manifest.f4m')
|
||||||
|
formats.extend(self._extract_f4m_formats(
|
||||||
|
update_url_query(f4m_url, {'hdcore': '3.7.0'}),
|
||||||
|
video_id, f4m_id='hds', fatal=False))
|
||||||
|
m3u8_url = re.sub(r'(https?://.+?)/z/', r'\1/i/', manifest_url).replace('/manifest.f4m', '/master.m3u8')
|
||||||
|
formats.extend(self._extract_m3u8_formats(
|
||||||
|
m3u8_url, video_id, 'mp4', 'm3u8_native',
|
||||||
|
m3u8_id='hls', fatal=False))
|
||||||
|
return formats
|
||||||
|
|
||||||
def _live_title(self, name):
|
def _live_title(self, name):
|
||||||
""" Generate the title for a live video """
|
""" Generate the title for a live video """
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
@@ -1676,7 +1837,7 @@ class InfoExtractor(object):
|
|||||||
|
|
||||||
any_restricted = False
|
any_restricted = False
|
||||||
for tc in self.get_testcases(include_onlymatching=False):
|
for tc in self.get_testcases(include_onlymatching=False):
|
||||||
if 'playlist' in tc:
|
if tc.get('playlist', []):
|
||||||
tc = tc['playlist'][0]
|
tc = tc['playlist'][0]
|
||||||
is_restricted = age_restricted(
|
is_restricted = age_restricted(
|
||||||
tc.get('info_dict', {}).get('age_limit'), age_limit)
|
tc.get('info_dict', {}).get('age_limit'), age_limit)
|
||||||
@@ -1729,6 +1890,13 @@ class InfoExtractor(object):
|
|||||||
def _mark_watched(self, *args, **kwargs):
|
def _mark_watched(self, *args, **kwargs):
|
||||||
raise NotImplementedError('This method must be implemented by subclasses')
|
raise NotImplementedError('This method must be implemented by subclasses')
|
||||||
|
|
||||||
|
def geo_verification_headers(self):
|
||||||
|
headers = {}
|
||||||
|
geo_verification_proxy = self._downloader.params.get('geo_verification_proxy')
|
||||||
|
if geo_verification_proxy:
|
||||||
|
headers['Ytdl-request-proxy'] = geo_verification_proxy
|
||||||
|
return headers
|
||||||
|
|
||||||
|
|
||||||
class SearchInfoExtractor(InfoExtractor):
|
class SearchInfoExtractor(InfoExtractor):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -5,13 +5,17 @@ import re
|
|||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import (
|
||||||
compat_urllib_parse_urlencode,
|
|
||||||
compat_urllib_parse_urlparse,
|
compat_urllib_parse_urlparse,
|
||||||
compat_urlparse,
|
compat_urlparse,
|
||||||
)
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
orderedSet,
|
orderedSet,
|
||||||
remove_end,
|
remove_end,
|
||||||
|
extract_attributes,
|
||||||
|
mimetype2ext,
|
||||||
|
determine_ext,
|
||||||
|
int_or_none,
|
||||||
|
parse_iso8601,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -58,6 +62,9 @@ class CondeNastIE(InfoExtractor):
|
|||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': '3D Printed Speakers Lit With LED',
|
'title': '3D Printed Speakers Lit With LED',
|
||||||
'description': 'Check out these beautiful 3D printed LED speakers. You can\'t actually buy them, but LumiGeek is working on a board that will let you make you\'re own.',
|
'description': 'Check out these beautiful 3D printed LED speakers. You can\'t actually buy them, but LumiGeek is working on a board that will let you make you\'re own.',
|
||||||
|
'uploader': 'wired',
|
||||||
|
'upload_date': '20130314',
|
||||||
|
'timestamp': 1363219200,
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
# JS embed
|
# JS embed
|
||||||
@@ -67,70 +74,93 @@ class CondeNastIE(InfoExtractor):
|
|||||||
'id': '55f9cf8b61646d1acf00000c',
|
'id': '55f9cf8b61646d1acf00000c',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': '3D printed TSA Travel Sentry keys really do open TSA locks',
|
'title': '3D printed TSA Travel Sentry keys really do open TSA locks',
|
||||||
|
'uploader': 'arstechnica',
|
||||||
|
'upload_date': '20150916',
|
||||||
|
'timestamp': 1442434955,
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _extract_series(self, url, webpage):
|
def _extract_series(self, url, webpage):
|
||||||
title = self._html_search_regex(r'<div class="cne-series-info">.*?<h1>(.+?)</h1>',
|
title = self._html_search_regex(
|
||||||
webpage, 'series title', flags=re.DOTALL)
|
r'(?s)<div class="cne-series-info">.*?<h1>(.+?)</h1>',
|
||||||
|
webpage, 'series title')
|
||||||
url_object = compat_urllib_parse_urlparse(url)
|
url_object = compat_urllib_parse_urlparse(url)
|
||||||
base_url = '%s://%s' % (url_object.scheme, url_object.netloc)
|
base_url = '%s://%s' % (url_object.scheme, url_object.netloc)
|
||||||
m_paths = re.finditer(r'<p class="cne-thumb-title">.*?<a href="(/watch/.+?)["\?]',
|
m_paths = re.finditer(
|
||||||
webpage, flags=re.DOTALL)
|
r'(?s)<p class="cne-thumb-title">.*?<a href="(/watch/.+?)["\?]', webpage)
|
||||||
paths = orderedSet(m.group(1) for m in m_paths)
|
paths = orderedSet(m.group(1) for m in m_paths)
|
||||||
build_url = lambda path: compat_urlparse.urljoin(base_url, path)
|
build_url = lambda path: compat_urlparse.urljoin(base_url, path)
|
||||||
entries = [self.url_result(build_url(path), 'CondeNast') for path in paths]
|
entries = [self.url_result(build_url(path), 'CondeNast') for path in paths]
|
||||||
return self.playlist_result(entries, playlist_title=title)
|
return self.playlist_result(entries, playlist_title=title)
|
||||||
|
|
||||||
def _extract_video(self, webpage, url_type):
|
def _extract_video(self, webpage, url_type):
|
||||||
if url_type != 'embed':
|
query = {}
|
||||||
description = self._html_search_regex(
|
params = self._search_regex(
|
||||||
[
|
r'(?s)var params = {(.+?)}[;,]', webpage, 'player params', default=None)
|
||||||
r'<div class="cne-video-description">(.+?)</div>',
|
if params:
|
||||||
r'<div class="video-post-content">(.+?)</div>',
|
query.update({
|
||||||
],
|
'videoId': self._search_regex(r'videoId: [\'"](.+?)[\'"]', params, 'video id'),
|
||||||
webpage, 'description', fatal=False, flags=re.DOTALL)
|
'playerId': self._search_regex(r'playerId: [\'"](.+?)[\'"]', params, 'player id'),
|
||||||
|
'target': self._search_regex(r'target: [\'"](.+?)[\'"]', params, 'target'),
|
||||||
|
})
|
||||||
else:
|
else:
|
||||||
description = None
|
params = extract_attributes(self._search_regex(
|
||||||
params = self._search_regex(r'var params = {(.+?)}[;,]', webpage,
|
r'(<[^>]+data-js="video-player"[^>]+>)',
|
||||||
'player params', flags=re.DOTALL)
|
webpage, 'player params element'))
|
||||||
video_id = self._search_regex(r'videoId: [\'"](.+?)[\'"]', params, 'video id')
|
query.update({
|
||||||
player_id = self._search_regex(r'playerId: [\'"](.+?)[\'"]', params, 'player id')
|
'videoId': params['data-video'],
|
||||||
target = self._search_regex(r'target: [\'"](.+?)[\'"]', params, 'target')
|
'playerId': params['data-player'],
|
||||||
data = compat_urllib_parse_urlencode({'videoId': video_id,
|
'target': params['id'],
|
||||||
'playerId': player_id,
|
})
|
||||||
'target': target,
|
video_id = query['videoId']
|
||||||
})
|
video_info = None
|
||||||
base_info_url = self._search_regex(r'url = [\'"](.+?)[\'"][,;]',
|
info_page = self._download_webpage(
|
||||||
webpage, 'base info url',
|
'http://player.cnevids.com/player/video.js',
|
||||||
default='http://player.cnevids.com/player/loader.js?')
|
video_id, 'Downloading video info', query=query, fatal=False)
|
||||||
info_url = base_info_url + data
|
if info_page:
|
||||||
info_page = self._download_webpage(info_url, video_id,
|
video_info = self._parse_json(self._search_regex(
|
||||||
'Downloading video info')
|
r'loadCallback\(({.+})\)', info_page, 'video info'), video_id)['video']
|
||||||
video_info = self._search_regex(r'var\s+video\s*=\s*({.+?});', info_page, 'video info')
|
else:
|
||||||
video_info = self._parse_json(video_info, video_id)
|
info_page = self._download_webpage(
|
||||||
|
'http://player.cnevids.com/player/loader.js',
|
||||||
|
video_id, 'Downloading loader info', query=query)
|
||||||
|
video_info = self._parse_json(self._search_regex(
|
||||||
|
r'var\s+video\s*=\s*({.+?});', info_page, 'video info'), video_id)
|
||||||
|
title = video_info['title']
|
||||||
|
|
||||||
formats = [{
|
formats = []
|
||||||
'format_id': '%s-%s' % (fdata['type'].split('/')[-1], fdata['quality']),
|
for fdata in video_info.get('sources', [{}])[0]:
|
||||||
'url': fdata['src'],
|
src = fdata.get('src')
|
||||||
'ext': fdata['type'].split('/')[-1],
|
if not src:
|
||||||
'quality': 1 if fdata['quality'] == 'high' else 0,
|
continue
|
||||||
} for fdata in video_info['sources'][0]]
|
ext = mimetype2ext(fdata.get('type')) or determine_ext(src)
|
||||||
|
quality = fdata.get('quality')
|
||||||
|
formats.append({
|
||||||
|
'format_id': ext + ('-%s' % quality if quality else ''),
|
||||||
|
'url': src,
|
||||||
|
'ext': ext,
|
||||||
|
'quality': 1 if quality == 'high' else 0,
|
||||||
|
})
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
return {
|
info = self._search_json_ld(
|
||||||
|
webpage, video_id, fatal=False) if url_type != 'embed' else {}
|
||||||
|
info.update({
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
'title': video_info['title'],
|
'title': title,
|
||||||
'thumbnail': video_info['poster_frame'],
|
'thumbnail': video_info.get('poster_frame'),
|
||||||
'description': description,
|
'uploader': video_info.get('brand'),
|
||||||
}
|
'duration': int_or_none(video_info.get('duration')),
|
||||||
|
'tags': video_info.get('tags'),
|
||||||
|
'series': video_info.get('series_title'),
|
||||||
|
'season': video_info.get('season_title'),
|
||||||
|
'timestamp': parse_iso8601(video_info.get('premiere_date')),
|
||||||
|
})
|
||||||
|
return info
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
site, url_type, item_id = re.match(self._VALID_URL, url).groups()
|
||||||
site = mobj.group('site')
|
|
||||||
url_type = mobj.group('type')
|
|
||||||
item_id = mobj.group('id')
|
|
||||||
|
|
||||||
# Convert JS embed to regular embed
|
# Convert JS embed to regular embed
|
||||||
if url_type == 'embedjs':
|
if url_type == 'embedjs':
|
||||||
|
|||||||
@@ -114,6 +114,21 @@ class CrunchyrollIE(CrunchyrollBaseIE):
|
|||||||
# rtmp
|
# rtmp
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
},
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.crunchyroll.com/rezero-starting-life-in-another-world-/episode-5-the-morning-of-our-promise-is-still-distant-702409',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '702409',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Re:ZERO -Starting Life in Another World- Episode 5 – The Morning of Our Promise Is Still Distant',
|
||||||
|
'description': 'md5:97664de1ab24bbf77a9c01918cb7dca9',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
'uploader': 'TV TOKYO',
|
||||||
|
'upload_date': '20160508',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# m3u8 download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.crunchyroll.fr/girl-friend-beta/episode-11-goodbye-la-mode-661697',
|
'url': 'http://www.crunchyroll.fr/girl-friend-beta/episode-11-goodbye-la-mode-661697',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
@@ -336,9 +351,18 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
|||||||
if video_encode_id in video_encode_ids:
|
if video_encode_id in video_encode_ids:
|
||||||
continue
|
continue
|
||||||
video_encode_ids.append(video_encode_id)
|
video_encode_ids.append(video_encode_id)
|
||||||
|
|
||||||
|
video_file = xpath_text(stream_info, './file')
|
||||||
|
if not video_file:
|
||||||
|
continue
|
||||||
|
if video_file.startswith('http'):
|
||||||
|
formats.extend(self._extract_m3u8_formats(
|
||||||
|
video_file, video_id, 'mp4', entry_protocol='m3u8_native',
|
||||||
|
m3u8_id='hls', fatal=False))
|
||||||
|
continue
|
||||||
|
|
||||||
video_url = xpath_text(stream_info, './host')
|
video_url = xpath_text(stream_info, './host')
|
||||||
video_play_path = xpath_text(stream_info, './file')
|
if not video_url:
|
||||||
if not video_url or not video_play_path:
|
|
||||||
continue
|
continue
|
||||||
metadata = stream_info.find('./metadata')
|
metadata = stream_info.find('./metadata')
|
||||||
format_info = {
|
format_info = {
|
||||||
@@ -353,7 +377,7 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
|||||||
parsed_video_url = compat_urlparse.urlparse(video_url)
|
parsed_video_url = compat_urlparse.urlparse(video_url)
|
||||||
direct_video_url = compat_urlparse.urlunparse(parsed_video_url._replace(
|
direct_video_url = compat_urlparse.urlunparse(parsed_video_url._replace(
|
||||||
netloc='v.lvlt.crcdn.net',
|
netloc='v.lvlt.crcdn.net',
|
||||||
path='%s/%s' % (remove_end(parsed_video_url.path, '/'), video_play_path.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({
|
||||||
'url': direct_video_url,
|
'url': direct_video_url,
|
||||||
@@ -363,7 +387,7 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
|||||||
|
|
||||||
format_info.update({
|
format_info.update({
|
||||||
'url': video_url,
|
'url': video_url,
|
||||||
'play_path': video_play_path,
|
'play_path': video_file,
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
})
|
})
|
||||||
formats.append(format_info)
|
formats.append(format_info)
|
||||||
|
|||||||
@@ -51,8 +51,11 @@ class CSpanIE(InfoExtractor):
|
|||||||
'url': 'http://www.c-span.org/video/?104517-1/immigration-reforms-needed-protect-skilled-american-workers',
|
'url': 'http://www.c-span.org/video/?104517-1/immigration-reforms-needed-protect-skilled-american-workers',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'judiciary031715',
|
'id': 'judiciary031715',
|
||||||
'ext': 'flv',
|
'ext': 'mp4',
|
||||||
'title': 'Immigration Reforms Needed to Protect Skilled American Workers',
|
'title': 'Immigration Reforms Needed to Protect Skilled American Workers',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True, # m3u8 downloads
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# coding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import parse_iso8601, ExtractorError
|
from ..utils import unified_timestamp
|
||||||
|
|
||||||
|
|
||||||
class CtsNewsIE(InfoExtractor):
|
class CtsNewsIE(InfoExtractor):
|
||||||
IE_DESC = '華視新聞'
|
IE_DESC = '華視新聞'
|
||||||
# https connection failed (Connection reset)
|
|
||||||
_VALID_URL = r'https?://news\.cts\.com\.tw/[a-z]+/[a-z]+/\d+/(?P<id>\d+)\.html'
|
_VALID_URL = r'https?://news\.cts\.com\.tw/[a-z]+/[a-z]+/\d+/(?P<id>\d+)\.html'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://news.cts.com.tw/cts/international/201501/201501291578109.html',
|
'url': 'http://news.cts.com.tw/cts/international/201501/201501291578109.html',
|
||||||
@@ -16,7 +15,7 @@ class CtsNewsIE(InfoExtractor):
|
|||||||
'id': '201501291578109',
|
'id': '201501291578109',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': '以色列.真主黨交火 3人死亡',
|
'title': '以色列.真主黨交火 3人死亡',
|
||||||
'description': 'md5:95e9b295c898b7ff294f09d450178d7d',
|
'description': '以色列和黎巴嫩真主黨,爆發五年最嚴重衝突,雙方砲轟交火,兩名以軍死亡,還有一名西班牙籍的聯合國維和人...',
|
||||||
'timestamp': 1422528540,
|
'timestamp': 1422528540,
|
||||||
'upload_date': '20150129',
|
'upload_date': '20150129',
|
||||||
}
|
}
|
||||||
@@ -28,7 +27,7 @@ class CtsNewsIE(InfoExtractor):
|
|||||||
'id': '201309031304098',
|
'id': '201309031304098',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': '韓國31歲童顏男 貌如十多歲小孩',
|
'title': '韓國31歲童顏男 貌如十多歲小孩',
|
||||||
'description': 'md5:f183feeba3752b683827aab71adad584',
|
'description': '越有年紀的人,越希望看起來年輕一點,而南韓卻有一位31歲的男子,看起來像是11、12歲的小孩,身...',
|
||||||
'thumbnail': 're:^https?://.*\.jpg$',
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
'timestamp': 1378205880,
|
'timestamp': 1378205880,
|
||||||
'upload_date': '20130903',
|
'upload_date': '20130903',
|
||||||
@@ -36,8 +35,7 @@ class CtsNewsIE(InfoExtractor):
|
|||||||
}, {
|
}, {
|
||||||
# With Youtube embedded video
|
# With Youtube embedded video
|
||||||
'url': 'http://news.cts.com.tw/cts/money/201501/201501291578003.html',
|
'url': 'http://news.cts.com.tw/cts/money/201501/201501291578003.html',
|
||||||
'md5': '1d842c771dc94c8c3bca5af2cc1db9c5',
|
'md5': 'e4726b2ccd70ba2c319865e28f0a91d1',
|
||||||
'add_ie': ['Youtube'],
|
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'OVbfO7d0_hQ',
|
'id': 'OVbfO7d0_hQ',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
@@ -47,42 +45,37 @@ class CtsNewsIE(InfoExtractor):
|
|||||||
'upload_date': '20150128',
|
'upload_date': '20150128',
|
||||||
'uploader_id': 'TBSCTS',
|
'uploader_id': 'TBSCTS',
|
||||||
'uploader': '中華電視公司',
|
'uploader': '中華電視公司',
|
||||||
}
|
},
|
||||||
|
'add_ie': ['Youtube'],
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
news_id = self._match_id(url)
|
news_id = self._match_id(url)
|
||||||
page = self._download_webpage(url, news_id)
|
page = self._download_webpage(url, news_id)
|
||||||
|
|
||||||
if self._search_regex(r'(CTSPlayer2)', page, 'CTSPlayer2 identifier', default=None):
|
news_id = self._hidden_inputs(page).get('get_id')
|
||||||
feed_url = self._html_search_regex(
|
|
||||||
r'(http://news\.cts\.com\.tw/action/mp4feed\.php\?news_id=\d+)',
|
if news_id:
|
||||||
page, 'feed url')
|
mp4_feed = self._download_json(
|
||||||
video_url = self._download_webpage(
|
'http://news.cts.com.tw/action/test_mp4feed.php',
|
||||||
feed_url, news_id, note='Fetching feed')
|
news_id, note='Fetching feed', query={'news_id': news_id})
|
||||||
|
video_url = mp4_feed['source_url']
|
||||||
else:
|
else:
|
||||||
self.to_screen('Not CTSPlayer video, trying Youtube...')
|
self.to_screen('Not CTSPlayer video, trying Youtube...')
|
||||||
youtube_url = self._search_regex(
|
youtube_url = self._search_regex(
|
||||||
r'src="(//www\.youtube\.com/embed/[^"]+)"', page, 'youtube url',
|
r'src="(//www\.youtube\.com/embed/[^"]+)"', page, 'youtube url')
|
||||||
default=None)
|
|
||||||
if not youtube_url:
|
|
||||||
raise ExtractorError('The news includes no videos!', expected=True)
|
|
||||||
|
|
||||||
return {
|
return self.url_result(youtube_url, ie='Youtube')
|
||||||
'_type': 'url',
|
|
||||||
'url': youtube_url,
|
|
||||||
'ie_key': 'Youtube',
|
|
||||||
}
|
|
||||||
|
|
||||||
description = self._html_search_meta('description', page)
|
description = self._html_search_meta('description', page)
|
||||||
title = self._html_search_meta('title', page)
|
title = self._html_search_meta('title', page, fatal=True)
|
||||||
thumbnail = self._html_search_meta('image', page)
|
thumbnail = self._html_search_meta('image', page)
|
||||||
|
|
||||||
datetime_str = self._html_search_regex(
|
datetime_str = self._html_search_regex(
|
||||||
r'(\d{4}/\d{2}/\d{2} \d{2}:\d{2})', page, 'date and time')
|
r'(\d{4}/\d{2}/\d{2} \d{2}:\d{2})', page, 'date and time', fatal=False)
|
||||||
# Transform into ISO 8601 format with timezone info
|
timestamp = None
|
||||||
datetime_str = datetime_str.replace('/', '-') + ':00+0800'
|
if datetime_str:
|
||||||
timestamp = parse_iso8601(datetime_str, delimiter=' ')
|
timestamp = unified_timestamp(datetime_str) - 8 * 3600
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': news_id,
|
'id': news_id,
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import time
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import int_or_none
|
from ..utils import (
|
||||||
|
int_or_none,
|
||||||
|
HEADRequest,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CultureUnpluggedIE(InfoExtractor):
|
class CultureUnpluggedIE(InfoExtractor):
|
||||||
@@ -32,6 +36,9 @@ class CultureUnpluggedIE(InfoExtractor):
|
|||||||
video_id = mobj.group('id')
|
video_id = mobj.group('id')
|
||||||
display_id = mobj.group('display_id') or video_id
|
display_id = mobj.group('display_id') or video_id
|
||||||
|
|
||||||
|
# request setClientTimezone.php to get PHPSESSID cookie which is need to get valid json data in the next request
|
||||||
|
self._request_webpage(HEADRequest(
|
||||||
|
'http://www.cultureunplugged.com/setClientTimezone.php?timeOffset=%d' % -(time.timezone / 3600)), display_id)
|
||||||
movie_data = self._download_json(
|
movie_data = self._download_json(
|
||||||
'http://www.cultureunplugged.com/movie-data/cu-%s.json' % video_id, display_id)
|
'http://www.cultureunplugged.com/movie-data/cu-%s.json' % video_id, display_id)
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class CWTVIE(InfoExtractor):
|
class CWTVIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?cw(?:tv|seed)\.com/(?:shows/)?(?:[^/]+/){2}\?.*\bplay=(?P<id>[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12})'
|
_VALID_URL = r'https?://(?:www\.)?cw(?:tv(?:pr)?|seed)\.com/(?:shows/)?(?:[^/]+/)+[^?]*\?.*\b(?:play|watch)=(?P<id>[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12})'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://cwtv.com/shows/arrow/legends-of-yesterday/?play=6b15e985-9345-4f60-baf8-56e96be57c63',
|
'url': 'http://cwtv.com/shows/arrow/legends-of-yesterday/?play=6b15e985-9345-4f60-baf8-56e96be57c63',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@@ -28,7 +28,8 @@ class CWTVIE(InfoExtractor):
|
|||||||
'params': {
|
'params': {
|
||||||
# m3u8 download
|
# m3u8 download
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
}
|
},
|
||||||
|
'skip': 'redirect to http://cwtv.com/shows/arrow/',
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.cwseed.com/shows/whose-line-is-it-anyway/jeff-davis-4/?play=24282b12-ead2-42f2-95ad-26770c2c6088',
|
'url': 'http://www.cwseed.com/shows/whose-line-is-it-anyway/jeff-davis-4/?play=24282b12-ead2-42f2-95ad-26770c2c6088',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@@ -44,22 +45,43 @@ class CWTVIE(InfoExtractor):
|
|||||||
'upload_date': '20151006',
|
'upload_date': '20151006',
|
||||||
'timestamp': 1444107300,
|
'timestamp': 1444107300,
|
||||||
},
|
},
|
||||||
'params': {
|
|
||||||
# m3u8 download
|
|
||||||
'skip_download': True,
|
|
||||||
}
|
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://cwtv.com/thecw/chroniclesofcisco/?play=8adebe35-f447-465f-ab52-e863506ff6d6',
|
'url': 'http://cwtv.com/thecw/chroniclesofcisco/?play=8adebe35-f447-465f-ab52-e863506ff6d6',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://cwtvpr.com/the-cw/video?watch=9eee3f60-ef4e-440b-b3b2-49428ac9c54e',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://cwtv.com/shows/arrow/legends-of-yesterday/?watch=6b15e985-9345-4f60-baf8-56e96be57c63',
|
||||||
|
'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)
|
||||||
video_data = self._download_json(
|
video_data = None
|
||||||
'http://metaframe.digitalsmiths.tv/v2/CWtv/assets/%s/partner/132?format=json' % video_id, video_id)
|
formats = []
|
||||||
|
for partner in (154, 213):
|
||||||
formats = self._extract_m3u8_formats(
|
vdata = self._download_json(
|
||||||
video_data['videos']['variantplaylist']['uri'], video_id, 'mp4')
|
'http://metaframe.digitalsmiths.tv/v2/CWtv/assets/%s/partner/%d?format=json' % (video_id, partner), video_id, fatal=False)
|
||||||
|
if not vdata:
|
||||||
|
continue
|
||||||
|
video_data = vdata
|
||||||
|
for quality, quality_data in vdata.get('videos', {}).items():
|
||||||
|
quality_url = quality_data.get('uri')
|
||||||
|
if not quality_url:
|
||||||
|
continue
|
||||||
|
if quality == 'variantplaylist':
|
||||||
|
formats.extend(self._extract_m3u8_formats(
|
||||||
|
quality_url, video_id, 'mp4', m3u8_id='hls', fatal=False))
|
||||||
|
else:
|
||||||
|
tbr = int_or_none(quality_data.get('bitrate'))
|
||||||
|
format_id = 'http' + ('-%d' % tbr if tbr else '')
|
||||||
|
if self._is_valid_url(quality_url, video_id, format_id):
|
||||||
|
formats.append({
|
||||||
|
'format_id': format_id,
|
||||||
|
'url': quality_url,
|
||||||
|
'tbr': tbr,
|
||||||
|
})
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
thumbnails = [{
|
thumbnails = [{
|
||||||
|
|||||||
@@ -5,19 +5,20 @@ from .common import InfoExtractor
|
|||||||
from ..utils import (
|
from ..utils import (
|
||||||
int_or_none,
|
int_or_none,
|
||||||
determine_protocol,
|
determine_protocol,
|
||||||
|
unescapeHTML,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
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-(?P<id>[0-9]+)'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.dailymail.co.uk/video/sciencetech/video-1288527/Turn-video-impressionist-masterpiece.html',
|
'url': 'http://www.dailymail.co.uk/video/tvshowbiz/video-1295863/The-Mountain-appears-sparkling-water-ad-Heavy-Bubbles.html',
|
||||||
'md5': '2f639d446394f53f3a33658b518b6615',
|
'md5': 'f6129624562251f628296c3a9ffde124',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '1288527',
|
'id': '1295863',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Turn any video into an impressionist masterpiece',
|
'title': 'The Mountain appears in sparkling water ad for \'Heavy Bubbles\'',
|
||||||
'description': 'md5:88ddbcb504367987b2708bb38677c9d2',
|
'description': 'md5:a93d74b6da172dd5dc4d973e0b766a84',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,7 +27,7 @@ class DailyMailIE(InfoExtractor):
|
|||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
video_data = self._parse_json(self._search_regex(
|
video_data = self._parse_json(self._search_regex(
|
||||||
r"data-opts='({.+?})'", webpage, 'video data'), video_id)
|
r"data-opts='({.+?})'", webpage, 'video data'), video_id)
|
||||||
title = video_data['title']
|
title = unescapeHTML(video_data['title'])
|
||||||
video_sources = self._download_json(video_data.get(
|
video_sources = self._download_json(video_data.get(
|
||||||
'sources', {}).get('url') or 'http://www.dailymail.co.uk/api/player/%s/video-sources.json' % video_id, video_id)
|
'sources', {}).get('url') or 'http://www.dailymail.co.uk/api/player/%s/video-sources.json' % video_id, video_id)
|
||||||
|
|
||||||
@@ -55,7 +56,7 @@ class DailyMailIE(InfoExtractor):
|
|||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': title,
|
'title': title,
|
||||||
'description': video_data.get('descr'),
|
'description': unescapeHTML(video_data.get('descr')),
|
||||||
'thumbnail': video_data.get('poster') or video_data.get('thumbnail'),
|
'thumbnail': video_data.get('poster') or video_data.get('thumbnail'),
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ from ..utils import (
|
|||||||
sanitized_Request,
|
sanitized_Request,
|
||||||
str_to_int,
|
str_to_int,
|
||||||
unescapeHTML,
|
unescapeHTML,
|
||||||
|
mimetype2ext,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -111,6 +112,13 @@ class DailymotionIE(DailymotionBaseInfoExtractor):
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _extract_urls(webpage):
|
||||||
|
# Look for embedded Dailymotion player
|
||||||
|
matches = re.findall(
|
||||||
|
r'<(?:(?:embed|iframe)[^>]+?src=|input[^>]+id=[\'"]dmcloudUrlEmissionSelect[\'"][^>]+value=)(["\'])(?P<url>(?:https?:)?//(?:www\.)?dailymotion\.com/(?:embed|swf)/video/.+?)\1', webpage)
|
||||||
|
return list(map(lambda m: unescapeHTML(m[1]), matches))
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
@@ -153,18 +161,19 @@ class DailymotionIE(DailymotionBaseInfoExtractor):
|
|||||||
type_ = media.get('type')
|
type_ = media.get('type')
|
||||||
if type_ == 'application/vnd.lumberjack.manifest':
|
if type_ == 'application/vnd.lumberjack.manifest':
|
||||||
continue
|
continue
|
||||||
ext = determine_ext(media_url)
|
ext = mimetype2ext(type_) or determine_ext(media_url)
|
||||||
if type_ == 'application/x-mpegURL' or ext == 'm3u8':
|
if ext == 'm3u8':
|
||||||
formats.extend(self._extract_m3u8_formats(
|
formats.extend(self._extract_m3u8_formats(
|
||||||
media_url, video_id, 'mp4', preference=-1,
|
media_url, video_id, 'mp4', preference=-1,
|
||||||
m3u8_id='hls', fatal=False))
|
m3u8_id='hls', fatal=False))
|
||||||
elif type_ == 'application/f4m' or ext == 'f4m':
|
elif ext == 'f4m':
|
||||||
formats.extend(self._extract_f4m_formats(
|
formats.extend(self._extract_f4m_formats(
|
||||||
media_url, video_id, preference=-1, f4m_id='hds', fatal=False))
|
media_url, video_id, preference=-1, f4m_id='hds', fatal=False))
|
||||||
else:
|
else:
|
||||||
f = {
|
f = {
|
||||||
'url': media_url,
|
'url': media_url,
|
||||||
'format_id': 'http-%s' % quality,
|
'format_id': 'http-%s' % quality,
|
||||||
|
'ext': ext,
|
||||||
}
|
}
|
||||||
m = re.search(r'H264-(?P<width>\d+)x(?P<height>\d+)', media_url)
|
m = re.search(r'H264-(?P<width>\d+)x(?P<height>\d+)', media_url)
|
||||||
if m:
|
if m:
|
||||||
@@ -322,7 +331,9 @@ class DailymotionPlaylistIE(DailymotionBaseInfoExtractor):
|
|||||||
|
|
||||||
for video_id in re.findall(r'data-xid="(.+?)"', webpage):
|
for video_id in re.findall(r'data-xid="(.+?)"', webpage):
|
||||||
if video_id not in video_ids:
|
if video_id not in video_ids:
|
||||||
yield self.url_result('http://www.dailymotion.com/video/%s' % video_id, 'Dailymotion')
|
yield self.url_result(
|
||||||
|
'http://www.dailymotion.com/video/%s' % video_id,
|
||||||
|
DailymotionIE.ie_key(), video_id)
|
||||||
video_ids.add(video_id)
|
video_ids.add(video_id)
|
||||||
|
|
||||||
if re.search(self._MORE_PAGES_INDICATOR, webpage) is None:
|
if re.search(self._MORE_PAGES_INDICATOR, webpage) is None:
|
||||||
|
|||||||
@@ -66,22 +66,32 @@ class DaumIE(InfoExtractor):
|
|||||||
'view_count': int,
|
'view_count': int,
|
||||||
'comment_count': int,
|
'comment_count': int,
|
||||||
},
|
},
|
||||||
|
}, {
|
||||||
|
# Requires dte_type=WEB (#9972)
|
||||||
|
'url': 'http://tvpot.daum.net/v/s3794Uf1NZeZ1qMpGpeqeRU',
|
||||||
|
'md5': 'a8917742069a4dd442516b86e7d66529',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 's3794Uf1NZeZ1qMpGpeqeRU',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': '러블리즈 - Destiny (나의 지구) (Lovelyz - Destiny) [쇼! 음악중심] 508회 20160611',
|
||||||
|
'description': '러블리즈 - Destiny (나의 지구) (Lovelyz - Destiny)\n\n[쇼! 음악중심] 20160611, 507회',
|
||||||
|
'upload_date': '20160611',
|
||||||
|
},
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = compat_urllib_parse_unquote(self._match_id(url))
|
video_id = compat_urllib_parse_unquote(self._match_id(url))
|
||||||
query = compat_urllib_parse_urlencode({'vid': video_id})
|
|
||||||
movie_data = self._download_json(
|
movie_data = self._download_json(
|
||||||
'http://videofarm.daum.net/controller/api/closed/v1_2/IntegratedMovieData.json?' + query,
|
'http://videofarm.daum.net/controller/api/closed/v1_2/IntegratedMovieData.json',
|
||||||
video_id, 'Downloading video formats info')
|
video_id, 'Downloading video formats info', query={'vid': video_id, 'dte_type': 'WEB'})
|
||||||
|
|
||||||
# For urls like http://m.tvpot.daum.net/v/65139429, where the video_id is really a clipid
|
# For urls like http://m.tvpot.daum.net/v/65139429, where the video_id is really a clipid
|
||||||
if not movie_data.get('output_list', {}).get('output_list') and re.match(r'^\d+$', video_id):
|
if not movie_data.get('output_list', {}).get('output_list') and re.match(r'^\d+$', video_id):
|
||||||
return self.url_result('http://tvpot.daum.net/clip/ClipView.do?clipid=%s' % video_id)
|
return self.url_result('http://tvpot.daum.net/clip/ClipView.do?clipid=%s' % video_id)
|
||||||
|
|
||||||
info = self._download_xml(
|
info = self._download_xml(
|
||||||
'http://tvpot.daum.net/clip/ClipInfoXml.do?' + query, video_id,
|
'http://tvpot.daum.net/clip/ClipInfoXml.do', video_id,
|
||||||
'Downloading video info')
|
'Downloading video info', query={'vid': video_id})
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
for format_el in movie_data['output_list']['output_list']:
|
for format_el in movie_data['output_list']['output_list']:
|
||||||
|
|||||||
@@ -4,78 +4,53 @@ 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 (
|
|
||||||
float_or_none,
|
|
||||||
int_or_none,
|
|
||||||
clean_html,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class DBTVIE(InfoExtractor):
|
class DBTVIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?dbtv\.no/(?:(?:lazyplayer|player)/)?(?P<id>[0-9]+)(?:#(?P<display_id>.+))?'
|
_VALID_URL = r'https?://(?:www\.)?dbtv\.no/(?:[^/]+/)?(?P<id>[0-9]+)(?:#(?P<display_id>.+))?'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://dbtv.no/3649835190001#Skulle_teste_ut_fornøyelsespark,_men_kollegaen_var_bare_opptatt_av_bikinikroppen',
|
'url': 'http://dbtv.no/3649835190001#Skulle_teste_ut_fornøyelsespark,_men_kollegaen_var_bare_opptatt_av_bikinikroppen',
|
||||||
'md5': 'b89953ed25dacb6edb3ef6c6f430f8bc',
|
'md5': '2e24f67936517b143a234b4cadf792ec',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '33100',
|
'id': '3649835190001',
|
||||||
'display_id': 'Skulle_teste_ut_fornøyelsespark,_men_kollegaen_var_bare_opptatt_av_bikinikroppen',
|
'display_id': 'Skulle_teste_ut_fornøyelsespark,_men_kollegaen_var_bare_opptatt_av_bikinikroppen',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Skulle teste ut fornøyelsespark, men kollegaen var bare opptatt av bikinikroppen',
|
'title': 'Skulle teste ut fornøyelsespark, men kollegaen var bare opptatt av bikinikroppen',
|
||||||
'description': 'md5:1504a54606c4dde3e4e61fc97aa857e0',
|
'description': 'md5:1504a54606c4dde3e4e61fc97aa857e0',
|
||||||
'thumbnail': 're:https?://.*\.jpg$',
|
'thumbnail': 're:https?://.*\.jpg',
|
||||||
'timestamp': 1404039863.438,
|
'timestamp': 1404039863,
|
||||||
'upload_date': '20140629',
|
'upload_date': '20140629',
|
||||||
'duration': 69.544,
|
'duration': 69.544,
|
||||||
'view_count': int,
|
'uploader_id': '1027729757001',
|
||||||
'categories': list,
|
},
|
||||||
}
|
'add_ie': ['BrightcoveNew']
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://dbtv.no/3649835190001',
|
'url': 'http://dbtv.no/3649835190001',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.dbtv.no/lazyplayer/4631135248001',
|
'url': 'http://www.dbtv.no/lazyplayer/4631135248001',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://dbtv.no/vice/5000634109001',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://dbtv.no/filmtrailer/3359293614001',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _extract_urls(webpage):
|
||||||
|
return [url for _, url in re.findall(
|
||||||
|
r'<iframe[^>]+src=(["\'])((?:https?:)?//(?:www\.)?dbtv\.no/(?:lazy)?player/\d+.*?)\1',
|
||||||
|
webpage)]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id, display_id = re.match(self._VALID_URL, url).groups()
|
||||||
video_id = mobj.group('id')
|
|
||||||
display_id = mobj.group('display_id') or video_id
|
|
||||||
|
|
||||||
data = self._download_json(
|
|
||||||
'http://api.dbtv.no/discovery/%s' % video_id, display_id)
|
|
||||||
|
|
||||||
video = data['playlist'][0]
|
|
||||||
|
|
||||||
formats = [{
|
|
||||||
'url': f['URL'],
|
|
||||||
'vcodec': f.get('container'),
|
|
||||||
'width': int_or_none(f.get('width')),
|
|
||||||
'height': int_or_none(f.get('height')),
|
|
||||||
'vbr': float_or_none(f.get('rate'), 1000),
|
|
||||||
'filesize': int_or_none(f.get('size')),
|
|
||||||
} for f in video['renditions'] if 'URL' in f]
|
|
||||||
|
|
||||||
if not formats:
|
|
||||||
for url_key, format_id in [('URL', 'mp4'), ('HLSURL', 'hls')]:
|
|
||||||
if url_key in video:
|
|
||||||
formats.append({
|
|
||||||
'url': video[url_key],
|
|
||||||
'format_id': format_id,
|
|
||||||
})
|
|
||||||
|
|
||||||
self._sort_formats(formats)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': compat_str(video['id']),
|
'_type': 'url_transparent',
|
||||||
|
'url': 'http://players.brightcove.net/1027729757001/default_default/index.html?videoId=%s' % video_id,
|
||||||
|
'id': video_id,
|
||||||
'display_id': display_id,
|
'display_id': display_id,
|
||||||
'title': video['title'],
|
'ie_key': 'BrightcoveNew',
|
||||||
'description': clean_html(video['desc']),
|
|
||||||
'thumbnail': video.get('splash') or video.get('thumb'),
|
|
||||||
'timestamp': float_or_none(video.get('publishedAt'), 1000),
|
|
||||||
'duration': float_or_none(video.get('length'), 1000),
|
|
||||||
'view_count': int_or_none(video.get('views')),
|
|
||||||
'categories': video.get('tags'),
|
|
||||||
'formats': formats,
|
|
||||||
}
|
}
|
||||||
|
|||||||
108
youtube_dl/extractor/discoverygo.py
Normal file
108
youtube_dl/extractor/discoverygo.py
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..compat import compat_str
|
||||||
|
from ..utils import (
|
||||||
|
extract_attributes,
|
||||||
|
int_or_none,
|
||||||
|
parse_age_limit,
|
||||||
|
unescapeHTML,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DiscoveryGoIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'''(?x)https?://(?:www\.)?(?:
|
||||||
|
discovery|
|
||||||
|
investigationdiscovery|
|
||||||
|
discoverylife|
|
||||||
|
animalplanet|
|
||||||
|
ahctv|
|
||||||
|
destinationamerica|
|
||||||
|
sciencechannel|
|
||||||
|
tlc|
|
||||||
|
velocitychannel
|
||||||
|
)go\.com/(?:[^/]+/)*(?P<id>[^/?#&]+)'''
|
||||||
|
_TEST = {
|
||||||
|
'url': 'https://www.discoverygo.com/love-at-first-kiss/kiss-first-ask-questions-later/',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '57a33c536b66d1cd0345eeb1',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Kiss First, Ask Questions Later!',
|
||||||
|
'description': 'md5:fe923ba34050eae468bffae10831cb22',
|
||||||
|
'duration': 2579,
|
||||||
|
'series': 'Love at First Kiss',
|
||||||
|
'season_number': 1,
|
||||||
|
'episode_number': 1,
|
||||||
|
'age_limit': 14,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
display_id = self._match_id(url)
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
|
||||||
|
container = extract_attributes(
|
||||||
|
self._search_regex(
|
||||||
|
r'(<div[^>]+class=["\']video-player-container[^>]+>)',
|
||||||
|
webpage, 'video container'))
|
||||||
|
|
||||||
|
video = self._parse_json(
|
||||||
|
unescapeHTML(container.get('data-video') or container.get('data-json')),
|
||||||
|
display_id)
|
||||||
|
|
||||||
|
title = video['name']
|
||||||
|
|
||||||
|
stream = video['stream']
|
||||||
|
STREAM_URL_SUFFIX = 'streamUrl'
|
||||||
|
formats = []
|
||||||
|
for stream_kind in ('', 'hds'):
|
||||||
|
suffix = STREAM_URL_SUFFIX.capitalize() if stream_kind else STREAM_URL_SUFFIX
|
||||||
|
stream_url = stream.get('%s%s' % (stream_kind, suffix))
|
||||||
|
if not stream_url:
|
||||||
|
continue
|
||||||
|
if stream_kind == '':
|
||||||
|
formats.extend(self._extract_m3u8_formats(
|
||||||
|
stream_url, display_id, 'mp4', entry_protocol='m3u8_native',
|
||||||
|
m3u8_id='hls', fatal=False))
|
||||||
|
elif stream_kind == 'hds':
|
||||||
|
formats.extend(self._extract_f4m_formats(
|
||||||
|
stream_url, display_id, f4m_id=stream_kind, fatal=False))
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
video_id = video.get('id') or display_id
|
||||||
|
description = video.get('description', {}).get('detailed')
|
||||||
|
duration = int_or_none(video.get('duration'))
|
||||||
|
|
||||||
|
series = video.get('show', {}).get('name')
|
||||||
|
season_number = int_or_none(video.get('season', {}).get('number'))
|
||||||
|
episode_number = int_or_none(video.get('episodeNumber'))
|
||||||
|
|
||||||
|
tags = video.get('tags')
|
||||||
|
age_limit = parse_age_limit(video.get('parental', {}).get('rating'))
|
||||||
|
|
||||||
|
subtitles = {}
|
||||||
|
captions = stream.get('captions')
|
||||||
|
if isinstance(captions, list):
|
||||||
|
for caption in captions:
|
||||||
|
subtitle_url = caption.get('fileUrl')
|
||||||
|
if (not subtitle_url or not isinstance(subtitle_url, compat_str) or
|
||||||
|
not subtitle_url.startswith('http')):
|
||||||
|
continue
|
||||||
|
lang = caption.get('fileLang', 'en')
|
||||||
|
subtitles.setdefault(lang, []).append({'url': subtitle_url})
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'display_id': display_id,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'duration': duration,
|
||||||
|
'series': series,
|
||||||
|
'season_number': season_number,
|
||||||
|
'episode_number': episode_number,
|
||||||
|
'tags': tags,
|
||||||
|
'age_limit': age_limit,
|
||||||
|
'formats': formats,
|
||||||
|
'subtitles': subtitles,
|
||||||
|
}
|
||||||
@@ -10,18 +10,18 @@ from ..utils import (
|
|||||||
class DotsubIE(InfoExtractor):
|
class DotsubIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?dotsub\.com/view/(?P<id>[^/]+)'
|
_VALID_URL = r'https?://(?:www\.)?dotsub\.com/view/(?P<id>[^/]+)'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://dotsub.com/view/aed3b8b2-1889-4df5-ae63-ad85f5572f27',
|
'url': 'https://dotsub.com/view/9c63db2a-fa95-4838-8e6e-13deafe47f09',
|
||||||
'md5': '0914d4d69605090f623b7ac329fea66e',
|
'md5': '21c7ff600f545358134fea762a6d42b6',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'aed3b8b2-1889-4df5-ae63-ad85f5572f27',
|
'id': '9c63db2a-fa95-4838-8e6e-13deafe47f09',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'title': 'Pyramids of Waste (2010), AKA The Lightbulb Conspiracy - Planned obsolescence documentary',
|
'title': 'MOTIVATION - "It\'s Possible" Best Inspirational Video Ever',
|
||||||
'description': 'md5:699a0f7f50aeec6042cb3b1db2d0d074',
|
'description': 'md5:41af1e273edbbdfe4e216a78b9d34ac6',
|
||||||
'thumbnail': 're:^https?://dotsub.com/media/aed3b8b2-1889-4df5-ae63-ad85f5572f27/p',
|
'thumbnail': 're:^https?://dotsub.com/media/9c63db2a-fa95-4838-8e6e-13deafe47f09/p',
|
||||||
'duration': 3169,
|
'duration': 198,
|
||||||
'uploader': '4v4l0n42',
|
'uploader': 'liuxt',
|
||||||
'timestamp': 1292248482.625,
|
'timestamp': 1385778501.104,
|
||||||
'upload_date': '20101213',
|
'upload_date': '20131130',
|
||||||
'view_count': int,
|
'view_count': int,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,12 @@ class DreiSatIE(ZDFIE):
|
|||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Waidmannsheil',
|
'title': 'Waidmannsheil',
|
||||||
'description': 'md5:cce00ca1d70e21425e72c86a98a56817',
|
'description': 'md5:cce00ca1d70e21425e72c86a98a56817',
|
||||||
'uploader': '3sat',
|
'uploader': 'SCHWEIZWEIT',
|
||||||
|
'uploader_id': '100000210',
|
||||||
'upload_date': '20140913'
|
'upload_date': '20140913'
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True, # m3u8 downloads
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ from __future__ import unicode_literals
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import str_to_int
|
from ..utils import (
|
||||||
|
NO_DEFAULT,
|
||||||
|
str_to_int,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class DrTuberIE(InfoExtractor):
|
class DrTuberIE(InfoExtractor):
|
||||||
@@ -17,7 +20,6 @@ class DrTuberIE(InfoExtractor):
|
|||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'hot perky blonde naked golf',
|
'title': 'hot perky blonde naked golf',
|
||||||
'like_count': int,
|
'like_count': int,
|
||||||
'dislike_count': int,
|
|
||||||
'comment_count': int,
|
'comment_count': int,
|
||||||
'categories': ['Babe', 'Blonde', 'Erotic', 'Outdoor', 'Softcore', 'Solo'],
|
'categories': ['Babe', 'Blonde', 'Erotic', 'Outdoor', 'Softcore', 'Solo'],
|
||||||
'thumbnail': 're:https?://.*\.jpg$',
|
'thumbnail': 're:https?://.*\.jpg$',
|
||||||
@@ -36,25 +38,29 @@ class DrTuberIE(InfoExtractor):
|
|||||||
r'<source src="([^"]+)"', webpage, 'video URL')
|
r'<source src="([^"]+)"', webpage, 'video URL')
|
||||||
|
|
||||||
title = self._html_search_regex(
|
title = self._html_search_regex(
|
||||||
[r'<p[^>]+class="title_substrate">([^<]+)</p>', r'<title>([^<]+) - \d+'],
|
(r'class="title_watch"[^>]*><p>([^<]+)<',
|
||||||
|
r'<p[^>]+class="title_substrate">([^<]+)</p>',
|
||||||
|
r'<title>([^<]+) - \d+'),
|
||||||
webpage, 'title')
|
webpage, 'title')
|
||||||
|
|
||||||
thumbnail = self._html_search_regex(
|
thumbnail = self._html_search_regex(
|
||||||
r'poster="([^"]+)"',
|
r'poster="([^"]+)"',
|
||||||
webpage, 'thumbnail', fatal=False)
|
webpage, 'thumbnail', fatal=False)
|
||||||
|
|
||||||
def extract_count(id_, name):
|
def extract_count(id_, name, default=NO_DEFAULT):
|
||||||
return str_to_int(self._html_search_regex(
|
return str_to_int(self._html_search_regex(
|
||||||
r'<span[^>]+(?:class|id)="%s"[^>]*>([\d,\.]+)</span>' % id_,
|
r'<span[^>]+(?:class|id)="%s"[^>]*>([\d,\.]+)</span>' % id_,
|
||||||
webpage, '%s count' % name, fatal=False))
|
webpage, '%s count' % name, default=default, fatal=False))
|
||||||
|
|
||||||
like_count = extract_count('rate_likes', 'like')
|
like_count = extract_count('rate_likes', 'like')
|
||||||
dislike_count = extract_count('rate_dislikes', 'dislike')
|
dislike_count = extract_count('rate_dislikes', 'dislike', default=None)
|
||||||
comment_count = extract_count('comments_count', 'comment')
|
comment_count = extract_count('comments_count', 'comment')
|
||||||
|
|
||||||
cats_str = self._search_regex(
|
cats_str = self._search_regex(
|
||||||
r'<div[^>]+class="categories_list">(.+?)</div>', webpage, 'categories', fatal=False)
|
r'<div[^>]+class="categories_list">(.+?)</div>',
|
||||||
categories = [] if not cats_str else re.findall(r'<a title="([^"]+)"', cats_str)
|
webpage, 'categories', fatal=False)
|
||||||
|
categories = [] if not cats_str else re.findall(
|
||||||
|
r'<a title="([^"]+)"', cats_str)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
|
|||||||
@@ -52,11 +52,24 @@ class EaglePlatformIE(InfoExtractor):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _extract_url(webpage):
|
def _extract_url(webpage):
|
||||||
|
# Regular iframe embedding
|
||||||
mobj = re.search(
|
mobj = re.search(
|
||||||
r'<iframe[^>]+src=(["\'])(?P<url>(?:https?:)?//.+?\.media\.eagleplatform\.com/index/player\?.+?)\1',
|
r'<iframe[^>]+src=(["\'])(?P<url>(?:https?:)?//.+?\.media\.eagleplatform\.com/index/player\?.+?)\1',
|
||||||
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/)
|
||||||
|
mobj = re.search(
|
||||||
|
r'''(?xs)
|
||||||
|
<script[^>]+
|
||||||
|
src=(?P<q1>["\'])(?:https?:)?//(?P<host>.+?\.media\.eagleplatform\.com)/player/player\.js(?P=q1)
|
||||||
|
.+?
|
||||||
|
<div[^>]+
|
||||||
|
class=(?P<q2>["\'])eagleplayer(?P=q2)[^>]+
|
||||||
|
data-id=["\'](?P<id>\d+)
|
||||||
|
''', webpage)
|
||||||
|
if mobj is not None:
|
||||||
|
return 'eagleplatform:%(host)s:%(id)s' % mobj.groupdict()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _handle_error(response):
|
def _handle_error(response):
|
||||||
|
|||||||
@@ -6,12 +6,13 @@ import json
|
|||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
|
NO_DEFAULT,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class EllenTVIE(InfoExtractor):
|
class EllenTVIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?(?:ellentv|ellentube)\.com/videos/(?P<id>[a-z0-9_-]+)'
|
_VALID_URL = r'https?://(?:www\.)?(?:ellentv|ellentube)\.com/videos/(?P<id>[a-z0-9_-]+)'
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
'url': 'http://www.ellentv.com/videos/0-ipq1gsai/',
|
'url': 'http://www.ellentv.com/videos/0-ipq1gsai/',
|
||||||
'md5': '4294cf98bc165f218aaa0b89e0fd8042',
|
'md5': '4294cf98bc165f218aaa0b89e0fd8042',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@@ -22,24 +23,47 @@ class EllenTVIE(InfoExtractor):
|
|||||||
'timestamp': 1428035648,
|
'timestamp': 1428035648,
|
||||||
'upload_date': '20150403',
|
'upload_date': '20150403',
|
||||||
'uploader_id': 'batchUser',
|
'uploader_id': 'batchUser',
|
||||||
}
|
},
|
||||||
}
|
}, {
|
||||||
|
# not available via http://widgets.ellentube.com/
|
||||||
|
'url': 'http://www.ellentv.com/videos/1-szkgu2m2/',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '1_szkgu2m2',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': "Ellen's Amazingly Talented Audience",
|
||||||
|
'description': 'md5:86ff1e376ff0d717d7171590e273f0a5',
|
||||||
|
'timestamp': 1255140900,
|
||||||
|
'upload_date': '20091010',
|
||||||
|
'uploader_id': 'ellenkaltura@gmail.com',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
webpage = self._download_webpage(
|
URLS = ('http://widgets.ellentube.com/videos/%s' % video_id, url)
|
||||||
'http://widgets.ellentube.com/videos/%s' % video_id,
|
|
||||||
video_id)
|
|
||||||
|
|
||||||
partner_id = self._search_regex(
|
for num, url_ in enumerate(URLS, 1):
|
||||||
r"var\s+partnerId\s*=\s*'([^']+)", webpage, 'partner id')
|
webpage = self._download_webpage(
|
||||||
|
url_, video_id, fatal=num == len(URLS))
|
||||||
|
|
||||||
kaltura_id = self._search_regex(
|
default = NO_DEFAULT if num == len(URLS) else None
|
||||||
[r'id="kaltura_player_([^"]+)"',
|
|
||||||
r"_wb_entry_id\s*:\s*'([^']+)",
|
partner_id = self._search_regex(
|
||||||
r'data-kaltura-entry-id="([^"]+)'],
|
r"var\s+partnerId\s*=\s*'([^']+)", webpage, 'partner id',
|
||||||
webpage, 'kaltura id')
|
default=default)
|
||||||
|
|
||||||
|
kaltura_id = self._search_regex(
|
||||||
|
[r'id="kaltura_player_([^"]+)"',
|
||||||
|
r"_wb_entry_id\s*:\s*'([^']+)",
|
||||||
|
r'data-kaltura-entry-id="([^"]+)'],
|
||||||
|
webpage, 'kaltura id', default=default)
|
||||||
|
|
||||||
|
if partner_id and kaltura_id:
|
||||||
|
break
|
||||||
|
|
||||||
return self.url_result('kaltura:%s:%s' % (partner_id, kaltura_id), 'Kaltura')
|
return self.url_result('kaltura:%s:%s' % (partner_id, kaltura_id), 'Kaltura')
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ from .common import InfoExtractor
|
|||||||
|
|
||||||
|
|
||||||
class EngadgetIE(InfoExtractor):
|
class EngadgetIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://www.engadget.com/video/(?P<id>\d+)'
|
_VALID_URL = r'https?://www.engadget.com/video/(?P<id>[^/?#]+)'
|
||||||
|
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
|
# video with 5min ID
|
||||||
'url': 'http://www.engadget.com/video/518153925/',
|
'url': 'http://www.engadget.com/video/518153925/',
|
||||||
'md5': 'c6820d4828a5064447a4d9fc73f312c9',
|
'md5': 'c6820d4828a5064447a4d9fc73f312c9',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@@ -15,8 +16,12 @@ class EngadgetIE(InfoExtractor):
|
|||||||
'title': 'Samsung Galaxy Tab Pro 8.4 Review',
|
'title': 'Samsung Galaxy Tab Pro 8.4 Review',
|
||||||
},
|
},
|
||||||
'add_ie': ['FiveMin'],
|
'add_ie': ['FiveMin'],
|
||||||
}
|
}, {
|
||||||
|
# video with vidible ID
|
||||||
|
'url': 'https://www.engadget.com/video/57a28462134aa15a39f0421a/',
|
||||||
|
'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)
|
||||||
return self.url_result('5min:%s' % video_id)
|
return self.url_result('aol-video:%s' % video_id)
|
||||||
|
|||||||
@@ -4,19 +4,23 @@ 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 (
|
||||||
|
encode_base_n,
|
||||||
|
ExtractorError,
|
||||||
|
int_or_none,
|
||||||
parse_duration,
|
parse_duration,
|
||||||
str_to_int,
|
str_to_int,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
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/(?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',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '95008',
|
'id': 'qlDUmNsj6VS',
|
||||||
'display_id': 'Infamous-Tiffany-Teen-Strip-Tease-Video',
|
'display_id': 'Infamous-Tiffany-Teen-Strip-Tease-Video',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Infamous Tiffany Teen Strip Tease Video',
|
'title': 'Infamous Tiffany Teen Strip Tease Video',
|
||||||
@@ -28,34 +32,72 @@ class EpornerIE(InfoExtractor):
|
|||||||
# New (May 2016) URL layout
|
# New (May 2016) URL layout
|
||||||
'url': 'http://www.eporner.com/hd-porn/3YRUtzMcWn0/Star-Wars-XXX-Parody/',
|
'url': 'http://www.eporner.com/hd-porn/3YRUtzMcWn0/Star-Wars-XXX-Parody/',
|
||||||
'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):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
video_id = mobj.group('id')
|
video_id = mobj.group('id')
|
||||||
display_id = mobj.group('display_id')
|
display_id = mobj.group('display_id') or video_id
|
||||||
|
|
||||||
webpage = self._download_webpage(url, display_id)
|
webpage, urlh = self._download_webpage_handle(url, display_id)
|
||||||
title = self._html_search_regex(
|
|
||||||
r'<title>(.*?) - EPORNER', webpage, 'title')
|
|
||||||
|
|
||||||
redirect_url = 'http://www.eporner.com/config5/%s' % video_id
|
video_id = self._match_id(compat_str(urlh.geturl()))
|
||||||
player_code = self._download_webpage(
|
|
||||||
redirect_url, display_id, note='Downloading player config')
|
|
||||||
|
|
||||||
sources = self._search_regex(
|
hash = self._search_regex(
|
||||||
r'(?s)sources\s*:\s*\[\s*({.+?})\s*\]', player_code, 'sources')
|
r'hash\s*:\s*["\']([\da-f]{32})', webpage, 'hash')
|
||||||
|
|
||||||
|
title = self._og_search_title(webpage, default=None) or self._html_search_regex(
|
||||||
|
r'<title>(.+?) - EPORNER', webpage, 'title')
|
||||||
|
|
||||||
|
# Reverse engineered from vjs.js
|
||||||
|
def calc_hash(s):
|
||||||
|
return ''.join((encode_base_n(int(s[lb:lb + 8], 16), 36) for lb in range(0, 32, 8)))
|
||||||
|
|
||||||
|
video = self._download_json(
|
||||||
|
'http://www.eporner.com/xhr/video/%s' % video_id,
|
||||||
|
display_id, note='Downloading video JSON',
|
||||||
|
query={
|
||||||
|
'hash': calc_hash(hash),
|
||||||
|
'device': 'generic',
|
||||||
|
'domain': 'www.eporner.com',
|
||||||
|
'fallback': 'false',
|
||||||
|
})
|
||||||
|
|
||||||
|
if video.get('available') is False:
|
||||||
|
raise ExtractorError(
|
||||||
|
'%s said: %s' % (self.IE_NAME, video['message']), expected=True)
|
||||||
|
|
||||||
|
sources = video['sources']
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
for video_url, format_id in re.findall(r'file\s*:\s*"([^"]+)",\s*label\s*:\s*"([^"]+)"', sources):
|
for kind, formats_dict in sources.items():
|
||||||
fmt = {
|
if not isinstance(formats_dict, dict):
|
||||||
'url': video_url,
|
continue
|
||||||
'format_id': format_id,
|
for format_id, format_dict in formats_dict.items():
|
||||||
}
|
if not isinstance(format_dict, dict):
|
||||||
m = re.search(r'^(\d+)', format_id)
|
continue
|
||||||
if m:
|
src = format_dict.get('src')
|
||||||
fmt['height'] = int(m.group(1))
|
if not isinstance(src, compat_str) or not src.startswith('http'):
|
||||||
formats.append(fmt)
|
continue
|
||||||
|
if kind == 'hls':
|
||||||
|
formats.extend(self._extract_m3u8_formats(
|
||||||
|
src, display_id, 'mp4', entry_protocol='m3u8_native',
|
||||||
|
m3u8_id=kind, fatal=False))
|
||||||
|
else:
|
||||||
|
height = int_or_none(self._search_regex(
|
||||||
|
r'(\d+)[pP]', format_id, 'height', default=None))
|
||||||
|
fps = int_or_none(self._search_regex(
|
||||||
|
r'(\d+)fps', format_id, 'fps', default=None))
|
||||||
|
|
||||||
|
formats.append({
|
||||||
|
'url': src,
|
||||||
|
'format_id': format_id,
|
||||||
|
'height': height,
|
||||||
|
'fps': fps,
|
||||||
|
})
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
duration = parse_duration(self._html_search_meta('duration', webpage))
|
duration = parse_duration(self._html_search_meta('duration', webpage))
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
int_or_none,
|
int_or_none,
|
||||||
@@ -12,23 +10,22 @@ from ..utils import (
|
|||||||
class ExpoTVIE(InfoExtractor):
|
class ExpoTVIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://www\.expotv\.com/videos/[^?#]*/(?P<id>[0-9]+)($|[?#])'
|
_VALID_URL = r'https?://www\.expotv\.com/videos/[^?#]*/(?P<id>[0-9]+)($|[?#])'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.expotv.com/videos/reviews/1/24/LinneCardscom/17561',
|
'url': 'http://www.expotv.com/videos/reviews/3/40/NYX-Butter-lipstick/667916',
|
||||||
'md5': '2985e6d7a392b2f7a05e0ca350fe41d0',
|
'md5': 'fe1d728c3a813ff78f595bc8b7a707a8',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '17561',
|
'id': '667916',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'upload_date': '20060212',
|
'title': 'NYX Butter Lipstick Little Susie',
|
||||||
'title': 'My Favorite Online Scrapbook Store',
|
'description': 'Goes on like butter, but looks better!',
|
||||||
'view_count': int,
|
|
||||||
'description': 'You\'ll find most everything you need at this virtual store front.',
|
|
||||||
'uploader': 'Anna T.',
|
|
||||||
'thumbnail': 're:^https?://.*\.jpg$',
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
'uploader': 'Stephanie S.',
|
||||||
|
'upload_date': '20150520',
|
||||||
|
'view_count': int,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = mobj.group('id')
|
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
player_key = self._search_regex(
|
player_key = self._search_regex(
|
||||||
@@ -66,7 +63,7 @@ class ExpoTVIE(InfoExtractor):
|
|||||||
fatal=False)
|
fatal=False)
|
||||||
upload_date = unified_strdate(self._search_regex(
|
upload_date = unified_strdate(self._search_regex(
|
||||||
r'<h5>Reviewed on ([0-9/.]+)</h5>', webpage, 'upload date',
|
r'<h5>Reviewed on ([0-9/.]+)</h5>', webpage, 'upload date',
|
||||||
fatal=False))
|
fatal=False), day_first=False)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
# flake8: noqa
|
# flake8: noqa
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .abc import ABCIE
|
from .abc import (
|
||||||
|
ABCIE,
|
||||||
|
ABCIViewIE,
|
||||||
|
)
|
||||||
from .abc7news import Abc7NewsIE
|
from .abc7news import Abc7NewsIE
|
||||||
from .abcnews import (
|
from .abcnews import (
|
||||||
AbcNewsIE,
|
AbcNewsIE,
|
||||||
@@ -29,6 +32,7 @@ from .aftonbladet import AftonbladetIE
|
|||||||
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 .animeondemand import AnimeOnDemandIE
|
from .animeondemand import AnimeOnDemandIE
|
||||||
from .anitube import AnitubeIE
|
from .anitube import AnitubeIE
|
||||||
from .anysex import AnySexIE
|
from .anysex import AnySexIE
|
||||||
@@ -44,6 +48,7 @@ from .appletrailers import (
|
|||||||
AppleTrailersSectionIE,
|
AppleTrailersSectionIE,
|
||||||
)
|
)
|
||||||
from .archiveorg import ArchiveOrgIE
|
from .archiveorg import ArchiveOrgIE
|
||||||
|
from .arkena import ArkenaIE
|
||||||
from .ard import (
|
from .ard import (
|
||||||
ARDIE,
|
ARDIE,
|
||||||
ARDMediathekIE,
|
ARDMediathekIE,
|
||||||
@@ -66,6 +71,12 @@ from .atttechchannel import ATTTechChannelIE
|
|||||||
from .audimedia import AudiMediaIE
|
from .audimedia import AudiMediaIE
|
||||||
from .audioboom import AudioBoomIE
|
from .audioboom import AudioBoomIE
|
||||||
from .audiomack import AudiomackIE, AudiomackAlbumIE
|
from .audiomack import AudiomackIE, AudiomackAlbumIE
|
||||||
|
from .awaan import (
|
||||||
|
AWAANIE,
|
||||||
|
AWAANVideoIE,
|
||||||
|
AWAANLiveIE,
|
||||||
|
AWAANSeasonIE,
|
||||||
|
)
|
||||||
from .azubu import AzubuIE, AzubuLiveIE
|
from .azubu import AzubuIE, AzubuLiveIE
|
||||||
from .baidu import BaiduVideoIE
|
from .baidu import BaiduVideoIE
|
||||||
from .bambuser import BambuserIE, BambuserChannelIE
|
from .bambuser import BambuserIE, BambuserChannelIE
|
||||||
@@ -132,6 +143,7 @@ from .ccc import CCCIE
|
|||||||
from .cda import CDAIE
|
from .cda import CDAIE
|
||||||
from .ceskatelevize import CeskaTelevizeIE
|
from .ceskatelevize import CeskaTelevizeIE
|
||||||
from .channel9 import Channel9IE
|
from .channel9 import Channel9IE
|
||||||
|
from .charlierose import CharlieRoseIE
|
||||||
from .chaturbate import ChaturbateIE
|
from .chaturbate import ChaturbateIE
|
||||||
from .chilloutzone import ChilloutzoneIE
|
from .chilloutzone import ChilloutzoneIE
|
||||||
from .chirbit import (
|
from .chirbit import (
|
||||||
@@ -139,9 +151,9 @@ from .chirbit import (
|
|||||||
ChirbitProfileIE,
|
ChirbitProfileIE,
|
||||||
)
|
)
|
||||||
from .cinchcast import CinchcastIE
|
from .cinchcast import CinchcastIE
|
||||||
from .cliprs import ClipRsIE
|
|
||||||
from .clipfish import ClipfishIE
|
from .clipfish import ClipfishIE
|
||||||
from .cliphunter import CliphunterIE
|
from .cliphunter import CliphunterIE
|
||||||
|
from .cliprs import ClipRsIE
|
||||||
from .clipsyndicate import ClipsyndicateIE
|
from .clipsyndicate import ClipsyndicateIE
|
||||||
from .closertotruth import CloserToTruthIE
|
from .closertotruth import CloserToTruthIE
|
||||||
from .cloudy import CloudyIE
|
from .cloudy import CloudyIE
|
||||||
@@ -156,7 +168,12 @@ from .cnn import (
|
|||||||
)
|
)
|
||||||
from .coub import CoubIE
|
from .coub import CoubIE
|
||||||
from .collegerama import CollegeRamaIE
|
from .collegerama import CollegeRamaIE
|
||||||
from .comedycentral import ComedyCentralIE, ComedyCentralShowsIE
|
from .comedycentral import (
|
||||||
|
ComedyCentralIE,
|
||||||
|
ComedyCentralShortnameIE,
|
||||||
|
ComedyCentralTVIE,
|
||||||
|
ToshIE,
|
||||||
|
)
|
||||||
from .comcarcoff import ComCarCoffIE
|
from .comcarcoff import ComCarCoffIE
|
||||||
from .commonmistakes import CommonMistakesIE, UnicodeBOMIE
|
from .commonmistakes import CommonMistakesIE, UnicodeBOMIE
|
||||||
from .commonprotocols import RtmpIE
|
from .commonprotocols import RtmpIE
|
||||||
@@ -189,12 +206,6 @@ from .daum import (
|
|||||||
DaumUserIE,
|
DaumUserIE,
|
||||||
)
|
)
|
||||||
from .dbtv import DBTVIE
|
from .dbtv import DBTVIE
|
||||||
from .dcn import (
|
|
||||||
DCNIE,
|
|
||||||
DCNVideoIE,
|
|
||||||
DCNLiveIE,
|
|
||||||
DCNSeasonIE,
|
|
||||||
)
|
|
||||||
from .dctp import DctpTvIE
|
from .dctp import DctpTvIE
|
||||||
from .deezer import DeezerPlaylistIE
|
from .deezer import DeezerPlaylistIE
|
||||||
from .democracynow import DemocracynowIE
|
from .democracynow import DemocracynowIE
|
||||||
@@ -215,6 +226,7 @@ from .dvtv import DVTVIE
|
|||||||
from .dumpert import DumpertIE
|
from .dumpert import DumpertIE
|
||||||
from .defense import DefenseGouvFrIE
|
from .defense import DefenseGouvFrIE
|
||||||
from .discovery import DiscoveryIE
|
from .discovery import DiscoveryIE
|
||||||
|
from .discoverygo import DiscoveryGoIE
|
||||||
from .dispeak import DigitallySpeakingIE
|
from .dispeak import DigitallySpeakingIE
|
||||||
from .dropbox import DropboxIE
|
from .dropbox import DropboxIE
|
||||||
from .dw import (
|
from .dw import (
|
||||||
@@ -256,6 +268,7 @@ from .fivemin import FiveMinIE
|
|||||||
from .fivetv import FiveTVIE
|
from .fivetv import FiveTVIE
|
||||||
from .fktv import FKTVIE
|
from .fktv import FKTVIE
|
||||||
from .flickr import FlickrIE
|
from .flickr import FlickrIE
|
||||||
|
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
|
||||||
@@ -264,10 +277,7 @@ from .fox import FOXIE
|
|||||||
from .foxgay import FoxgayIE
|
from .foxgay import FoxgayIE
|
||||||
from .foxnews import FoxNewsIE
|
from .foxnews import FoxNewsIE
|
||||||
from .foxsports import FoxSportsIE
|
from .foxsports import FoxSportsIE
|
||||||
from .franceculture import (
|
from .franceculture import FranceCultureIE
|
||||||
FranceCultureIE,
|
|
||||||
FranceCultureEmissionIE,
|
|
||||||
)
|
|
||||||
from .franceinter import FranceInterIE
|
from .franceinter import FranceInterIE
|
||||||
from .francetv import (
|
from .francetv import (
|
||||||
PluzzIE,
|
PluzzIE,
|
||||||
@@ -282,8 +292,8 @@ from .freevideo import FreeVideoIE
|
|||||||
from .funimation import FunimationIE
|
from .funimation import FunimationIE
|
||||||
from .funnyordie import FunnyOrDieIE
|
from .funnyordie import FunnyOrDieIE
|
||||||
from .fusion import FusionIE
|
from .fusion import FusionIE
|
||||||
|
from .fxnetworks import FXNetworksIE
|
||||||
from .gameinformer import GameInformerIE
|
from .gameinformer import GameInformerIE
|
||||||
from .gamekings import GamekingsIE
|
|
||||||
from .gameone import (
|
from .gameone import (
|
||||||
GameOneIE,
|
GameOneIE,
|
||||||
GameOnePlaylistIE,
|
GameOnePlaylistIE,
|
||||||
@@ -304,7 +314,6 @@ from .globo import (
|
|||||||
)
|
)
|
||||||
from .godtube import GodTubeIE
|
from .godtube import GodTubeIE
|
||||||
from .godtv import GodTVIE
|
from .godtv import GodTVIE
|
||||||
from .goldenmoustache import GoldenMoustacheIE
|
|
||||||
from .golem import GolemIE
|
from .golem import GolemIE
|
||||||
from .googledrive import GoogleDriveIE
|
from .googledrive import GoogleDriveIE
|
||||||
from .googleplus import GooglePlusIE
|
from .googleplus import GooglePlusIE
|
||||||
@@ -319,6 +328,10 @@ from .heise import HeiseIE
|
|||||||
from .hellporno import HellPornoIE
|
from .hellporno import HellPornoIE
|
||||||
from .helsinki import HelsinkiIE
|
from .helsinki import HelsinkiIE
|
||||||
from .hentaistigma import HentaiStigmaIE
|
from .hentaistigma import HentaiStigmaIE
|
||||||
|
from .hgtv import (
|
||||||
|
HGTVIE,
|
||||||
|
HGTVComShowIE,
|
||||||
|
)
|
||||||
from .historicfilms import HistoricFilmsIE
|
from .historicfilms import HistoricFilmsIE
|
||||||
from .hitbox import HitboxIE, HitboxLiveIE
|
from .hitbox import HitboxIE, HitboxLiveIE
|
||||||
from .hornbunny import HornBunnyIE
|
from .hornbunny import HornBunnyIE
|
||||||
@@ -326,6 +339,10 @@ from .hotnewhiphop import HotNewHipHopIE
|
|||||||
from .hotstar import HotStarIE
|
from .hotstar import HotStarIE
|
||||||
from .howcast import HowcastIE
|
from .howcast import HowcastIE
|
||||||
from .howstuffworks import HowStuffWorksIE
|
from .howstuffworks import HowStuffWorksIE
|
||||||
|
from .hrti import (
|
||||||
|
HRTiIE,
|
||||||
|
HRTiPlaylistIE,
|
||||||
|
)
|
||||||
from .huffpost import HuffPostIE
|
from .huffpost import HuffPostIE
|
||||||
from .hypem import HypemIE
|
from .hypem import HypemIE
|
||||||
from .iconosquare import IconosquareIE
|
from .iconosquare import IconosquareIE
|
||||||
@@ -364,6 +381,7 @@ from .jove import JoveIE
|
|||||||
from .jwplatform import JWPlatformIE
|
from .jwplatform import JWPlatformIE
|
||||||
from .jpopsukitv import JpopsukiIE
|
from .jpopsukitv import JpopsukiIE
|
||||||
from .kaltura import KalturaIE
|
from .kaltura import KalturaIE
|
||||||
|
from .kamcord import KamcordIE
|
||||||
from .kanalplay import KanalPlayIE
|
from .kanalplay import KanalPlayIE
|
||||||
from .kankan import KankanIE
|
from .kankan import KankanIE
|
||||||
from .karaoketv import KaraoketvIE
|
from .karaoketv import KaraoketvIE
|
||||||
@@ -387,6 +405,10 @@ from .kuwo import (
|
|||||||
)
|
)
|
||||||
from .la7 import LA7IE
|
from .la7 import LA7IE
|
||||||
from .laola1tv import Laola1TvIE
|
from .laola1tv import Laola1TvIE
|
||||||
|
from .lcp import (
|
||||||
|
LcpPlayIE,
|
||||||
|
LcpIE,
|
||||||
|
)
|
||||||
from .learnr import LearnrIE
|
from .learnr import LearnrIE
|
||||||
from .lecture2go import Lecture2GoIE
|
from .lecture2go import Lecture2GoIE
|
||||||
from .lemonde import LemondeIE
|
from .lemonde import LemondeIE
|
||||||
@@ -465,7 +487,6 @@ from .msn import MSNIE
|
|||||||
from .mtv import (
|
from .mtv import (
|
||||||
MTVIE,
|
MTVIE,
|
||||||
MTVServicesEmbeddedIE,
|
MTVServicesEmbeddedIE,
|
||||||
MTVIggyIE,
|
|
||||||
MTVDEIE,
|
MTVDEIE,
|
||||||
)
|
)
|
||||||
from .muenchentv import MuenchenTVIE
|
from .muenchentv import MuenchenTVIE
|
||||||
@@ -477,8 +498,9 @@ from .myvi import MyviIE
|
|||||||
from .myvideo import MyVideoIE
|
from .myvideo import MyVideoIE
|
||||||
from .myvidster import MyVidsterIE
|
from .myvidster import MyVidsterIE
|
||||||
from .nationalgeographic import (
|
from .nationalgeographic import (
|
||||||
|
NationalGeographicVideoIE,
|
||||||
NationalGeographicIE,
|
NationalGeographicIE,
|
||||||
NationalGeographicChannelIE,
|
NationalGeographicEpisodeGuideIE,
|
||||||
)
|
)
|
||||||
from .naver import NaverIE
|
from .naver import NaverIE
|
||||||
from .nba import NBAIE
|
from .nba import NBAIE
|
||||||
@@ -515,7 +537,6 @@ from .nextmedia import (
|
|||||||
NextMediaActionNewsIE,
|
NextMediaActionNewsIE,
|
||||||
AppleDailyIE,
|
AppleDailyIE,
|
||||||
)
|
)
|
||||||
from .nextmovie import NextMovieIE
|
|
||||||
from .nfb import NFBIE
|
from .nfb import NFBIE
|
||||||
from .nfl import NFLIE
|
from .nfl import NFLIE
|
||||||
from .nhl import (
|
from .nhl import (
|
||||||
@@ -531,6 +552,8 @@ from .nick import (
|
|||||||
from .niconico import NiconicoIE, NiconicoPlaylistIE
|
from .niconico import NiconicoIE, NiconicoPlaylistIE
|
||||||
from .ninecninemedia import NineCNineMediaIE
|
from .ninecninemedia import NineCNineMediaIE
|
||||||
from .ninegag import NineGagIE
|
from .ninegag import NineGagIE
|
||||||
|
from .ninenow import NineNowIE
|
||||||
|
from .nintendo import NintendoIE
|
||||||
from .noco import NocoIE
|
from .noco import NocoIE
|
||||||
from .normalboots import NormalbootsIE
|
from .normalboots import NormalbootsIE
|
||||||
from .nosvideo import NosVideoIE
|
from .nosvideo import NosVideoIE
|
||||||
@@ -575,8 +598,13 @@ from .nytimes import (
|
|||||||
NYTimesArticleIE,
|
NYTimesArticleIE,
|
||||||
)
|
)
|
||||||
from .nuvid import NuvidIE
|
from .nuvid import NuvidIE
|
||||||
|
from .odatv import OdaTVIE
|
||||||
from .odnoklassniki import OdnoklassnikiIE
|
from .odnoklassniki import OdnoklassnikiIE
|
||||||
from .oktoberfesttv import OktoberfestTVIE
|
from .oktoberfesttv import OktoberfestTVIE
|
||||||
|
from .onet import (
|
||||||
|
OnetIE,
|
||||||
|
OnetChannelIE,
|
||||||
|
)
|
||||||
from .onionstudios import OnionStudiosIE
|
from .onionstudios import OnionStudiosIE
|
||||||
from .ooyala import (
|
from .ooyala import (
|
||||||
OoyalaIE,
|
OoyalaIE,
|
||||||
@@ -615,8 +643,10 @@ from .pluralsight import (
|
|||||||
PluralsightCourseIE,
|
PluralsightCourseIE,
|
||||||
)
|
)
|
||||||
from .podomatic import PodomaticIE
|
from .podomatic import PodomaticIE
|
||||||
|
from .pokemon import PokemonIE
|
||||||
from .polskieradio import PolskieRadioIE
|
from .polskieradio import PolskieRadioIE
|
||||||
from .porn91 import Porn91IE
|
from .porn91 import Porn91IE
|
||||||
|
from .porncom import PornComIE
|
||||||
from .pornhd import PornHdIE
|
from .pornhd import PornHdIE
|
||||||
from .pornhub import (
|
from .pornhub import (
|
||||||
PornHubIE,
|
PornHubIE,
|
||||||
@@ -670,16 +700,19 @@ from .rice import RICEIE
|
|||||||
from .ringtv import RingTVIE
|
from .ringtv import RingTVIE
|
||||||
from .ro220 import Ro220IE
|
from .ro220 import Ro220IE
|
||||||
from .rockstargames import RockstarGamesIE
|
from .rockstargames import RockstarGamesIE
|
||||||
|
from .roosterteeth import RoosterTeethIE
|
||||||
from .rottentomatoes import RottenTomatoesIE
|
from .rottentomatoes import RottenTomatoesIE
|
||||||
from .roxwel import RoxwelIE
|
from .roxwel import RoxwelIE
|
||||||
|
from .rozhlas import RozhlasIE
|
||||||
from .rtbf import RTBFIE
|
from .rtbf import RTBFIE
|
||||||
from .rte import RteIE, RteRadioIE
|
from .rte import RteIE, RteRadioIE
|
||||||
from .rtlnl import RtlNlIE
|
from .rtlnl import RtlNlIE
|
||||||
from .rtl2 import RTL2IE
|
from .rtl2 import RTL2IE
|
||||||
from .rtp import RTPIE
|
from .rtp import RTPIE
|
||||||
from .rts import RTSIE
|
from .rts import RTSIE
|
||||||
from .rtve import RTVEALaCartaIE, RTVELiveIE, RTVEInfantilIE
|
from .rtve import RTVEALaCartaIE, RTVELiveIE, RTVEInfantilIE, RTVELiveIE, RTVETelevisionIE
|
||||||
from .rtvnh import RTVNHIE
|
from .rtvnh import RTVNHIE
|
||||||
|
from .rudo import RudoIE
|
||||||
from .ruhd import RUHDIE
|
from .ruhd import RUHDIE
|
||||||
from .ruleporn import RulePornIE
|
from .ruleporn import RulePornIE
|
||||||
from .rutube import (
|
from .rutube import (
|
||||||
@@ -730,6 +763,7 @@ from .smotri import (
|
|||||||
)
|
)
|
||||||
from .snotr import SnotrIE
|
from .snotr import SnotrIE
|
||||||
from .sohu import SohuIE
|
from .sohu import SohuIE
|
||||||
|
from .sonyliv import SonyLIVIE
|
||||||
from .soundcloud import (
|
from .soundcloud import (
|
||||||
SoundcloudIE,
|
SoundcloudIE,
|
||||||
SoundcloudSetIE,
|
SoundcloudSetIE,
|
||||||
@@ -769,6 +803,7 @@ from .srmediathek import SRMediathekIE
|
|||||||
from .ssa import SSAIE
|
from .ssa import SSAIE
|
||||||
from .stanfordoc import StanfordOpenClassroomIE
|
from .stanfordoc import StanfordOpenClassroomIE
|
||||||
from .steam import SteamIE
|
from .steam import SteamIE
|
||||||
|
from .streamable import StreamableIE
|
||||||
from .streamcloud import StreamcloudIE
|
from .streamcloud import StreamcloudIE
|
||||||
from .streamcz import StreamCZIE
|
from .streamcz import StreamCZIE
|
||||||
from .streetvoice import StreetVoiceIE
|
from .streetvoice import StreetVoiceIE
|
||||||
@@ -784,7 +819,6 @@ from .tagesschau import (
|
|||||||
TagesschauPlayerIE,
|
TagesschauPlayerIE,
|
||||||
TagesschauIE,
|
TagesschauIE,
|
||||||
)
|
)
|
||||||
from .tapely import TapelyIE
|
|
||||||
from .tass import TassIE
|
from .tass import TassIE
|
||||||
from .tdslifeway import TDSLifewayIE
|
from .tdslifeway import TDSLifewayIE
|
||||||
from .teachertube import (
|
from .teachertube import (
|
||||||
@@ -868,10 +902,14 @@ from .tvc import (
|
|||||||
from .tvigle import TvigleIE
|
from .tvigle import TvigleIE
|
||||||
from .tvland import TVLandIE
|
from .tvland import TVLandIE
|
||||||
from .tvp import (
|
from .tvp import (
|
||||||
|
TVPEmbedIE,
|
||||||
TVPIE,
|
TVPIE,
|
||||||
TVPSeriesIE,
|
TVPSeriesIE,
|
||||||
)
|
)
|
||||||
from .tvplay import TVPlayIE
|
from .tvplay import (
|
||||||
|
TVPlayIE,
|
||||||
|
ViafreeIE,
|
||||||
|
)
|
||||||
from .tweakers import TweakersIE
|
from .tweakers import TweakersIE
|
||||||
from .twentyfourvideo import TwentyFourVideoIE
|
from .twentyfourvideo import TwentyFourVideoIE
|
||||||
from .twentymin import TwentyMinutenIE
|
from .twentymin import TwentyMinutenIE
|
||||||
@@ -900,6 +938,11 @@ from .udemy import (
|
|||||||
from .udn import UDNEmbedIE
|
from .udn import UDNEmbedIE
|
||||||
from .digiteka import DigitekaIE
|
from .digiteka import DigitekaIE
|
||||||
from .unistra import UnistraIE
|
from .unistra import UnistraIE
|
||||||
|
from .uol import UOLIE
|
||||||
|
from .uplynk import (
|
||||||
|
UplynkIE,
|
||||||
|
UplynkPreplayIE,
|
||||||
|
)
|
||||||
from .urort import UrortIE
|
from .urort import UrortIE
|
||||||
from .urplay import URPlayIE
|
from .urplay import URPlayIE
|
||||||
from .usatoday import USATodayIE
|
from .usatoday import USATodayIE
|
||||||
@@ -928,6 +971,7 @@ from .vice import (
|
|||||||
ViceIE,
|
ViceIE,
|
||||||
ViceShowIE,
|
ViceShowIE,
|
||||||
)
|
)
|
||||||
|
from .viceland import VicelandIE
|
||||||
from .vidbit import VidbitIE
|
from .vidbit import VidbitIE
|
||||||
from .viddler import ViddlerIE
|
from .viddler import ViddlerIE
|
||||||
from .videodetective import VideoDetectiveIE
|
from .videodetective import VideoDetectiveIE
|
||||||
@@ -977,9 +1021,11 @@ from .viki import (
|
|||||||
from .vk import (
|
from .vk import (
|
||||||
VKIE,
|
VKIE,
|
||||||
VKUserVideosIE,
|
VKUserVideosIE,
|
||||||
|
VKWallPostIE,
|
||||||
)
|
)
|
||||||
from .vlive import VLiveIE
|
from .vlive import VLiveIE
|
||||||
from .vodlocker import VodlockerIE
|
from .vodlocker import VodlockerIE
|
||||||
|
from .vodplatform import VODPlatformIE
|
||||||
from .voicerepublic import VoiceRepublicIE
|
from .voicerepublic import VoiceRepublicIE
|
||||||
from .voxmedia import VoxMediaIE
|
from .voxmedia import VoxMediaIE
|
||||||
from .vporn import VpornIE
|
from .vporn import VpornIE
|
||||||
@@ -1062,6 +1108,7 @@ from .youtube import (
|
|||||||
YoutubeSearchDateIE,
|
YoutubeSearchDateIE,
|
||||||
YoutubeSearchIE,
|
YoutubeSearchIE,
|
||||||
YoutubeSearchURLIE,
|
YoutubeSearchURLIE,
|
||||||
|
YoutubeSharedVideoIE,
|
||||||
YoutubeShowIE,
|
YoutubeShowIE,
|
||||||
YoutubeSubscriptionsIE,
|
YoutubeSubscriptionsIE,
|
||||||
YoutubeTruncatedIDIE,
|
YoutubeTruncatedIDIE,
|
||||||
@@ -1071,8 +1118,4 @@ from .youtube import (
|
|||||||
)
|
)
|
||||||
from .zapiks import ZapiksIE
|
from .zapiks import ZapiksIE
|
||||||
from .zdf import ZDFIE, ZDFChannelIE
|
from .zdf import ZDFIE, ZDFChannelIE
|
||||||
from .zingmp3 import (
|
from .zingmp3 import ZingMp3IE
|
||||||
ZingMp3SongIE,
|
|
||||||
ZingMp3AlbumIE,
|
|
||||||
)
|
|
||||||
from .zippcast import ZippCastIE
|
|
||||||
|
|||||||
@@ -1,20 +1,14 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
from ..utils import str_to_int
|
||||||
|
from .keezmovies import KeezMoviesIE
|
||||||
from .common import InfoExtractor
|
|
||||||
from ..utils import (
|
|
||||||
int_or_none,
|
|
||||||
sanitized_Request,
|
|
||||||
str_to_int,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ExtremeTubeIE(InfoExtractor):
|
class ExtremeTubeIE(KeezMoviesIE):
|
||||||
_VALID_URL = r'https?://(?:www\.)?extremetube\.com/(?:[^/]+/)?video/(?P<id>[^/#?&]+)'
|
_VALID_URL = r'https?://(?:www\.)?extremetube\.com/(?:[^/]+/)?video/(?P<id>[^/#?&]+)'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.extremetube.com/video/music-video-14-british-euro-brit-european-cumshots-swallow-652431',
|
'url': 'http://www.extremetube.com/video/music-video-14-british-euro-brit-european-cumshots-swallow-652431',
|
||||||
'md5': '344d0c6d50e2f16b06e49ca011d8ac69',
|
'md5': '1fb9228f5e3332ec8c057d6ac36f33e0',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'music-video-14-british-euro-brit-european-cumshots-swallow-652431',
|
'id': 'music-video-14-british-euro-brit-european-cumshots-swallow-652431',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
@@ -35,58 +29,22 @@ class ExtremeTubeIE(InfoExtractor):
|
|||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
webpage, info = self._extract_info(url)
|
||||||
|
|
||||||
req = sanitized_Request(url)
|
if not info['title']:
|
||||||
req.add_header('Cookie', 'age_verified=1')
|
info['title'] = self._search_regex(
|
||||||
webpage = self._download_webpage(req, video_id)
|
r'<h1[^>]+title="([^"]+)"[^>]*>', webpage, 'title')
|
||||||
|
|
||||||
video_title = self._html_search_regex(
|
|
||||||
r'<h1 [^>]*?title="([^"]+)"[^>]*>', webpage, 'title')
|
|
||||||
uploader = self._html_search_regex(
|
uploader = self._html_search_regex(
|
||||||
r'Uploaded by:\s*</strong>\s*(.+?)\s*</div>',
|
r'Uploaded by:\s*</strong>\s*(.+?)\s*</div>',
|
||||||
webpage, 'uploader', fatal=False)
|
webpage, 'uploader', fatal=False)
|
||||||
view_count = str_to_int(self._html_search_regex(
|
view_count = str_to_int(self._search_regex(
|
||||||
r'Views:\s*</strong>\s*<span>([\d,\.]+)</span>',
|
r'Views:\s*</strong>\s*<span>([\d,\.]+)</span>',
|
||||||
webpage, 'view count', fatal=False))
|
webpage, 'view count', fatal=False))
|
||||||
|
|
||||||
flash_vars = self._parse_json(
|
info.update({
|
||||||
self._search_regex(
|
|
||||||
r'var\s+flashvars\s*=\s*({.+?});', webpage, 'flash vars'),
|
|
||||||
video_id)
|
|
||||||
|
|
||||||
formats = []
|
|
||||||
for quality_key, video_url in flash_vars.items():
|
|
||||||
height = int_or_none(self._search_regex(
|
|
||||||
r'quality_(\d+)[pP]$', quality_key, 'height', default=None))
|
|
||||||
if not height:
|
|
||||||
continue
|
|
||||||
f = {
|
|
||||||
'url': video_url,
|
|
||||||
}
|
|
||||||
mobj = re.search(
|
|
||||||
r'/(?P<height>\d{3,4})[pP]_(?P<bitrate>\d+)[kK]_\d+', video_url)
|
|
||||||
if mobj:
|
|
||||||
height = int(mobj.group('height'))
|
|
||||||
bitrate = int(mobj.group('bitrate'))
|
|
||||||
f.update({
|
|
||||||
'format_id': '%dp-%dk' % (height, bitrate),
|
|
||||||
'height': height,
|
|
||||||
'tbr': bitrate,
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
f.update({
|
|
||||||
'format_id': '%dp' % height,
|
|
||||||
'height': height,
|
|
||||||
})
|
|
||||||
formats.append(f)
|
|
||||||
self._sort_formats(formats)
|
|
||||||
|
|
||||||
return {
|
|
||||||
'id': video_id,
|
|
||||||
'title': video_title,
|
|
||||||
'formats': formats,
|
|
||||||
'uploader': uploader,
|
'uploader': uploader,
|
||||||
'view_count': view_count,
|
'view_count': view_count,
|
||||||
'age_limit': 18,
|
})
|
||||||
}
|
|
||||||
|
return info
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class FacebookIE(InfoExtractor):
|
|||||||
_VALID_URL = r'''(?x)
|
_VALID_URL = r'''(?x)
|
||||||
(?:
|
(?:
|
||||||
https?://
|
https?://
|
||||||
(?:\w+\.)?facebook\.com/
|
(?:[\w-]+\.)?facebook\.com/
|
||||||
(?:[^#]*?\#!/)?
|
(?:[^#]*?\#!/)?
|
||||||
(?:
|
(?:
|
||||||
(?:
|
(?:
|
||||||
@@ -127,8 +127,26 @@ class FacebookIE(InfoExtractor):
|
|||||||
}, {
|
}, {
|
||||||
'url': 'https://www.facebook.com/groups/164828000315060/permalink/764967300301124/',
|
'url': 'https://www.facebook.com/groups/164828000315060/permalink/764967300301124/',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://zh-hk.facebook.com/peoplespower/videos/1135894589806027/',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _extract_url(webpage):
|
||||||
|
mobj = re.search(
|
||||||
|
r'<iframe[^>]+?src=(["\'])(?P<url>https://www\.facebook\.com/video/embed.+?)\1', webpage)
|
||||||
|
if mobj is not None:
|
||||||
|
return mobj.group('url')
|
||||||
|
|
||||||
|
# Facebook API embed
|
||||||
|
# see https://developers.facebook.com/docs/plugins/embedded-video-player
|
||||||
|
mobj = re.search(r'''(?x)<div[^>]+
|
||||||
|
class=(?P<q1>[\'"])[^\'"]*\bfb-(?:video|post)\b[^\'"]*(?P=q1)[^>]+
|
||||||
|
data-href=(?P<q2>[\'"])(?P<url>(?:https?:)?//(?:www\.)?facebook.com/.+?)(?P=q2)''', webpage)
|
||||||
|
if mobj is not None:
|
||||||
|
return mobj.group('url')
|
||||||
|
|
||||||
def _login(self):
|
def _login(self):
|
||||||
(useremail, password) = self._get_login_info()
|
(useremail, password) = self._get_login_info()
|
||||||
if useremail is None:
|
if useremail is None:
|
||||||
@@ -204,12 +222,25 @@ class FacebookIE(InfoExtractor):
|
|||||||
|
|
||||||
BEFORE = '{swf.addParam(param[0], param[1]);});'
|
BEFORE = '{swf.addParam(param[0], param[1]);});'
|
||||||
AFTER = '.forEach(function(variable) {swf.addVariable(variable[0], variable[1]);});'
|
AFTER = '.forEach(function(variable) {swf.addVariable(variable[0], variable[1]);});'
|
||||||
m = re.search(re.escape(BEFORE) + '(?:\n|\\\\n)(.*?)' + re.escape(AFTER), webpage)
|
PATTERN = re.escape(BEFORE) + '(?:\n|\\\\n)(.*?)' + re.escape(AFTER)
|
||||||
if m:
|
|
||||||
swf_params = m.group(1).replace('\\\\', '\\').replace('\\"', '"')
|
for m in re.findall(PATTERN, webpage):
|
||||||
|
swf_params = m.replace('\\\\', '\\').replace('\\"', '"')
|
||||||
data = dict(json.loads(swf_params))
|
data = dict(json.loads(swf_params))
|
||||||
params_raw = compat_urllib_parse_unquote(data['params'])
|
params_raw = compat_urllib_parse_unquote(data['params'])
|
||||||
video_data = json.loads(params_raw)['video_data']
|
video_data_candidate = json.loads(params_raw)['video_data']
|
||||||
|
for _, f in video_data_candidate.items():
|
||||||
|
if not f:
|
||||||
|
continue
|
||||||
|
if isinstance(f, dict):
|
||||||
|
f = [f]
|
||||||
|
if not isinstance(f, list):
|
||||||
|
continue
|
||||||
|
if f[0].get('video_id') == video_id:
|
||||||
|
video_data = video_data_candidate
|
||||||
|
break
|
||||||
|
if video_data:
|
||||||
|
break
|
||||||
|
|
||||||
def video_data_list2dict(video_data):
|
def video_data_list2dict(video_data):
|
||||||
ret = {}
|
ret = {}
|
||||||
|
|||||||
@@ -2,44 +2,40 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import compat_xpath
|
from ..compat import compat_urlparse
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
int_or_none,
|
int_or_none,
|
||||||
qualities,
|
qualities,
|
||||||
unified_strdate,
|
unified_strdate,
|
||||||
xpath_attr,
|
|
||||||
xpath_element,
|
|
||||||
xpath_text,
|
|
||||||
xpath_with_ns,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class FirstTVIE(InfoExtractor):
|
class FirstTVIE(InfoExtractor):
|
||||||
IE_NAME = '1tv'
|
IE_NAME = '1tv'
|
||||||
IE_DESC = 'Первый канал'
|
IE_DESC = 'Первый канал'
|
||||||
_VALID_URL = r'https?://(?:www\.)?1tv\.ru/(?:[^/]+/)+p?(?P<id>\d+)'
|
_VALID_URL = r'https?://(?:www\.)?1tv\.ru/(?:[^/]+/)+(?P<id>[^/?#]+)'
|
||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
# single format via video_materials.json API
|
# single format
|
||||||
'url': 'http://www.1tv.ru/prj/inprivate/vypusk/35930',
|
'url': 'http://www.1tv.ru/shows/naedine-so-vsemi/vypuski/gost-lyudmila-senchina-naedine-so-vsemi-vypusk-ot-12-02-2015',
|
||||||
'md5': '82a2777648acae812d58b3f5bd42882b',
|
'md5': 'a1b6b60d530ebcf8daacf4565762bbaf',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '35930',
|
'id': '40049',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Гость Людмила Сенчина. Наедине со всеми. Выпуск от 12.02.2015',
|
'title': 'Гость Людмила Сенчина. Наедине со всеми. Выпуск от 12.02.2015',
|
||||||
'description': 'md5:357933adeede13b202c7c21f91b871b2',
|
'description': 'md5:36a39c1d19618fec57d12efe212a8370',
|
||||||
'thumbnail': 're:^https?://.*\.(?:jpg|JPG)$',
|
'thumbnail': 're:^https?://.*\.(?:jpg|JPG)$',
|
||||||
'upload_date': '20150212',
|
'upload_date': '20150212',
|
||||||
'duration': 2694,
|
'duration': 2694,
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
# multiple formats via video_materials.json API
|
# multiple formats
|
||||||
'url': 'http://www.1tv.ru/video_archive/projects/dobroeutro/p113641',
|
'url': 'http://www.1tv.ru/shows/dobroe-utro/pro-zdorove/vesennyaya-allergiya-dobroe-utro-fragment-vypuska-ot-07042016',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '113641',
|
'id': '364746',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Весенняя аллергия. Доброе утро. Фрагмент выпуска от 07.04.2016',
|
'title': 'Весенняя аллергия. Доброе утро. Фрагмент выпуска от 07.04.2016',
|
||||||
'description': 'md5:8dcebb3dded0ff20fade39087fd1fee2',
|
'description': 'md5:a242eea0031fd180a4497d52640a9572',
|
||||||
'thumbnail': 're:^https?://.*\.(?:jpg|JPG)$',
|
'thumbnail': 're:^https?://.*\.(?:jpg|JPG)$',
|
||||||
'upload_date': '20160407',
|
'upload_date': '20160407',
|
||||||
'duration': 179,
|
'duration': 179,
|
||||||
@@ -48,84 +44,47 @@ class FirstTVIE(InfoExtractor):
|
|||||||
'params': {
|
'params': {
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
},
|
},
|
||||||
}, {
|
|
||||||
# single format only available via ONE_ONLINE_VIDEOS.archive_single_xml API
|
|
||||||
'url': 'http://www.1tv.ru/video_archive/series/f7552/p47038',
|
|
||||||
'md5': '519d306c5b5669761fd8906c39dbee23',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '47038',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': '"Побег". Второй сезон. 3 серия',
|
|
||||||
'description': 'md5:3abf8f6b9bce88201c33e9a3d794a00b',
|
|
||||||
'thumbnail': 're:^https?://.*\.(?:jpg|JPG)$',
|
|
||||||
'upload_date': '20120516',
|
|
||||||
'duration': 3080,
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
'url': 'http://www.1tv.ru/videoarchive/9967',
|
|
||||||
'only_matching': True,
|
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
display_id = self._match_id(url)
|
||||||
|
|
||||||
# Videos with multiple formats only available via this API
|
webpage = self._download_webpage(url, display_id)
|
||||||
video = self._download_json(
|
playlist_url = compat_urlparse.urljoin(url, self._search_regex(
|
||||||
'http://www.1tv.ru/video_materials.json?legacy_id=%s' % video_id,
|
r'data-playlist-url="([^"]+)', webpage, 'playlist url'))
|
||||||
video_id, fatal=False)
|
|
||||||
|
|
||||||
description, thumbnail, upload_date, duration = [None] * 4
|
|
||||||
|
|
||||||
if video:
|
|
||||||
item = video[0]
|
|
||||||
title = item['title']
|
|
||||||
quality = qualities(('ld', 'sd', 'hd', ))
|
|
||||||
formats = [{
|
|
||||||
'url': f['src'],
|
|
||||||
'format_id': f.get('name'),
|
|
||||||
'quality': quality(f.get('name')),
|
|
||||||
} for f in item['mbr'] if f.get('src')]
|
|
||||||
thumbnail = item.get('poster')
|
|
||||||
else:
|
|
||||||
# Some videos are not available via video_materials.json
|
|
||||||
video = self._download_xml(
|
|
||||||
'http://www.1tv.ru/owa/win/ONE_ONLINE_VIDEOS.archive_single_xml?pid=%s' % video_id,
|
|
||||||
video_id)
|
|
||||||
|
|
||||||
NS_MAP = {
|
|
||||||
'media': 'http://search.yahoo.com/mrss/',
|
|
||||||
}
|
|
||||||
|
|
||||||
item = xpath_element(video, './channel/item', fatal=True)
|
|
||||||
title = xpath_text(item, './title', fatal=True)
|
|
||||||
formats = [{
|
|
||||||
'url': content.attrib['url'],
|
|
||||||
} for content in item.findall(
|
|
||||||
compat_xpath(xpath_with_ns('./media:content', NS_MAP))) if content.attrib.get('url')]
|
|
||||||
thumbnail = xpath_attr(
|
|
||||||
item, xpath_with_ns('./media:thumbnail', NS_MAP), 'url')
|
|
||||||
|
|
||||||
|
item = self._download_json(playlist_url, display_id)[0]
|
||||||
|
video_id = item['id']
|
||||||
|
quality = qualities(('ld', 'sd', 'hd', ))
|
||||||
|
formats = []
|
||||||
|
for f in item.get('mbr', []):
|
||||||
|
src = f.get('src')
|
||||||
|
if not src:
|
||||||
|
continue
|
||||||
|
fname = f.get('name')
|
||||||
|
formats.append({
|
||||||
|
'url': src,
|
||||||
|
'format_id': fname,
|
||||||
|
'quality': quality(fname),
|
||||||
|
})
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id, 'Downloading page', fatal=False)
|
title = self._html_search_regex(
|
||||||
if webpage:
|
(r'<div class="tv_translation">\s*<h1><a href="[^"]+">([^<]*)</a>',
|
||||||
title = self._html_search_regex(
|
r"'title'\s*:\s*'([^']+)'"),
|
||||||
(r'<div class="tv_translation">\s*<h1><a href="[^"]+">([^<]*)</a>',
|
webpage, 'title', default=None) or item['title']
|
||||||
r"'title'\s*:\s*'([^']+)'"),
|
description = self._html_search_regex(
|
||||||
webpage, 'title', default=None) or title
|
r'<div class="descr">\s*<div> </div>\s*<p>([^<]*)</p></div>',
|
||||||
description = self._html_search_regex(
|
webpage, 'description', default=None) or self._html_search_meta(
|
||||||
r'<div class="descr">\s*<div> </div>\s*<p>([^<]*)</p></div>',
|
'description', webpage, 'description')
|
||||||
webpage, 'description', default=None) or self._html_search_meta(
|
duration = int_or_none(self._html_search_meta(
|
||||||
'description', webpage, 'description')
|
'video:duration', webpage, 'video duration', fatal=False))
|
||||||
thumbnail = thumbnail or self._og_search_thumbnail(webpage)
|
upload_date = unified_strdate(self._html_search_meta(
|
||||||
duration = int_or_none(self._html_search_meta(
|
'ya:ovs:upload_date', webpage, 'upload date', fatal=False))
|
||||||
'video:duration', webpage, 'video duration', fatal=False))
|
|
||||||
upload_date = unified_strdate(self._html_search_meta(
|
|
||||||
'ya:ovs:upload_date', webpage, 'upload date', fatal=False))
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'thumbnail': thumbnail,
|
'thumbnail': item.get('poster') or self._og_search_thumbnail(webpage),
|
||||||
'title': title,
|
'title': title,
|
||||||
'description': description,
|
'description': description,
|
||||||
'upload_date': upload_date,
|
'upload_date': upload_date,
|
||||||
|
|||||||
@@ -1,24 +1,11 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
|
||||||
compat_parse_qs,
|
|
||||||
compat_urllib_parse_urlencode,
|
|
||||||
compat_urllib_parse_urlparse,
|
|
||||||
compat_urlparse,
|
|
||||||
)
|
|
||||||
from ..utils import (
|
|
||||||
ExtractorError,
|
|
||||||
parse_duration,
|
|
||||||
replace_extension,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class FiveMinIE(InfoExtractor):
|
class FiveMinIE(InfoExtractor):
|
||||||
IE_NAME = '5min'
|
IE_NAME = '5min'
|
||||||
_VALID_URL = r'(?:5min:(?P<id>\d+)(?::(?P<sid>\d+))?|https?://[^/]*?5min\.com/Scripts/PlayerSeed\.js\?(?P<query>.*))'
|
_VALID_URL = r'(?:5min:|https?://(?:[^/]*?5min\.com/|delivery\.vidible\.tv/aol)(?:(?:Scripts/PlayerSeed\.js|playerseed/?)?\?.*?playList=)?)(?P<id>\d+)'
|
||||||
|
|
||||||
_TESTS = [
|
_TESTS = [
|
||||||
{
|
{
|
||||||
@@ -29,8 +16,16 @@ class FiveMinIE(InfoExtractor):
|
|||||||
'id': '518013791',
|
'id': '518013791',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'iPad Mini with Retina Display Review',
|
'title': 'iPad Mini with Retina Display Review',
|
||||||
|
'description': 'iPad mini with Retina Display review',
|
||||||
'duration': 177,
|
'duration': 177,
|
||||||
|
'uploader': 'engadget',
|
||||||
|
'upload_date': '20131115',
|
||||||
|
'timestamp': 1384515288,
|
||||||
},
|
},
|
||||||
|
'params': {
|
||||||
|
# m3u8 download
|
||||||
|
'skip_download': True,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
# From http://on.aol.com/video/how-to-make-a-next-level-fruit-salad-518086247
|
# From http://on.aol.com/video/how-to-make-a-next-level-fruit-salad-518086247
|
||||||
@@ -44,108 +39,16 @@ class FiveMinIE(InfoExtractor):
|
|||||||
},
|
},
|
||||||
'skip': 'no longer available',
|
'skip': 'no longer available',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://embed.5min.com/518726732/',
|
||||||
|
'only_matching': True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://delivery.vidible.tv/aol?playList=518013791',
|
||||||
|
'only_matching': True,
|
||||||
|
}
|
||||||
]
|
]
|
||||||
_ERRORS = {
|
|
||||||
'ErrorVideoNotExist': 'We\'re sorry, but the video you are trying to watch does not exist.',
|
|
||||||
'ErrorVideoNoLongerAvailable': 'We\'re sorry, but the video you are trying to watch is no longer available.',
|
|
||||||
'ErrorVideoRejected': 'We\'re sorry, but the video you are trying to watch has been removed.',
|
|
||||||
'ErrorVideoUserNotGeo': 'We\'re sorry, but the video you are trying to watch cannot be viewed from your current location.',
|
|
||||||
'ErrorVideoLibraryRestriction': 'We\'re sorry, but the video you are trying to watch is currently unavailable for viewing at this domain.',
|
|
||||||
'ErrorExposurePermission': 'We\'re sorry, but the video you are trying to watch is currently unavailable for viewing at this domain.',
|
|
||||||
}
|
|
||||||
_QUALITIES = {
|
|
||||||
1: {
|
|
||||||
'width': 640,
|
|
||||||
'height': 360,
|
|
||||||
},
|
|
||||||
2: {
|
|
||||||
'width': 854,
|
|
||||||
'height': 480,
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
'width': 1280,
|
|
||||||
'height': 720,
|
|
||||||
},
|
|
||||||
8: {
|
|
||||||
'width': 1920,
|
|
||||||
'height': 1080,
|
|
||||||
},
|
|
||||||
16: {
|
|
||||||
'width': 640,
|
|
||||||
'height': 360,
|
|
||||||
},
|
|
||||||
32: {
|
|
||||||
'width': 854,
|
|
||||||
'height': 480,
|
|
||||||
},
|
|
||||||
64: {
|
|
||||||
'width': 1280,
|
|
||||||
'height': 720,
|
|
||||||
},
|
|
||||||
128: {
|
|
||||||
'width': 640,
|
|
||||||
'height': 360,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = mobj.group('id')
|
return self.url_result('aol-video:%s' % video_id)
|
||||||
sid = mobj.group('sid')
|
|
||||||
|
|
||||||
if mobj.group('query'):
|
|
||||||
qs = compat_parse_qs(mobj.group('query'))
|
|
||||||
if not qs.get('playList'):
|
|
||||||
raise ExtractorError('Invalid URL', expected=True)
|
|
||||||
video_id = qs['playList'][0]
|
|
||||||
if qs.get('sid'):
|
|
||||||
sid = qs['sid'][0]
|
|
||||||
|
|
||||||
embed_url = 'https://embed.5min.com/playerseed/?playList=%s' % video_id
|
|
||||||
if not sid:
|
|
||||||
embed_page = self._download_webpage(embed_url, video_id,
|
|
||||||
'Downloading embed page')
|
|
||||||
sid = self._search_regex(r'sid=(\d+)', embed_page, 'sid')
|
|
||||||
|
|
||||||
response = self._download_json(
|
|
||||||
'https://syn.5min.com/handlers/SenseHandler.ashx?' +
|
|
||||||
compat_urllib_parse_urlencode({
|
|
||||||
'func': 'GetResults',
|
|
||||||
'playlist': video_id,
|
|
||||||
'sid': sid,
|
|
||||||
'isPlayerSeed': 'true',
|
|
||||||
'url': embed_url,
|
|
||||||
}),
|
|
||||||
video_id)
|
|
||||||
if not response['success']:
|
|
||||||
raise ExtractorError(
|
|
||||||
'%s said: %s' % (
|
|
||||||
self.IE_NAME,
|
|
||||||
self._ERRORS.get(response['errorMessage'], response['errorMessage'])),
|
|
||||||
expected=True)
|
|
||||||
info = response['binding'][0]
|
|
||||||
|
|
||||||
formats = []
|
|
||||||
parsed_video_url = compat_urllib_parse_urlparse(compat_parse_qs(
|
|
||||||
compat_urllib_parse_urlparse(info['EmbededURL']).query)['videoUrl'][0])
|
|
||||||
for rendition in info['Renditions']:
|
|
||||||
if rendition['RenditionType'] == 'aac' or rendition['RenditionType'] == 'm3u8':
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
rendition_url = compat_urlparse.urlunparse(parsed_video_url._replace(path=replace_extension(parsed_video_url.path.replace('//', '/%s/' % rendition['ID']), rendition['RenditionType'])))
|
|
||||||
quality = self._QUALITIES.get(rendition['ID'], {})
|
|
||||||
formats.append({
|
|
||||||
'format_id': '%s-%d' % (rendition['RenditionType'], rendition['ID']),
|
|
||||||
'url': rendition_url,
|
|
||||||
'width': quality.get('width'),
|
|
||||||
'height': quality.get('height'),
|
|
||||||
})
|
|
||||||
self._sort_formats(formats)
|
|
||||||
|
|
||||||
return {
|
|
||||||
'id': video_id,
|
|
||||||
'title': info['Title'],
|
|
||||||
'thumbnail': info.get('ThumbURL'),
|
|
||||||
'duration': parse_duration(info.get('Duration')),
|
|
||||||
'formats': formats,
|
|
||||||
}
|
|
||||||
|
|||||||
115
youtube_dl/extractor/flipagram.py
Normal file
115
youtube_dl/extractor/flipagram.py
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..compat import compat_str
|
||||||
|
from ..utils import (
|
||||||
|
int_or_none,
|
||||||
|
float_or_none,
|
||||||
|
try_get,
|
||||||
|
unified_timestamp,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class FlipagramIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?flipagram\.com/f/(?P<id>[^/?#&]+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'https://flipagram.com/f/nyvTSJMKId',
|
||||||
|
'md5': '888dcf08b7ea671381f00fab74692755',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'nyvTSJMKId',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Flipagram by sjuria101 featuring Midnight Memories by One Direction',
|
||||||
|
'description': 'md5:d55e32edc55261cae96a41fa85ff630e',
|
||||||
|
'duration': 35.571,
|
||||||
|
'timestamp': 1461244995,
|
||||||
|
'upload_date': '20160421',
|
||||||
|
'uploader': 'kitty juria',
|
||||||
|
'uploader_id': 'sjuria101',
|
||||||
|
'creator': 'kitty juria',
|
||||||
|
'view_count': int,
|
||||||
|
'like_count': int,
|
||||||
|
'repost_count': int,
|
||||||
|
'comment_count': int,
|
||||||
|
'comments': list,
|
||||||
|
'formats': 'mincount:2',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
video_data = self._parse_json(
|
||||||
|
self._search_regex(
|
||||||
|
r'window\.reactH2O\s*=\s*({.+});', webpage, 'video data'),
|
||||||
|
video_id)
|
||||||
|
|
||||||
|
flipagram = video_data['flipagram']
|
||||||
|
video = flipagram['video']
|
||||||
|
|
||||||
|
json_ld = self._search_json_ld(webpage, video_id, default={})
|
||||||
|
title = json_ld.get('title') or flipagram['captionText']
|
||||||
|
description = json_ld.get('description') or flipagram.get('captionText')
|
||||||
|
|
||||||
|
formats = [{
|
||||||
|
'url': video['url'],
|
||||||
|
'width': int_or_none(video.get('width')),
|
||||||
|
'height': int_or_none(video.get('height')),
|
||||||
|
'filesize': int_or_none(video_data.get('size')),
|
||||||
|
}]
|
||||||
|
|
||||||
|
preview_url = try_get(
|
||||||
|
flipagram, lambda x: x['music']['track']['previewUrl'], compat_str)
|
||||||
|
if preview_url:
|
||||||
|
formats.append({
|
||||||
|
'url': preview_url,
|
||||||
|
'ext': 'm4a',
|
||||||
|
'vcodec': 'none',
|
||||||
|
})
|
||||||
|
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
counts = flipagram.get('counts', {})
|
||||||
|
user = flipagram.get('user', {})
|
||||||
|
video_data = flipagram.get('video', {})
|
||||||
|
|
||||||
|
thumbnails = [{
|
||||||
|
'url': self._proto_relative_url(cover['url']),
|
||||||
|
'width': int_or_none(cover.get('width')),
|
||||||
|
'height': int_or_none(cover.get('height')),
|
||||||
|
'filesize': int_or_none(cover.get('size')),
|
||||||
|
} for cover in flipagram.get('covers', []) if cover.get('url')]
|
||||||
|
|
||||||
|
# Note that this only retrieves comments that are initally loaded.
|
||||||
|
# For videos with large amounts of comments, most won't be retrieved.
|
||||||
|
comments = []
|
||||||
|
for comment in video_data.get('comments', {}).get(video_id, {}).get('items', []):
|
||||||
|
text = comment.get('comment')
|
||||||
|
if not text or not isinstance(text, list):
|
||||||
|
continue
|
||||||
|
comments.append({
|
||||||
|
'author': comment.get('user', {}).get('name'),
|
||||||
|
'author_id': comment.get('user', {}).get('username'),
|
||||||
|
'id': comment.get('id'),
|
||||||
|
'text': text[0],
|
||||||
|
'timestamp': unified_timestamp(comment.get('created')),
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'duration': float_or_none(flipagram.get('duration'), 1000),
|
||||||
|
'thumbnails': thumbnails,
|
||||||
|
'timestamp': unified_timestamp(flipagram.get('iso8601Created')),
|
||||||
|
'uploader': user.get('name'),
|
||||||
|
'uploader_id': user.get('username'),
|
||||||
|
'creator': user.get('name'),
|
||||||
|
'view_count': int_or_none(counts.get('plays')),
|
||||||
|
'like_count': int_or_none(counts.get('likes')),
|
||||||
|
'repost_count': int_or_none(counts.get('reflips')),
|
||||||
|
'comment_count': int_or_none(counts.get('comments')),
|
||||||
|
'comments': comments,
|
||||||
|
'formats': formats,
|
||||||
|
}
|
||||||
@@ -5,8 +5,8 @@ from .common import InfoExtractor
|
|||||||
|
|
||||||
|
|
||||||
class Formula1IE(InfoExtractor):
|
class Formula1IE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?formula1\.com/content/fom-website/en/video/\d{4}/\d{1,2}/(?P<id>.+?)\.html'
|
_VALID_URL = r'https?://(?:www\.)?formula1\.com/(?:content/fom-website/)?en/video/\d{4}/\d{1,2}/(?P<id>.+?)\.html'
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
'url': 'http://www.formula1.com/content/fom-website/en/video/2016/5/Race_highlights_-_Spain_2016.html',
|
'url': 'http://www.formula1.com/content/fom-website/en/video/2016/5/Race_highlights_-_Spain_2016.html',
|
||||||
'md5': '8c79e54be72078b26b89e0e111c0502b',
|
'md5': '8c79e54be72078b26b89e0e111c0502b',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@@ -15,7 +15,10 @@ class Formula1IE(InfoExtractor):
|
|||||||
'title': 'Race highlights - Spain 2016',
|
'title': 'Race highlights - Spain 2016',
|
||||||
},
|
},
|
||||||
'add_ie': ['Ooyala'],
|
'add_ie': ['Ooyala'],
|
||||||
}
|
}, {
|
||||||
|
'url': 'http://www.formula1.com/en/video/2016/5/Race_highlights_-_Spain_2016.html',
|
||||||
|
'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)
|
||||||
|
|||||||
@@ -43,14 +43,14 @@ 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="img-avatar" href="[^"]+/channels/([^/"]+)" title="Go to [^"]+ page">',
|
r'<a class="item-to-subscribe" href="[^"]+/channels/([^/"]+)" 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="img-avatar" href="[^"]+/channels/[^/"]+" title="Go to ([^"]+) page">',
|
r'<a class="item-to-subscribe" href="[^"]+/channels/[^/"]+" title="Go to ([^"]+) page">',
|
||||||
webpage, 'uploader', fatal=False)
|
webpage, 'uploader', fatal=False)
|
||||||
|
|
||||||
categories_html = self._search_regex(
|
categories_html = self._search_regex(
|
||||||
r'(?s)><i class="icon icon-tag"></i>\s*Categories / Tags\s*.*?<ul class="list">(.*?)</ul>',
|
r'(?s)><i class="icon icon-tag"></i>\s*Categories / Tags\s*.*?<ul class="[^"]*?list[^"]*?">(.*?)</ul>',
|
||||||
webpage, 'categories', fatal=False)
|
webpage, 'categories', fatal=False)
|
||||||
categories = None
|
categories = None
|
||||||
if categories_html:
|
if categories_html:
|
||||||
@@ -59,10 +59,10 @@ class FourTubeIE(InfoExtractor):
|
|||||||
r'(?s)<li><a.*?>(.*?)</a>', categories_html)]
|
r'(?s)<li><a.*?>(.*?)</a>', categories_html)]
|
||||||
|
|
||||||
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', fatal=False))
|
||||||
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', fatal=False))
|
||||||
duration = parse_duration(self._html_search_meta('duration', webpage))
|
duration = parse_duration(self._html_search_meta('duration', webpage))
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,10 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import smuggle_url
|
from ..utils import (
|
||||||
|
smuggle_url,
|
||||||
|
update_url_query,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class FOXIE(InfoExtractor):
|
class FOXIE(InfoExtractor):
|
||||||
@@ -29,11 +32,12 @@ class FOXIE(InfoExtractor):
|
|||||||
|
|
||||||
release_url = self._parse_json(self._search_regex(
|
release_url = self._parse_json(self._search_regex(
|
||||||
r'"fox_pdk_player"\s*:\s*({[^}]+?})', webpage, 'fox_pdk_player'),
|
r'"fox_pdk_player"\s*:\s*({[^}]+?})', webpage, 'fox_pdk_player'),
|
||||||
video_id)['release_url'] + '&switch=http'
|
video_id)['release_url']
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'_type': 'url_transparent',
|
'_type': 'url_transparent',
|
||||||
'ie_key': 'ThePlatform',
|
'ie_key': 'ThePlatform',
|
||||||
'url': smuggle_url(release_url, {'force_smil_url': True}),
|
'url': smuggle_url(update_url_query(
|
||||||
|
release_url, {'switch': 'http'}), {'force_smil_url': True}),
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,104 +2,56 @@
|
|||||||
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 (
|
from ..utils import (
|
||||||
determine_ext,
|
determine_ext,
|
||||||
int_or_none,
|
unified_strdate,
|
||||||
ExtractorError,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class FranceCultureIE(InfoExtractor):
|
class FranceCultureIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?franceculture\.fr/player/reecouter\?play=(?P<id>[0-9]+)'
|
_VALID_URL = r'https?://(?:www\.)?franceculture\.fr/emissions/(?:[^/]+/)*(?P<id>[^/?#&]+)'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.franceculture.fr/player/reecouter?play=4795174',
|
'url': 'http://www.franceculture.fr/emissions/carnet-nomade/rendez-vous-au-pays-des-geeks',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '4795174',
|
'id': 'rendez-vous-au-pays-des-geeks',
|
||||||
|
'display_id': 'rendez-vous-au-pays-des-geeks',
|
||||||
'ext': 'mp3',
|
'ext': 'mp3',
|
||||||
'title': 'Rendez-vous au pays des geeks',
|
'title': 'Rendez-vous au pays des geeks',
|
||||||
'alt_title': 'Carnet nomade | 13-14',
|
'thumbnail': 're:^https?://.*\\.jpg$',
|
||||||
'vcodec': 'none',
|
|
||||||
'upload_date': '20140301',
|
'upload_date': '20140301',
|
||||||
'thumbnail': r're:^http://static\.franceculture\.fr/.*/images/player/Carnet-nomade\.jpg$',
|
'vcodec': 'none',
|
||||||
'description': 'startswith:Avec :Jean-Baptiste Péretié pour son documentaire sur Arte "La revanche',
|
|
||||||
'timestamp': 1393700400,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def _extract_from_player(self, url, video_id):
|
def _real_extract(self, url):
|
||||||
webpage = self._download_webpage(url, video_id)
|
display_id = self._match_id(url)
|
||||||
|
|
||||||
video_path = self._search_regex(
|
webpage = self._download_webpage(url, display_id)
|
||||||
r'<a id="player".*?href="([^"]+)"', webpage, 'video path')
|
|
||||||
video_url = compat_urlparse.urljoin(url, video_path)
|
video_url = self._search_regex(
|
||||||
timestamp = int_or_none(self._search_regex(
|
r'(?s)<div[^>]+class="[^"]*?title-zone-diffusion[^"]*?"[^>]*>.*?<a[^>]+href="([^"]+)"',
|
||||||
r'<a id="player".*?data-date="([0-9]+)"',
|
webpage, 'video path')
|
||||||
|
|
||||||
|
title = self._og_search_title(webpage)
|
||||||
|
|
||||||
|
upload_date = unified_strdate(self._search_regex(
|
||||||
|
'(?s)<div[^>]+class="date"[^>]*>.*?<span[^>]+class="inner"[^>]*>([^<]+)<',
|
||||||
webpage, 'upload date', fatal=False))
|
webpage, 'upload date', fatal=False))
|
||||||
thumbnail = self._search_regex(
|
thumbnail = self._search_regex(
|
||||||
r'<a id="player".*?>\s+<img src="([^"]+)"',
|
r'(?s)<figure[^>]+itemtype="https://schema.org/ImageObject"[^>]*>.*?<img[^>]+data-pagespeed-(?:lazy|high-res)-src="([^"]+)"',
|
||||||
webpage, 'thumbnail', fatal=False)
|
webpage, 'thumbnail', fatal=False)
|
||||||
|
|
||||||
display_id = self._search_regex(
|
|
||||||
r'<span class="path-diffusion">emission-(.*?)</span>', webpage, 'display_id')
|
|
||||||
|
|
||||||
title = self._html_search_regex(
|
|
||||||
r'<span class="title-diffusion">(.*?)</span>', webpage, 'title')
|
|
||||||
alt_title = self._html_search_regex(
|
|
||||||
r'<span class="title">(.*?)</span>',
|
|
||||||
webpage, 'alt_title', fatal=False)
|
|
||||||
description = self._html_search_regex(
|
|
||||||
r'<span class="description">(.*?)</span>',
|
|
||||||
webpage, 'description', fatal=False)
|
|
||||||
|
|
||||||
uploader = self._html_search_regex(
|
uploader = self._html_search_regex(
|
||||||
r'(?s)<div id="emission".*?<span class="author">(.*?)</span>',
|
r'(?s)<div id="emission".*?<span class="author">(.*?)</span>',
|
||||||
webpage, 'uploader', default=None)
|
webpage, 'uploader', default=None)
|
||||||
vcodec = 'none' if determine_ext(video_url.lower()) == 'mp3' else None
|
vcodec = 'none' if determine_ext(video_url.lower()) == 'mp3' else None
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': display_id,
|
||||||
|
'display_id': display_id,
|
||||||
'url': video_url,
|
'url': video_url,
|
||||||
|
'title': title,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
'vcodec': vcodec,
|
'vcodec': vcodec,
|
||||||
'uploader': uploader,
|
'uploader': uploader,
|
||||||
'timestamp': timestamp,
|
'upload_date': upload_date,
|
||||||
'title': title,
|
|
||||||
'alt_title': alt_title,
|
|
||||||
'thumbnail': thumbnail,
|
|
||||||
'description': description,
|
|
||||||
'display_id': display_id,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
|
||||||
video_id = self._match_id(url)
|
|
||||||
return self._extract_from_player(url, video_id)
|
|
||||||
|
|
||||||
|
|
||||||
class FranceCultureEmissionIE(FranceCultureIE):
|
|
||||||
_VALID_URL = r'https?://(?:www\.)?franceculture\.fr/emission-(?P<id>[^?#]+)'
|
|
||||||
_TEST = {
|
|
||||||
'url': 'http://www.franceculture.fr/emission-les-carnets-de-la-creation-jean-gabriel-periot-cineaste-2015-10-13',
|
|
||||||
'info_dict': {
|
|
||||||
'title': 'Jean-Gabriel Périot, cinéaste',
|
|
||||||
'alt_title': 'Les Carnets de la création',
|
|
||||||
'id': '5093239',
|
|
||||||
'display_id': 'les-carnets-de-la-creation-jean-gabriel-periot-cineaste-2015-10-13',
|
|
||||||
'ext': 'mp3',
|
|
||||||
'timestamp': 1444762500,
|
|
||||||
'upload_date': '20151013',
|
|
||||||
'description': 'startswith:Aujourd\'hui dans "Les carnets de la création", le cinéaste',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
|
||||||
video_id = self._match_id(url)
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
|
||||||
video_path = self._html_search_regex(
|
|
||||||
r'<a class="rf-player-open".*?href="([^"]+)"', webpage, 'video path', 'no_path_player')
|
|
||||||
if video_path == 'no_path_player':
|
|
||||||
raise ExtractorError('no player : no sound in this page.', expected=True)
|
|
||||||
new_id = self._search_regex('play=(?P<id>[0-9]+)', video_path, 'new_id', group='id')
|
|
||||||
video_url = compat_urlparse.urljoin(url, video_path)
|
|
||||||
return self._extract_from_player(video_url, new_id)
|
|
||||||
|
|||||||
@@ -14,7 +14,10 @@ from ..utils import (
|
|||||||
parse_duration,
|
parse_duration,
|
||||||
determine_ext,
|
determine_ext,
|
||||||
)
|
)
|
||||||
from .dailymotion import DailymotionCloudIE
|
from .dailymotion import (
|
||||||
|
DailymotionIE,
|
||||||
|
DailymotionCloudIE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class FranceTVBaseInfoExtractor(InfoExtractor):
|
class FranceTVBaseInfoExtractor(InfoExtractor):
|
||||||
@@ -128,7 +131,7 @@ class PluzzIE(FranceTVBaseInfoExtractor):
|
|||||||
|
|
||||||
class FranceTvInfoIE(FranceTVBaseInfoExtractor):
|
class FranceTvInfoIE(FranceTVBaseInfoExtractor):
|
||||||
IE_NAME = 'francetvinfo.fr'
|
IE_NAME = 'francetvinfo.fr'
|
||||||
_VALID_URL = r'https?://(?:www|mobile|france3-regions)\.francetvinfo\.fr/.*/(?P<title>.+)\.html'
|
_VALID_URL = r'https?://(?:www|mobile|france3-regions)\.francetvinfo\.fr/(?:[^/]+/)*(?P<title>[^/?#&.]+)'
|
||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.francetvinfo.fr/replay-jt/france-3/soir-3/jt-grand-soir-3-lundi-26-aout-2013_393427.html',
|
'url': 'http://www.francetvinfo.fr/replay-jt/france-3/soir-3/jt-grand-soir-3-lundi-26-aout-2013_393427.html',
|
||||||
@@ -188,6 +191,24 @@ class FranceTvInfoIE(FranceTVBaseInfoExtractor):
|
|||||||
'params': {
|
'params': {
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
},
|
},
|
||||||
|
}, {
|
||||||
|
# Dailymotion embed
|
||||||
|
'url': 'http://www.francetvinfo.fr/politique/notre-dame-des-landes/video-sur-france-inter-cecile-duflot-denonce-le-regard-meprisant-de-patrick-cohen_1520091.html',
|
||||||
|
'md5': 'ee7f1828f25a648addc90cb2687b1f12',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'x4iiko0',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'NDDL, référendum, Brexit : Cécile Duflot répond à Patrick Cohen',
|
||||||
|
'description': 'Au lendemain de la victoire du "oui" au référendum sur l\'aéroport de Notre-Dame-des-Landes, l\'ancienne ministre écologiste est l\'invitée de Patrick Cohen. Plus d\'info : https://www.franceinter.fr/emissions/le-7-9/le-7-9-27-juin-2016',
|
||||||
|
'timestamp': 1467011958,
|
||||||
|
'upload_date': '20160627',
|
||||||
|
'uploader': 'France Inter',
|
||||||
|
'uploader_id': 'x2q2ez',
|
||||||
|
},
|
||||||
|
'add_ie': ['Dailymotion'],
|
||||||
|
}, {
|
||||||
|
'url': 'http://france3-regions.francetvinfo.fr/limousin/emissions/jt-1213-limousin',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
@@ -197,7 +218,13 @@ class FranceTvInfoIE(FranceTVBaseInfoExtractor):
|
|||||||
|
|
||||||
dmcloud_url = DailymotionCloudIE._extract_dmcloud_url(webpage)
|
dmcloud_url = DailymotionCloudIE._extract_dmcloud_url(webpage)
|
||||||
if dmcloud_url:
|
if dmcloud_url:
|
||||||
return self.url_result(dmcloud_url, 'DailymotionCloud')
|
return self.url_result(dmcloud_url, DailymotionCloudIE.ie_key())
|
||||||
|
|
||||||
|
dailymotion_urls = DailymotionIE._extract_urls(webpage)
|
||||||
|
if dailymotion_urls:
|
||||||
|
return self.playlist_result([
|
||||||
|
self.url_result(dailymotion_url, DailymotionIE.ie_key())
|
||||||
|
for dailymotion_url in dailymotion_urls])
|
||||||
|
|
||||||
video_id, catalogue = self._search_regex(
|
video_id, catalogue = self._search_regex(
|
||||||
(r'id-video=([^@]+@[^"]+)',
|
(r'id-video=([^@]+@[^"]+)',
|
||||||
|
|||||||
70
youtube_dl/extractor/fxnetworks.py
Normal file
70
youtube_dl/extractor/fxnetworks.py
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .adobepass import AdobePassIE
|
||||||
|
from ..utils import (
|
||||||
|
update_url_query,
|
||||||
|
extract_attributes,
|
||||||
|
parse_age_limit,
|
||||||
|
smuggle_url,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class FXNetworksIE(AdobePassIE):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?(?:fxnetworks|simpsonsworld)\.com/video/(?P<id>\d+)'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://www.fxnetworks.com/video/719841347694',
|
||||||
|
'md5': '1447d4722e42ebca19e5232ab93abb22',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '719841347694',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Vanpage',
|
||||||
|
'description': 'F*ck settling down. You\'re the Worst returns for an all new season August 31st on FXX.',
|
||||||
|
'age_limit': 14,
|
||||||
|
'uploader': 'NEWA-FNG-FX',
|
||||||
|
'upload_date': '20160706',
|
||||||
|
'timestamp': 1467844741,
|
||||||
|
},
|
||||||
|
'add_ie': ['ThePlatform'],
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.simpsonsworld.com/video/716094019682',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
if 'The content you are trying to access is not available in your region.' in webpage:
|
||||||
|
self.raise_geo_restricted()
|
||||||
|
video_data = extract_attributes(self._search_regex(
|
||||||
|
r'(<a.+?rel="http://link\.theplatform\.com/s/.+?</a>)', webpage, 'video data'))
|
||||||
|
player_type = self._search_regex(r'playerType\s*=\s*[\'"]([^\'"]+)', webpage, 'player type', default=None)
|
||||||
|
release_url = video_data['rel']
|
||||||
|
title = video_data['data-title']
|
||||||
|
rating = video_data.get('data-rating')
|
||||||
|
query = {
|
||||||
|
'mbr': 'true',
|
||||||
|
}
|
||||||
|
if player_type == 'movies':
|
||||||
|
query.update({
|
||||||
|
'manifest': 'm3u',
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
query.update({
|
||||||
|
'switch': 'http',
|
||||||
|
})
|
||||||
|
if video_data.get('data-req-auth') == '1':
|
||||||
|
resource = self._get_mvpd_resource(
|
||||||
|
video_data['data-channel'], title,
|
||||||
|
video_data.get('data-guid'), rating)
|
||||||
|
query['auth'] = self._extract_mvpd_auth(url, video_id, 'fx', resource)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'_type': 'url_transparent',
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'url': smuggle_url(update_url_query(release_url, query), {'force_smil_url': True}),
|
||||||
|
'thumbnail': video_data.get('data-large-thumb'),
|
||||||
|
'age_limit': parse_age_limit(rating),
|
||||||
|
'ie_key': 'ThePlatform',
|
||||||
|
}
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
# coding: utf-8
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
|
||||||
from ..utils import (
|
|
||||||
xpath_text,
|
|
||||||
xpath_with_ns,
|
|
||||||
)
|
|
||||||
from .youtube import YoutubeIE
|
|
||||||
|
|
||||||
|
|
||||||
class GamekingsIE(InfoExtractor):
|
|
||||||
_VALID_URL = r'https?://www\.gamekings\.nl/(?:videos|nieuws)/(?P<id>[^/]+)'
|
|
||||||
_TESTS = [{
|
|
||||||
# YouTube embed video
|
|
||||||
'url': 'http://www.gamekings.nl/videos/phoenix-wright-ace-attorney-dual-destinies-review/',
|
|
||||||
'md5': '5208d3a17adeaef829a7861887cb9029',
|
|
||||||
'info_dict': {
|
|
||||||
'id': 'HkSQKetlGOU',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': 'Phoenix Wright: Ace Attorney - Dual Destinies Review',
|
|
||||||
'description': 'md5:db88c0e7f47e9ea50df3271b9dc72e1d',
|
|
||||||
'thumbnail': 're:^https?://.*\.jpg$',
|
|
||||||
'uploader_id': 'UCJugRGo4STYMeFr5RoOShtQ',
|
|
||||||
'uploader': 'Gamekings Vault',
|
|
||||||
'upload_date': '20151123',
|
|
||||||
},
|
|
||||||
'add_ie': ['Youtube'],
|
|
||||||
}, {
|
|
||||||
# vimeo video
|
|
||||||
'url': 'http://www.gamekings.nl/videos/the-legend-of-zelda-majoras-mask/',
|
|
||||||
'md5': '12bf04dfd238e70058046937657ea68d',
|
|
||||||
'info_dict': {
|
|
||||||
'id': 'the-legend-of-zelda-majoras-mask',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': 'The Legend of Zelda: Majora’s Mask',
|
|
||||||
'description': 'md5:9917825fe0e9f4057601fe1e38860de3',
|
|
||||||
'thumbnail': 're:^https?://.*\.jpg$',
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
'url': 'http://www.gamekings.nl/nieuws/gamekings-extra-shelly-en-david-bereiden-zich-voor-op-de-livestream/',
|
|
||||||
'only_matching': True,
|
|
||||||
}]
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
|
||||||
video_id = self._match_id(url)
|
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
|
||||||
|
|
||||||
playlist_id = self._search_regex(
|
|
||||||
r'gogoVideo\([^,]+,\s*"([^"]+)', webpage, 'playlist id')
|
|
||||||
|
|
||||||
# Check if a YouTube embed is used
|
|
||||||
if YoutubeIE.suitable(playlist_id):
|
|
||||||
return self.url_result(playlist_id, ie='Youtube')
|
|
||||||
|
|
||||||
playlist = self._download_xml(
|
|
||||||
'http://www.gamekings.tv/wp-content/themes/gk2010/rss_playlist.php?id=%s' % playlist_id,
|
|
||||||
video_id)
|
|
||||||
|
|
||||||
NS_MAP = {
|
|
||||||
'jwplayer': 'http://rss.jwpcdn.com/'
|
|
||||||
}
|
|
||||||
|
|
||||||
item = playlist.find('./channel/item')
|
|
||||||
|
|
||||||
thumbnail = xpath_text(item, xpath_with_ns('./jwplayer:image', NS_MAP), 'thumbnail')
|
|
||||||
video_url = item.find(xpath_with_ns('./jwplayer:source', NS_MAP)).get('file')
|
|
||||||
|
|
||||||
return {
|
|
||||||
'id': video_id,
|
|
||||||
'url': video_url,
|
|
||||||
'title': self._og_search_title(webpage),
|
|
||||||
'description': self._og_search_description(webpage),
|
|
||||||
'thumbnail': thumbnail,
|
|
||||||
}
|
|
||||||
@@ -28,10 +28,13 @@ class GameSpotIE(OnceIE):
|
|||||||
'url': 'http://www.gamespot.com/videos/the-witcher-3-wild-hunt-xbox-one-now-playing/2300-6424837/',
|
'url': 'http://www.gamespot.com/videos/the-witcher-3-wild-hunt-xbox-one-now-playing/2300-6424837/',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'gs-2300-6424837',
|
'id': 'gs-2300-6424837',
|
||||||
'ext': 'flv',
|
'ext': 'mp4',
|
||||||
'title': 'The Witcher 3: Wild Hunt [Xbox ONE] - Now Playing',
|
'title': 'Now Playing - The Witcher 3: Wild Hunt',
|
||||||
'description': 'Join us as we take a look at the early hours of The Witcher 3: Wild Hunt and more.',
|
'description': 'Join us as we take a look at the early hours of The Witcher 3: Wild Hunt and more.',
|
||||||
},
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True, # m3u8 downloads
|
||||||
|
},
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user