Compare commits
522 Commits
2015.10.23
...
2015.12.13
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f3711edcf1 | ||
![]() |
22d07ba4e4 | ||
![]() |
f6abca506e | ||
![]() |
b5424acdb9 | ||
![]() |
47c7f3d995 | ||
![]() |
0014ffa829 | ||
![]() |
c03943f394 | ||
![]() |
deb1e8d20e | ||
![]() |
174964a7bc | ||
![]() |
9c568178fb | ||
![]() |
dbb7d7e26c | ||
![]() |
ade2340971 | ||
![]() |
4d77550cf0 | ||
![]() |
c683454e7e | ||
![]() |
f133fd326b | ||
![]() |
1faa66f005 | ||
![]() |
8773f3158f | ||
![]() |
7e37c39485 | ||
![]() |
14c17cafa1 | ||
![]() |
8696a7fd13 | ||
![]() |
d63cfc3f0f | ||
![]() |
f377f44dae | ||
![]() |
0b1bb1ac3a | ||
![]() |
f208e52a76 | ||
![]() |
b091529a3c | ||
![]() |
b323a3cbff | ||
![]() |
b59623ef43 | ||
![]() |
9c163950da | ||
![]() |
d357bbd375 | ||
![]() |
f542a3d26b | ||
![]() |
59a4ff482a | ||
![]() |
40ca5b04f4 | ||
![]() |
411e5b88c9 | ||
![]() |
b4c299bad0 | ||
![]() |
ab4bdc913f | ||
![]() |
1fe248a51b | ||
![]() |
2559b9d017 | ||
![]() |
4db43567e8 | ||
![]() |
5333842a1d | ||
![]() |
98c3806b15 | ||
![]() |
b6afc225c8 | ||
![]() |
ad30dc1e20 | ||
![]() |
ff51983e15 | ||
![]() |
fdf01663d1 | ||
![]() |
4b94288301 | ||
![]() |
4bf99ade15 | ||
![]() |
75ed53320e | ||
![]() |
17b786ae73 | ||
![]() |
dfd42a43c3 | ||
![]() |
f7b8dd63f0 | ||
![]() |
a8abf124c8 | ||
![]() |
176ccefcd8 | ||
![]() |
cbd2ffd031 | ||
![]() |
0b534d2adc | ||
![]() |
526a20bd16 | ||
![]() |
51094b1b08 | ||
![]() |
f1ac2033ab | ||
![]() |
a1b8d815f5 | ||
![]() |
8b756bd98e | ||
![]() |
46047c58d0 | ||
![]() |
374c761e77 | ||
![]() |
6c7b26e13f | ||
![]() |
b51b108045 | ||
![]() |
86e8c89488 | ||
![]() |
47f48f5d85 | ||
![]() |
e15e2ef7a0 | ||
![]() |
d0c8b279da | ||
![]() |
612d83b51d | ||
![]() |
9c30efeb7e | ||
![]() |
39fa4cc107 | ||
![]() |
b09c122373 | ||
![]() |
3348243b7b | ||
![]() |
b46b65ed37 | ||
![]() |
18e4088fad | ||
![]() |
5fd6cd64f9 | ||
![]() |
3d24bbfbe4 | ||
![]() |
1775612512 | ||
![]() |
0d2d967cc7 | ||
![]() |
a5e52a1fd4 | ||
![]() |
291a93bafa | ||
![]() |
c4737bea17 | ||
![]() |
45dad7ba1b | ||
![]() |
db7c9da871 | ||
![]() |
bc92621ade | ||
![]() |
fd8e559c3a | ||
![]() |
222e11d4ae | ||
![]() |
7d682f0acb | ||
![]() |
8364b6b0b1 | ||
![]() |
7ac40e5521 | ||
![]() |
36066dd3ee | ||
![]() |
636aa83ed3 | ||
![]() |
33d152b6cc | ||
![]() |
51c4fec0d5 | ||
![]() |
0017486dca | ||
![]() |
edc70f4aaf | ||
![]() |
756926ff00 | ||
![]() |
cb160dd531 | ||
![]() |
77334ccb44 | ||
![]() |
796db21295 | ||
![]() |
535d7b681b | ||
![]() |
960e038886 | ||
![]() |
ea14422ff1 | ||
![]() |
38d05d17e5 | ||
![]() |
db9bd5267f | ||
![]() |
ab3b773bbe | ||
![]() |
0bc4ee60e0 | ||
![]() |
a3ef0e1cdd | ||
![]() |
679bacf0b5 | ||
![]() |
02e3952f3b | ||
![]() |
64b7e89c0c | ||
![]() |
bee4c5571a | ||
![]() |
96929dd1e8 | ||
![]() |
53e06b2507 | ||
![]() |
b80d4bebf3 | ||
![]() |
55bec9b658 | ||
![]() |
2a63b0f110 | ||
![]() |
07b88cffce | ||
![]() |
58c8451f36 | ||
![]() |
3047121c63 | ||
![]() |
7079f8ff1f | ||
![]() |
2c3b9f3570 | ||
![]() |
fad2428f47 | ||
![]() |
c3d3110f6a | ||
![]() |
79ec00276c | ||
![]() |
9c117d345f | ||
![]() |
46cc1c65a4 | ||
![]() |
71d9fe7818 | ||
![]() |
4ccabf93db | ||
![]() |
6612a34939 | ||
![]() |
e5b4225f7c | ||
![]() |
b2ca35ddbc | ||
![]() |
76ab842d9b | ||
![]() |
24dc1ed715 | ||
![]() |
682d8dcd21 | ||
![]() |
640bb54e73 | ||
![]() |
e0977d7686 | ||
![]() |
112ab398db | ||
![]() |
af93fcfa05 | ||
![]() |
62d231c004 | ||
![]() |
49358274d7 | ||
![]() |
7b1e379ca9 | ||
![]() |
22d7368dfb | ||
![]() |
24121bc703 | ||
![]() |
9fc87fa767 | ||
![]() |
328f82d59a | ||
![]() |
78717fc328 | ||
![]() |
3b35c3425e | ||
![]() |
874ae0354e | ||
![]() |
4c6b4764f0 | ||
![]() |
59ee8a8647 | ||
![]() |
af284305d5 | ||
![]() |
d53a4af1a4 | ||
![]() |
2e1b928540 | ||
![]() |
040ac68679 | ||
![]() |
049d71d874 | ||
![]() |
bf2c8c8f82 | ||
![]() |
ef428960c9 | ||
![]() |
992fc9d6e1 | ||
![]() |
8639f89f51 | ||
![]() |
0424ec307b | ||
![]() |
ac5a69af45 | ||
![]() |
94e8c80473 | ||
![]() |
87f0e62d94 | ||
![]() |
46b4070f3f | ||
![]() |
2a776f9788 | ||
![]() |
f4c7ef9862 | ||
![]() |
50e12e9df1 | ||
![]() |
b7faebbac8 | ||
![]() |
4191fdf147 | ||
![]() |
9a4f12be98 | ||
![]() |
7ad4258add | ||
![]() |
9945c4994c | ||
![]() |
5faf9fed7e | ||
![]() |
13a9b69b09 | ||
![]() |
4975650e00 | ||
![]() |
0cc7178546 | ||
![]() |
41f24c321d | ||
![]() |
4b3fbafdd2 | ||
![]() |
7ac40086f5 | ||
![]() |
313dfc45f5 | ||
![]() |
78a55d7a28 | ||
![]() |
bb6ac83698 | ||
![]() |
9d0e366880 | ||
![]() |
9bff48a0e7 | ||
![]() |
60121eb514 | ||
![]() |
527ca1da4f | ||
![]() |
7689413e42 | ||
![]() |
ba7a92b0ce | ||
![]() |
4c7d816dd7 | ||
![]() |
032f2f260f | ||
![]() |
20e98bf6c0 | ||
![]() |
5c2266df4b | ||
![]() |
67dda51722 | ||
![]() |
e4c4bcf36f | ||
![]() |
82d8a8b6e2 | ||
![]() |
13a10d5aa3 | ||
![]() |
9022726446 | ||
![]() |
94bfcd23b7 | ||
![]() |
526b3b0716 | ||
![]() |
61f92af1cf | ||
![]() |
a72778d364 | ||
![]() |
5ae17037a3 | ||
![]() |
02f0da20b0 | ||
![]() |
b41631c4e6 | ||
![]() |
0e49d9a6b0 | ||
![]() |
4a7d108ab3 | ||
![]() |
3cfd000849 | ||
![]() |
1b38185361 | ||
![]() |
9cb9a5df77 | ||
![]() |
5035536e3f | ||
![]() |
3e12bc583a | ||
![]() |
e568c2233e | ||
![]() |
061a75edd6 | ||
![]() |
82c4d7b0ce | ||
![]() |
136dadde95 | ||
![]() |
0c14841585 | ||
![]() |
0eebf34d9d | ||
![]() |
cf186b77a7 | ||
![]() |
a3372437bf | ||
![]() |
4c57b4853d | ||
![]() |
38eb2968ab | ||
![]() |
bea56c9569 | ||
![]() |
7e508ff2cf | ||
![]() |
563772eda4 | ||
![]() |
0533915aad | ||
![]() |
c3a227d1c4 | ||
![]() |
f6c903e708 | ||
![]() |
7dc011c063 | ||
![]() |
4e3b303016 | ||
![]() |
7e1f5447e7 | ||
![]() |
7e3472758b | ||
![]() |
328a22e175 | ||
![]() |
417b453699 | ||
![]() |
6ea7190a3e | ||
![]() |
b54b08c91b | ||
![]() |
c30943b1c0 | ||
![]() |
2abf7cab80 | ||
![]() |
4137196899 | ||
![]() |
019839faaa | ||
![]() |
f52183a878 | ||
![]() |
750b9ff032 | ||
![]() |
28602e747c | ||
![]() |
6cc37c69e2 | ||
![]() |
a5cd0eb8a4 | ||
![]() |
c23e266427 | ||
![]() |
651acffbe5 | ||
![]() |
71bd93b89c | ||
![]() |
6da620de58 | ||
![]() |
bdceea7afd | ||
![]() |
d80a39cec8 | ||
![]() |
5b5fae5f20 | ||
![]() |
01b06aedcf | ||
![]() |
c711383811 | ||
![]() |
17cc153435 | ||
![]() |
67446fd49b | ||
![]() |
325bb615a7 | ||
![]() |
ee5cd8418e | ||
![]() |
342609a1b4 | ||
![]() |
f270cf1a26 | ||
![]() |
371c3b796c | ||
![]() |
6b7ceee1b9 | ||
![]() |
fdb20a27a3 | ||
![]() |
2c94198eb6 | ||
![]() |
e8110b8125 | ||
![]() |
c39fd7b1ca | ||
![]() |
a9c09a7c62 | ||
![]() |
82beaabb41 | ||
![]() |
63b4295d20 | ||
![]() |
312a3f389b | ||
![]() |
609af1ae1c | ||
![]() |
4cd759f73d | ||
![]() |
e156e70281 | ||
![]() |
9b464929fe | ||
![]() |
0c176d7bde | ||
![]() |
7a3f0c00ad | ||
![]() |
7aefc49c40 | ||
![]() |
741dd8ea65 | ||
![]() |
76adc82068 | ||
![]() |
bd1512d196 | ||
![]() |
9a4acbfaf5 | ||
![]() |
ad1f4e7902 | ||
![]() |
b328295910 | ||
![]() |
828b2a5cd9 | ||
![]() |
2ff7cbeaaa | ||
![]() |
b2f7738830 | ||
![]() |
dc0279532a | ||
![]() |
0c59d02bdc | ||
![]() |
0f72beb515 | ||
![]() |
d781e29316 | ||
![]() |
3b3e8ed332 | ||
![]() |
dcdfeb33d2 | ||
![]() |
0d85c3a732 | ||
![]() |
903d136942 | ||
![]() |
9d584da7d0 | ||
![]() |
31752f76f7 | ||
![]() |
5f1b2aea80 | ||
![]() |
4479600d57 | ||
![]() |
a90189c3ad | ||
![]() |
d8a1caf04f | ||
![]() |
cb33d389ed | ||
![]() |
967e0955f0 | ||
![]() |
e01b432ad3 | ||
![]() |
fd91257c40 | ||
![]() |
c7b959ce38 | ||
![]() |
75eac8961e | ||
![]() |
3b7d9aa487 | ||
![]() |
1f4b722b00 | ||
![]() |
f6519f89b0 | ||
![]() |
24af85298e | ||
![]() |
e721d857c2 | ||
![]() |
5c17f0a67a | ||
![]() |
4fcaa4f4a5 | ||
![]() |
536f819eda | ||
![]() |
a662489877 | ||
![]() |
a2973eb597 | ||
![]() |
4e21b3a94f | ||
![]() |
b703ebeeaf | ||
![]() |
b84a5f0337 | ||
![]() |
a1ec9a7553 | ||
![]() |
91d644b5ba | ||
![]() |
5d6c3d6a66 | ||
![]() |
1ebb4717df | ||
![]() |
cf5881fc4d | ||
![]() |
fcd817a326 | ||
![]() |
031ec536f0 | ||
![]() |
668db403f9 | ||
![]() |
b9ad101926 | ||
![]() |
435911029f | ||
![]() |
699ed30cee | ||
![]() |
9eab37dca0 | ||
![]() |
9a8a12b7d8 | ||
![]() |
a4c2ab35c1 | ||
![]() |
3d9c4bf09a | ||
![]() |
8b8a39e279 | ||
![]() |
82393e2bb2 | ||
![]() |
2eb99a4b98 | ||
![]() |
6abce58a12 | ||
![]() |
990e6e8fa3 | ||
![]() |
bfd88516eb | ||
![]() |
d8b7e80d29 | ||
![]() |
37120974dc | ||
![]() |
42fc93c709 | ||
![]() |
a625e56543 | ||
![]() |
9b738b2caa | ||
![]() |
90bb5667bf | ||
![]() |
d3d3e2e3aa | ||
![]() |
37ca7b22b5 | ||
![]() |
50f84a9ae1 | ||
![]() |
ff29bf81f8 | ||
![]() |
b25f753397 | ||
![]() |
6a5d6de1e3 | ||
![]() |
1c31a5b0e0 | ||
![]() |
4f5cdf7c9b | ||
![]() |
f09a767d31 | ||
![]() |
cc8034cc4c | ||
![]() |
50506cb607 | ||
![]() |
aa8d2d5be6 | ||
![]() |
114e6025b0 | ||
![]() |
fda2717ef9 | ||
![]() |
937511dfc0 | ||
![]() |
d5c181a14e | ||
![]() |
e8ce2375e0 | ||
![]() |
6fdb39ded1 | ||
![]() |
8e3a2bd620 | ||
![]() |
a06bf87a2c | ||
![]() |
ee4337d100 | ||
![]() |
cff551c0b0 | ||
![]() |
6d02b9a392 | ||
![]() |
2c740cf28d | ||
![]() |
5214f1e31d | ||
![]() |
5d0f84d32c | ||
![]() |
ee223abb88 | ||
![]() |
21d0c33ecd | ||
![]() |
8b6d9406db | ||
![]() |
686f98816e | ||
![]() |
0fa6b17dcc | ||
![]() |
472404953a | ||
![]() |
ae4ddf9efa | ||
![]() |
ea8ed40b2f | ||
![]() |
71bb016160 | ||
![]() |
179ffab69c | ||
![]() |
deb85c32bb | ||
![]() |
92366d189e | ||
![]() |
81413c0165 | ||
![]() |
1e2eb4b40d | ||
![]() |
01003d072c | ||
![]() |
5003e4283b | ||
![]() |
123c781044 | ||
![]() |
e68dd1921a | ||
![]() |
6953d8e95a | ||
![]() |
b3613d36da | ||
![]() |
53472df857 | ||
![]() |
2549e113b8 | ||
![]() |
b15c44cd36 | ||
![]() |
f93ded9852 | ||
![]() |
89ea063eeb | ||
![]() |
44b2264fea | ||
![]() |
cb5a470635 | ||
![]() |
17d1900581 | ||
![]() |
5d501a0901 | ||
![]() |
c13722480b | ||
![]() |
e7d34c03f2 | ||
![]() |
264cd00fff | ||
![]() |
a4a6b7b80f | ||
![]() |
aebb42d32b | ||
![]() |
b4ef6a0038 | ||
![]() |
5d235ca7f6 | ||
![]() |
c3459d24f1 | ||
![]() |
e3778cce0e | ||
![]() |
ad607563a2 | ||
![]() |
236cb2131b | ||
![]() |
66d041f250 | ||
![]() |
f3cb54e6d9 | ||
![]() |
0aeb9a106e | ||
![]() |
fd8102820c | ||
![]() |
bfdf891fd3 | ||
![]() |
3fa3ff1bc3 | ||
![]() |
0a0110fc6b | ||
![]() |
852fad922f | ||
![]() |
fc68d52bb9 | ||
![]() |
dde9fe9788 | ||
![]() |
a230068ff7 | ||
![]() |
6a75040278 | ||
![]() |
c514b0ec65 | ||
![]() |
eb97f46e8b | ||
![]() |
c90d16cf36 | ||
![]() |
ab6ca04802 | ||
![]() |
999079b454 | ||
![]() |
8a06999ba0 | ||
![]() |
80dcee5cd5 | ||
![]() |
9550ca506f | ||
![]() |
30eecc6a04 | ||
![]() |
dbd82a1d4f | ||
![]() |
76f0c50d3d | ||
![]() |
dc519b5421 | ||
![]() |
ae12bc3ebb | ||
![]() |
e327b736ca | ||
![]() |
82b69a5cbb | ||
![]() |
11465da702 | ||
![]() |
578c074575 | ||
![]() |
8cdb5c8453 | ||
![]() |
2b1b2d83ca | ||
![]() |
c3040bd00a | ||
![]() |
8c1aa28c27 | ||
![]() |
78d7ee19dc | ||
![]() |
892015b088 | ||
![]() |
47f2d01a5a | ||
![]() |
33a513faf7 | ||
![]() |
240384afe6 | ||
![]() |
6722ebd437 | ||
![]() |
9a605c8859 | ||
![]() |
402ca40c9d | ||
![]() |
30bd1c16c8 | ||
![]() |
721f5a277c | ||
![]() |
6fb8ace671 | ||
![]() |
ae37338e68 | ||
![]() |
03c2c162f9 | ||
![]() |
52c3a6e49d | ||
![]() |
4e16c1f80b | ||
![]() |
7ccb2b84dd | ||
![]() |
0a192fbea7 | ||
![]() |
a526167d40 | ||
![]() |
f78546272c | ||
![]() |
c137cc0d33 | ||
![]() |
6e4b8b2891 | ||
![]() |
5dadae079b | ||
![]() |
cd08d806b1 | ||
![]() |
5f9f87c06f | ||
![]() |
387db16a78 | ||
![]() |
36e6f62cd0 | ||
![]() |
755ff8d22c | ||
![]() |
7b3a19e533 | ||
![]() |
4f13f8f798 | ||
![]() |
feb7711cf5 | ||
![]() |
589c33dade | ||
![]() |
e572a1010b | ||
![]() |
7e0dc61334 | ||
![]() |
8e82ecfe8f | ||
![]() |
ec29539e06 | ||
![]() |
8cd9614abf | ||
![]() |
324ac0a243 | ||
![]() |
3711304510 | ||
![]() |
50b936936d | ||
![]() |
d97da29da2 | ||
![]() |
7687b354c5 | ||
![]() |
36d7281037 | ||
![]() |
865d1fbafc | ||
![]() |
ac21e71968 | ||
![]() |
943a1e24b8 | ||
![]() |
50f01302d3 | ||
![]() |
0198807ef9 | ||
![]() |
6856139705 | ||
![]() |
c93153852f | ||
![]() |
ab9c7214ee | ||
![]() |
dae69640d0 | ||
![]() |
edeb3e7cb1 | ||
![]() |
5c43afd40f | ||
![]() |
9170ca5b16 | ||
![]() |
65d49afa48 | ||
![]() |
497f5fd93f | ||
![]() |
4bf5614195 | ||
![]() |
520e753390 | ||
![]() |
355c7ad361 | ||
![]() |
55af2b26e0 | ||
![]() |
d90e40305b | ||
![]() |
cce9d15d01 | ||
![]() |
dd414c970b | ||
![]() |
497ca088a6 | ||
![]() |
90bddb6cdd | ||
![]() |
6a11bb77ba | ||
![]() |
ecf6de5b02 | ||
![]() |
139f27827e | ||
![]() |
30787f7259 | ||
![]() |
c233e6bcc3 | ||
![]() |
28809ab07a | ||
![]() |
8fc226ef99 | ||
![]() |
c01e1a96aa | ||
![]() |
53407e3f38 | ||
![]() |
ed1269000f | ||
![]() |
689fb748ee | ||
![]() |
eb08081330 | ||
![]() |
f870544302 |
5
AUTHORS
5
AUTHORS
@@ -144,3 +144,8 @@ Lee Jenkins
|
|||||||
Anssi Hannula
|
Anssi Hannula
|
||||||
Lukáš Lalinský
|
Lukáš Lalinský
|
||||||
Qijiang Fan
|
Qijiang Fan
|
||||||
|
Rémy Léone
|
||||||
|
Marco Ferragina
|
||||||
|
reiv
|
||||||
|
Muratcan Simsek
|
||||||
|
Evan Lu
|
||||||
|
@@ -1,6 +1,20 @@
|
|||||||
**Please include the full output of youtube-dl when run with `-v`**.
|
**Please include the full output of youtube-dl when run with `-v`**, i.e. add `-v` flag to your command line, copy the **whole** output and post it in the issue body wrapped in \`\`\` for better formatting. It should look similar to this:
|
||||||
|
```
|
||||||
|
$ youtube-dl -v http://www.youtube.com/watch?v=BaW_jenozKcj
|
||||||
|
[debug] System config: []
|
||||||
|
[debug] User config: []
|
||||||
|
[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] youtube-dl version 2015.12.06
|
||||||
|
[debug] Git HEAD: 135392e
|
||||||
|
[debug] Python version 2.6.6 - Windows-2003Server-5.2.3790-SP2
|
||||||
|
[debug] exe versions: ffmpeg N-75573-g1d0487f, ffprobe N-75573-g1d0487f, rtmpdump 2.4
|
||||||
|
[debug] Proxy map: {}
|
||||||
|
...
|
||||||
|
```
|
||||||
|
**Do not post screenshots of verbose log only plain text is acceptable.**
|
||||||
|
|
||||||
The output (including the first lines) contain important debugging information. Issues without the full output are often not reproducible and therefore do not get solved in short order, if ever.
|
The output (including the first lines) contains important debugging information. Issues without the full output are often not reproducible and therefore do not get solved in short order, if ever.
|
||||||
|
|
||||||
Please re-read your issue once again to avoid a couple of common mistakes (you can and should use this as a checklist):
|
Please re-read your issue once again to avoid a couple of common mistakes (you can and should use this as a checklist):
|
||||||
|
|
||||||
@@ -20,7 +34,7 @@ For bug reports, this means that your report should contain the *complete* outpu
|
|||||||
|
|
||||||
If your server has multiple IPs or you suspect censorship, adding `--call-home` may be a good idea to get more diagnostics. If the error is `ERROR: Unable to extract ...` and you cannot reproduce it from multiple countries, add `--dump-pages` (warning: this will yield a rather large output, redirect it to the file `log.txt` by adding `>log.txt 2>&1` to your command-line) or upload the `.dump` files you get when you add `--write-pages` [somewhere](https://gist.github.com/).
|
If your server has multiple IPs or you suspect censorship, adding `--call-home` may be a good idea to get more diagnostics. If the error is `ERROR: Unable to extract ...` and you cannot reproduce it from multiple countries, add `--dump-pages` (warning: this will yield a rather large output, redirect it to the file `log.txt` by adding `>log.txt 2>&1` to your command-line) or upload the `.dump` files you get when you add `--write-pages` [somewhere](https://gist.github.com/).
|
||||||
|
|
||||||
**Site support requests must contain an example URL**. An example URL is a URL you might want to download, like http://www.youtube.com/watch?v=BaW_jenozKc . There should be an obvious video present. Except under very special circumstances, the main page of a video service (e.g. http://www.youtube.com/ ) is *not* an example URL.
|
**Site support requests must contain an example URL**. An example URL is a URL you might want to download, like `http://www.youtube.com/watch?v=BaW_jenozKc`. There should be an obvious video present. Except under very special circumstances, the main page of a video service (e.g. `http://www.youtube.com/`) is *not* an example URL.
|
||||||
|
|
||||||
### Are you using the latest version?
|
### Are you using the latest version?
|
||||||
|
|
||||||
@@ -28,7 +42,7 @@ Before reporting any issue, type `youtube-dl -U`. This should report that you're
|
|||||||
|
|
||||||
### Is the issue already documented?
|
### Is the issue already documented?
|
||||||
|
|
||||||
Make sure that someone has not already opened the issue you're trying to open. Search at the top of the window or at https://github.com/rg3/youtube-dl/search?type=Issues . If there is an issue, feel free to write something along the lines of "This affects me as well, with version 2015.01.01. Here is some more information on the issue: ...". While some issues may be old, a new post into them often spurs rapid activity.
|
Make sure that someone has not already opened the issue you're trying to open. Search at the top of the window or browse the [GitHub Issues](https://github.com/rg3/youtube-dl/search?type=Issues) of this repository. If there is an issue, feel free to write something along the lines of "This affects me as well, with version 2015.01.01. Here is some more information on the issue: ...". While some issues may be old, a new post into them often spurs rapid activity.
|
||||||
|
|
||||||
### Why are existing options not enough?
|
### Why are existing options not enough?
|
||||||
|
|
||||||
|
14
Makefile
14
Makefile
@@ -61,34 +61,34 @@ youtube-dl: youtube_dl/*.py youtube_dl/*/*.py
|
|||||||
chmod a+x youtube-dl
|
chmod a+x youtube-dl
|
||||||
|
|
||||||
README.md: youtube_dl/*.py youtube_dl/*/*.py
|
README.md: youtube_dl/*.py youtube_dl/*/*.py
|
||||||
COLUMNS=80 python youtube_dl/__main__.py --help | python devscripts/make_readme.py
|
COLUMNS=80 $(PYTHON) youtube_dl/__main__.py --help | $(PYTHON) devscripts/make_readme.py
|
||||||
|
|
||||||
CONTRIBUTING.md: README.md
|
CONTRIBUTING.md: README.md
|
||||||
python devscripts/make_contributing.py README.md CONTRIBUTING.md
|
$(PYTHON) devscripts/make_contributing.py README.md CONTRIBUTING.md
|
||||||
|
|
||||||
supportedsites:
|
supportedsites:
|
||||||
python devscripts/make_supportedsites.py docs/supportedsites.md
|
$(PYTHON) devscripts/make_supportedsites.py docs/supportedsites.md
|
||||||
|
|
||||||
README.txt: README.md
|
README.txt: README.md
|
||||||
pandoc -f markdown -t plain README.md -o README.txt
|
pandoc -f markdown -t plain README.md -o README.txt
|
||||||
|
|
||||||
youtube-dl.1: README.md
|
youtube-dl.1: README.md
|
||||||
python devscripts/prepare_manpage.py >youtube-dl.1.temp.md
|
$(PYTHON) devscripts/prepare_manpage.py >youtube-dl.1.temp.md
|
||||||
pandoc -s -f markdown -t man youtube-dl.1.temp.md -o youtube-dl.1
|
pandoc -s -f markdown -t man youtube-dl.1.temp.md -o youtube-dl.1
|
||||||
rm -f youtube-dl.1.temp.md
|
rm -f youtube-dl.1.temp.md
|
||||||
|
|
||||||
youtube-dl.bash-completion: youtube_dl/*.py youtube_dl/*/*.py devscripts/bash-completion.in
|
youtube-dl.bash-completion: youtube_dl/*.py youtube_dl/*/*.py devscripts/bash-completion.in
|
||||||
python devscripts/bash-completion.py
|
$(PYTHON) devscripts/bash-completion.py
|
||||||
|
|
||||||
bash-completion: youtube-dl.bash-completion
|
bash-completion: youtube-dl.bash-completion
|
||||||
|
|
||||||
youtube-dl.zsh: youtube_dl/*.py youtube_dl/*/*.py devscripts/zsh-completion.in
|
youtube-dl.zsh: youtube_dl/*.py youtube_dl/*/*.py devscripts/zsh-completion.in
|
||||||
python devscripts/zsh-completion.py
|
$(PYTHON) devscripts/zsh-completion.py
|
||||||
|
|
||||||
zsh-completion: youtube-dl.zsh
|
zsh-completion: youtube-dl.zsh
|
||||||
|
|
||||||
youtube-dl.fish: youtube_dl/*.py youtube_dl/*/*.py devscripts/fish-completion.in
|
youtube-dl.fish: youtube_dl/*.py youtube_dl/*/*.py devscripts/fish-completion.in
|
||||||
python devscripts/fish-completion.py
|
$(PYTHON) devscripts/fish-completion.py
|
||||||
|
|
||||||
fish-completion: youtube-dl.fish
|
fish-completion: youtube-dl.fish
|
||||||
|
|
||||||
|
51
README.md
51
README.md
@@ -35,7 +35,7 @@ You can also use pip:
|
|||||||
|
|
||||||
sudo pip install youtube-dl
|
sudo pip install youtube-dl
|
||||||
|
|
||||||
Alternatively, refer to the [developer instructions](#developer-instructions) for how to check out and work with the git repository. For further options, including PGP signatures, see https://rg3.github.io/youtube-dl/download.html .
|
Alternatively, refer to the [developer instructions](#developer-instructions) for how to check out and work with the git repository. For further options, including PGP signatures, see the [youtube-dl Download Page](https://rg3.github.io/youtube-dl/download.html).
|
||||||
|
|
||||||
# DESCRIPTION
|
# DESCRIPTION
|
||||||
**youtube-dl** is a small command-line program to download videos from
|
**youtube-dl** is a small command-line program to download videos from
|
||||||
@@ -319,7 +319,8 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
--all-formats Download all available video formats
|
--all-formats Download all available video formats
|
||||||
--prefer-free-formats Prefer free video formats unless a specific
|
--prefer-free-formats Prefer free video formats unless a specific
|
||||||
one is requested
|
one is requested
|
||||||
-F, --list-formats List all available formats
|
-F, --list-formats List all available formats of requested
|
||||||
|
videos
|
||||||
--youtube-skip-dash-manifest Do not download the DASH manifests and
|
--youtube-skip-dash-manifest Do not download the DASH manifests and
|
||||||
related data on YouTube videos
|
related data on YouTube videos
|
||||||
--merge-output-format FORMAT If a merge is required (e.g.
|
--merge-output-format FORMAT If a merge is required (e.g.
|
||||||
@@ -329,8 +330,8 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
|
|
||||||
## Subtitle Options:
|
## Subtitle Options:
|
||||||
--write-sub Write subtitle file
|
--write-sub Write subtitle file
|
||||||
--write-auto-sub Write automatic subtitle file (YouTube
|
--write-auto-sub Write automatically generated subtitle file
|
||||||
only)
|
(YouTube only)
|
||||||
--all-subs Download all the available subtitles of the
|
--all-subs Download all the available subtitles of the
|
||||||
video
|
video
|
||||||
--list-subs List all available subtitles for the video
|
--list-subs List all available subtitles for the video
|
||||||
@@ -413,7 +414,7 @@ You can configure youtube-dl by placing any supported command line option to a c
|
|||||||
|
|
||||||
You can use `--ignore-config` if you want to disable the configuration file for a particular youtube-dl run.
|
You can use `--ignore-config` if you want to disable the configuration file for a particular youtube-dl run.
|
||||||
|
|
||||||
### Authentication with `.netrc` file ###
|
### Authentication with `.netrc` file
|
||||||
|
|
||||||
You may also want to configure automatic credentials storage for extractors that support authentication (by providing login and password with `--username` and `--password`) in order not to pass credentials as command line arguments on every youtube-dl execution and prevent tracking plain text passwords in the shell command history. You can achieve this using a [`.netrc` file](http://stackoverflow.com/tags/.netrc/info) on per extractor basis. For that you will need to create a`.netrc` file in your `$HOME` and restrict permissions to read/write by you only:
|
You may also want to configure automatic credentials storage for extractors that support authentication (by providing login and password with `--username` and `--password`) in order not to pass credentials as command line arguments on every youtube-dl execution and prevent tracking plain text passwords in the shell command history. You can achieve this using a [`.netrc` file](http://stackoverflow.com/tags/.netrc/info) on per extractor basis. For that you will need to create a`.netrc` file in your `$HOME` and restrict permissions to read/write by you only:
|
||||||
```
|
```
|
||||||
@@ -534,6 +535,12 @@ Most people asking this question are not aware that youtube-dl now defaults to d
|
|||||||
|
|
||||||
Apparently YouTube requires you to pass a CAPTCHA test if you download too much. We're [considering to provide a way to let you solve the CAPTCHA](https://github.com/rg3/youtube-dl/issues/154), but at the moment, your best course of action is pointing a webbrowser to the youtube URL, solving the CAPTCHA, and restart youtube-dl.
|
Apparently YouTube requires you to pass a CAPTCHA test if you download too much. We're [considering to provide a way to let you solve the CAPTCHA](https://github.com/rg3/youtube-dl/issues/154), but at the moment, your best course of action is pointing a webbrowser to the youtube URL, solving the CAPTCHA, and restart youtube-dl.
|
||||||
|
|
||||||
|
### Do I need any other programs?
|
||||||
|
|
||||||
|
youtube-dl works fine on its own on most sites. However, if you want to convert video/audio, you'll need [avconv](https://libav.org/) or [ffmpeg](https://www.ffmpeg.org/). On some sites - most notably YouTube - videos can be retrieved in a higher quality format without sound. youtube-dl will detect whether avconv/ffmpeg is present and automatically pick the best option.
|
||||||
|
|
||||||
|
Videos or video formats streamed via RTMP protocol can only be downloaded when [rtmpdump](https://rtmpdump.mplayerhq.hu/) is installed. Downloading MMS and RTSP videos requires either [mplayer](http://mplayerhq.hu/) or [mpv](https://mpv.io/) to be installed.
|
||||||
|
|
||||||
### I have downloaded a video but how can I play it?
|
### I have downloaded a video but how can I play it?
|
||||||
|
|
||||||
Once the video is fully downloaded, use any video player, such as [vlc](http://www.videolan.org) or [mplayer](http://www.mplayerhq.hu/).
|
Once the video is fully downloaded, use any video player, such as [vlc](http://www.videolan.org) or [mplayer](http://www.mplayerhq.hu/).
|
||||||
@@ -552,11 +559,11 @@ If you want to play the video on a machine that is not running youtube-dl, you c
|
|||||||
|
|
||||||
YouTube has switched to a new video info format in July 2011 which is not supported by old versions of youtube-dl. See [above](#how-do-i-update-youtube-dl) for how to update youtube-dl.
|
YouTube has switched to a new video info format in July 2011 which is not supported by old versions of youtube-dl. See [above](#how-do-i-update-youtube-dl) for how to update youtube-dl.
|
||||||
|
|
||||||
### ERROR: unable to download video ###
|
### ERROR: unable to download video
|
||||||
|
|
||||||
YouTube requires an additional signature since September 2012 which is not supported by old versions of youtube-dl. See [above](#how-do-i-update-youtube-dl) for how to update youtube-dl.
|
YouTube requires an additional signature since September 2012 which is not supported by old versions of youtube-dl. See [above](#how-do-i-update-youtube-dl) for how to update youtube-dl.
|
||||||
|
|
||||||
### Video URL contains an ampersand and I'm getting some strange output `[1] 2839` or `'v' is not recognized as an internal or external command` ###
|
### Video URL contains an ampersand and I'm getting some strange output `[1] 2839` or `'v' is not recognized as an internal or external command`
|
||||||
|
|
||||||
That's actually the output from your shell. Since ampersand is one of the special shell characters it's interpreted by the shell preventing you from passing the whole URL to youtube-dl. To disable your shell from interpreting the ampersands (or any other special characters) you have to either put the whole URL in quotes or escape them with a backslash (which approach will work depends on your shell).
|
That's actually the output from your shell. Since ampersand is one of the special shell characters it's interpreted by the shell preventing you from passing the whole URL to youtube-dl. To disable your shell from interpreting the ampersands (or any other special characters) you have to either put the whole URL in quotes or escape them with a backslash (which approach will work depends on your shell).
|
||||||
|
|
||||||
@@ -580,7 +587,7 @@ In February 2015, the new YouTube player contained a character sequence in a str
|
|||||||
|
|
||||||
These two error codes indicate that the service is blocking your IP address because of overuse. Contact the service and ask them to unblock your IP address, or - if you have acquired a whitelisted IP address already - use the [`--proxy` or `--source-address` options](#network-options) to select another IP address.
|
These two error codes indicate that the service is blocking your IP address because of overuse. Contact the service and ask them to unblock your IP address, or - if you have acquired a whitelisted IP address already - use the [`--proxy` or `--source-address` options](#network-options) to select another IP address.
|
||||||
|
|
||||||
### SyntaxError: Non-ASCII character ###
|
### SyntaxError: Non-ASCII character
|
||||||
|
|
||||||
The error
|
The error
|
||||||
|
|
||||||
@@ -609,7 +616,7 @@ From then on, after restarting your shell, you will be able to access both youtu
|
|||||||
|
|
||||||
Use the `-o` to specify an [output template](#output-template), for example `-o "/home/user/videos/%(title)s-%(id)s.%(ext)s"`. If you want this for all of your downloads, put the option into your [configuration file](#configuration).
|
Use the `-o` to specify an [output template](#output-template), for example `-o "/home/user/videos/%(title)s-%(id)s.%(ext)s"`. If you want this for all of your downloads, put the option into your [configuration file](#configuration).
|
||||||
|
|
||||||
### How do I download a video starting with a `-` ?
|
### How do I download a video starting with a `-`?
|
||||||
|
|
||||||
Either prepend `http://www.youtube.com/watch?v=` or separate the ID from the options with `--`:
|
Either prepend `http://www.youtube.com/watch?v=` or separate the ID from the options with `--`:
|
||||||
|
|
||||||
@@ -791,11 +798,25 @@ with youtube_dl.YoutubeDL(ydl_opts) as ydl:
|
|||||||
|
|
||||||
# BUGS
|
# BUGS
|
||||||
|
|
||||||
Bugs and suggestions should be reported at: <https://github.com/rg3/youtube-dl/issues> . Unless you were prompted so or there is another pertinent reason (e.g. GitHub fails to accept the bug report), please do not send bug reports via personal email. For discussions, join us in the irc channel #youtube-dl on freenode.
|
Bugs and suggestions should be reported at: <https://github.com/rg3/youtube-dl/issues>. Unless you were prompted so or there is another pertinent reason (e.g. GitHub fails to accept the bug report), please do not send bug reports via personal email. For discussions, join us in the IRC channel [#youtube-dl](irc://chat.freenode.net/#youtube-dl) on freenode ([webchat](http://webchat.freenode.net/?randomnick=1&channels=youtube-dl)).
|
||||||
|
|
||||||
**Please include the full output of youtube-dl when run with `-v`**.
|
**Please include the full output of youtube-dl when run with `-v`**, i.e. add `-v` flag to your command line, copy the **whole** output and post it in the issue body wrapped in \`\`\` for better formatting. It should look similar to this:
|
||||||
|
```
|
||||||
|
$ youtube-dl -v http://www.youtube.com/watch?v=BaW_jenozKcj
|
||||||
|
[debug] System config: []
|
||||||
|
[debug] User config: []
|
||||||
|
[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] youtube-dl version 2015.12.06
|
||||||
|
[debug] Git HEAD: 135392e
|
||||||
|
[debug] Python version 2.6.6 - Windows-2003Server-5.2.3790-SP2
|
||||||
|
[debug] exe versions: ffmpeg N-75573-g1d0487f, ffprobe N-75573-g1d0487f, rtmpdump 2.4
|
||||||
|
[debug] Proxy map: {}
|
||||||
|
...
|
||||||
|
```
|
||||||
|
**Do not post screenshots of verbose log only plain text is acceptable.**
|
||||||
|
|
||||||
The output (including the first lines) contain important debugging information. Issues without the full output are often not reproducible and therefore do not get solved in short order, if ever.
|
The output (including the first lines) contains important debugging information. Issues without the full output are often not reproducible and therefore do not get solved in short order, if ever.
|
||||||
|
|
||||||
Please re-read your issue once again to avoid a couple of common mistakes (you can and should use this as a checklist):
|
Please re-read your issue once again to avoid a couple of common mistakes (you can and should use this as a checklist):
|
||||||
|
|
||||||
@@ -815,7 +836,7 @@ For bug reports, this means that your report should contain the *complete* outpu
|
|||||||
|
|
||||||
If your server has multiple IPs or you suspect censorship, adding `--call-home` may be a good idea to get more diagnostics. If the error is `ERROR: Unable to extract ...` and you cannot reproduce it from multiple countries, add `--dump-pages` (warning: this will yield a rather large output, redirect it to the file `log.txt` by adding `>log.txt 2>&1` to your command-line) or upload the `.dump` files you get when you add `--write-pages` [somewhere](https://gist.github.com/).
|
If your server has multiple IPs or you suspect censorship, adding `--call-home` may be a good idea to get more diagnostics. If the error is `ERROR: Unable to extract ...` and you cannot reproduce it from multiple countries, add `--dump-pages` (warning: this will yield a rather large output, redirect it to the file `log.txt` by adding `>log.txt 2>&1` to your command-line) or upload the `.dump` files you get when you add `--write-pages` [somewhere](https://gist.github.com/).
|
||||||
|
|
||||||
**Site support requests must contain an example URL**. An example URL is a URL you might want to download, like http://www.youtube.com/watch?v=BaW_jenozKc . There should be an obvious video present. Except under very special circumstances, the main page of a video service (e.g. http://www.youtube.com/ ) is *not* an example URL.
|
**Site support requests must contain an example URL**. An example URL is a URL you might want to download, like `http://www.youtube.com/watch?v=BaW_jenozKc`. There should be an obvious video present. Except under very special circumstances, the main page of a video service (e.g. `http://www.youtube.com/`) is *not* an example URL.
|
||||||
|
|
||||||
### Are you using the latest version?
|
### Are you using the latest version?
|
||||||
|
|
||||||
@@ -823,7 +844,7 @@ Before reporting any issue, type `youtube-dl -U`. This should report that you're
|
|||||||
|
|
||||||
### Is the issue already documented?
|
### Is the issue already documented?
|
||||||
|
|
||||||
Make sure that someone has not already opened the issue you're trying to open. Search at the top of the window or at https://github.com/rg3/youtube-dl/search?type=Issues . If there is an issue, feel free to write something along the lines of "This affects me as well, with version 2015.01.01. Here is some more information on the issue: ...". While some issues may be old, a new post into them often spurs rapid activity.
|
Make sure that someone has not already opened the issue you're trying to open. Search at the top of the window or browse the [GitHub Issues](https://github.com/rg3/youtube-dl/search?type=Issues) of this repository. If there is an issue, feel free to write something along the lines of "This affects me as well, with version 2015.01.01. Here is some more information on the issue: ...". While some issues may be old, a new post into them often spurs rapid activity.
|
||||||
|
|
||||||
### Why are existing options not enough?
|
### Why are existing options not enough?
|
||||||
|
|
||||||
@@ -853,4 +874,4 @@ It may sound strange, but some bug reports we receive are completely unrelated t
|
|||||||
|
|
||||||
youtube-dl is released into the public domain by the copyright holders.
|
youtube-dl is released into the public domain by the copyright holders.
|
||||||
|
|
||||||
This README file was originally written by Daniel Bolton (<https://github.com/dbbolton>) and is likewise released into the public domain.
|
This README file was originally written by [Daniel Bolton](https://github.com/dbbolton) and is likewise released into the public domain.
|
||||||
|
@@ -15,8 +15,12 @@
|
|||||||
- **abc.net.au**
|
- **abc.net.au**
|
||||||
- **Abc7News**
|
- **Abc7News**
|
||||||
- **AcademicEarth:Course**
|
- **AcademicEarth:Course**
|
||||||
|
- **acast**
|
||||||
|
- **acast:channel**
|
||||||
- **AddAnime**
|
- **AddAnime**
|
||||||
- **AdobeTV**
|
- **AdobeTV**
|
||||||
|
- **AdobeTVChannel**
|
||||||
|
- **AdobeTVShow**
|
||||||
- **AdobeTVVideo**
|
- **AdobeTVVideo**
|
||||||
- **AdultSwim**
|
- **AdultSwim**
|
||||||
- **Aftenposten**
|
- **Aftenposten**
|
||||||
@@ -43,6 +47,7 @@
|
|||||||
- **arte.tv:future**
|
- **arte.tv:future**
|
||||||
- **AtresPlayer**
|
- **AtresPlayer**
|
||||||
- **ATTTechChannel**
|
- **ATTTechChannel**
|
||||||
|
- **AudiMedia**
|
||||||
- **audiomack**
|
- **audiomack**
|
||||||
- **audiomack:album**
|
- **audiomack:album**
|
||||||
- **Azubu**
|
- **Azubu**
|
||||||
@@ -67,7 +72,8 @@
|
|||||||
- **Bpb**: Bundeszentrale für politische Bildung
|
- **Bpb**: Bundeszentrale für politische Bildung
|
||||||
- **BR**: Bayerischer Rundfunk Mediathek
|
- **BR**: Bayerischer Rundfunk Mediathek
|
||||||
- **Break**
|
- **Break**
|
||||||
- **Brightcove**
|
- **brightcove:legacy**
|
||||||
|
- **brightcove:new**
|
||||||
- **bt:article**: Bergens Tidende Articles
|
- **bt:article**: Bergens Tidende Articles
|
||||||
- **bt:vestlendingen**: Bergens Tidende - Vestlendingen
|
- **bt:vestlendingen**: Bergens Tidende - Vestlendingen
|
||||||
- **BuzzFeed**
|
- **BuzzFeed**
|
||||||
@@ -91,8 +97,10 @@
|
|||||||
- **Clipfish**
|
- **Clipfish**
|
||||||
- **cliphunter**
|
- **cliphunter**
|
||||||
- **Clipsyndicate**
|
- **Clipsyndicate**
|
||||||
|
- **cloudtime**: CloudTime
|
||||||
- **Cloudy**
|
- **Cloudy**
|
||||||
- **Clubic**
|
- **Clubic**
|
||||||
|
- **Clyp**
|
||||||
- **cmt.com**
|
- **cmt.com**
|
||||||
- **CNET**
|
- **CNET**
|
||||||
- **CNN**
|
- **CNN**
|
||||||
@@ -122,10 +130,12 @@
|
|||||||
- **DctpTv**
|
- **DctpTv**
|
||||||
- **DeezerPlaylist**
|
- **DeezerPlaylist**
|
||||||
- **defense.gouv.fr**
|
- **defense.gouv.fr**
|
||||||
|
- **democracynow**
|
||||||
- **DHM**: Filmarchiv - Deutsches Historisches Museum
|
- **DHM**: Filmarchiv - Deutsches Historisches Museum
|
||||||
- **Discovery**
|
- **Discovery**
|
||||||
- **Dotsub**
|
- **Dotsub**
|
||||||
- **DouyuTV**: 斗鱼
|
- **DouyuTV**: 斗鱼
|
||||||
|
- **DPlay**
|
||||||
- **dramafever**
|
- **dramafever**
|
||||||
- **dramafever:series**
|
- **dramafever:series**
|
||||||
- **DRBonanza**
|
- **DRBonanza**
|
||||||
@@ -178,7 +188,9 @@
|
|||||||
- **Freesound**
|
- **Freesound**
|
||||||
- **freespeech.org**
|
- **freespeech.org**
|
||||||
- **FreeVideo**
|
- **FreeVideo**
|
||||||
|
- **Funimation**
|
||||||
- **FunnyOrDie**
|
- **FunnyOrDie**
|
||||||
|
- **GameInformer**
|
||||||
- **Gamekings**
|
- **Gamekings**
|
||||||
- **GameOne**
|
- **GameOne**
|
||||||
- **gameone:playlist**
|
- **gameone:playlist**
|
||||||
@@ -194,10 +206,10 @@
|
|||||||
- **Giga**
|
- **Giga**
|
||||||
- **Glide**: Glide mobile video messages (glide.me)
|
- **Glide**: Glide mobile video messages (glide.me)
|
||||||
- **Globo**
|
- **Globo**
|
||||||
|
- **GloboArticle**
|
||||||
- **GodTube**
|
- **GodTube**
|
||||||
- **GoldenMoustache**
|
- **GoldenMoustache**
|
||||||
- **Golem**
|
- **Golem**
|
||||||
- **GorillaVid**: GorillaVid.in, daclips.in, movpod.in, fastvideo.in, realvid.net and filehoot.com
|
|
||||||
- **Goshgay**
|
- **Goshgay**
|
||||||
- **Groupon**
|
- **Groupon**
|
||||||
- **Hark**
|
- **Hark**
|
||||||
@@ -281,7 +293,7 @@
|
|||||||
- **macgamestore**: MacGameStore trailers
|
- **macgamestore**: MacGameStore trailers
|
||||||
- **mailru**: Видео@Mail.Ru
|
- **mailru**: Видео@Mail.Ru
|
||||||
- **Malemotion**
|
- **Malemotion**
|
||||||
- **MDR**
|
- **MDR**: MDR.DE and KiKA
|
||||||
- **media.ccc.de**
|
- **media.ccc.de**
|
||||||
- **metacafe**
|
- **metacafe**
|
||||||
- **Metacritic**
|
- **Metacritic**
|
||||||
@@ -303,7 +315,6 @@
|
|||||||
- **MovieClips**
|
- **MovieClips**
|
||||||
- **MovieFap**
|
- **MovieFap**
|
||||||
- **Moviezine**
|
- **Moviezine**
|
||||||
- **movshare**: MovShare
|
|
||||||
- **MPORA**
|
- **MPORA**
|
||||||
- **MSNBC**
|
- **MSNBC**
|
||||||
- **MTV**
|
- **MTV**
|
||||||
@@ -364,6 +375,7 @@
|
|||||||
- **nowness:playlist**
|
- **nowness:playlist**
|
||||||
- **nowness:series**
|
- **nowness:series**
|
||||||
- **NowTV**
|
- **NowTV**
|
||||||
|
- **NowTVList**
|
||||||
- **nowvideo**: NowVideo
|
- **nowvideo**: NowVideo
|
||||||
- **npo**: npo.nl and ntr.nl
|
- **npo**: npo.nl and ntr.nl
|
||||||
- **npo.nl:live**
|
- **npo.nl:live**
|
||||||
@@ -389,7 +401,7 @@
|
|||||||
- **orf:tvthek**: ORF TVthek
|
- **orf:tvthek**: ORF TVthek
|
||||||
- **parliamentlive.tv**: UK parliament videos
|
- **parliamentlive.tv**: UK parliament videos
|
||||||
- **Patreon**
|
- **Patreon**
|
||||||
- **PBS**
|
- **pbs**: Public Broadcasting Service (PBS) and member stations: PBS: Public Broadcasting Service, APT - Alabama Public Television (WBIQ), GPB/Georgia Public Broadcasting (WGTV), Mississippi Public Broadcasting (WMPN), Nashville Public Television (WNPT), WFSU-TV (WFSU), WSRE (WSRE), WTCI (WTCI), WPBA/Channel 30 (WPBA), Alaska Public Media (KAKM), Arizona PBS (KAET), KNME-TV/Channel 5 (KNME), Vegas PBS (KLVX), AETN/ARKANSAS ETV NETWORK (KETS), KET (WKLE), WKNO/Channel 10 (WKNO), LPB/LOUISIANA PUBLIC BROADCASTING (WLPB), OETA (KETA), Ozarks Public Television (KOZK), WSIU Public Broadcasting (WSIU), KEET TV (KEET), KIXE/Channel 9 (KIXE), KPBS San Diego (KPBS), KQED (KQED), KVIE Public Television (KVIE), PBS SoCal/KOCE (KOCE), ValleyPBS (KVPT), CONNECTICUT PUBLIC TELEVISION (WEDH), KNPB Channel 5 (KNPB), SOPTV (KSYS), Rocky Mountain PBS (KRMA), KENW-TV3 (KENW), KUED Channel 7 (KUED), Wyoming PBS (KCWC), Colorado Public Television / KBDI 12 (KBDI), KBYU-TV (KBYU), Thirteen/WNET New York (WNET), WGBH/Channel 2 (WGBH), WGBY (WGBY), NJTV Public Media NJ (WNJT), WLIW21 (WLIW), mpt/Maryland Public Television (WMPB), WETA Television and Radio (WETA), WHYY (WHYY), PBS 39 (WLVT), WVPT - Your Source for PBS and More! (WVPT), Howard University Television (WHUT), WEDU PBS (WEDU), WGCU Public Media (WGCU), WPBT2 (WPBT), WUCF TV (WUCF), WUFT/Channel 5 (WUFT), WXEL/Channel 42 (WXEL), WLRN/Channel 17 (WLRN), WUSF Public Broadcasting (WUSF), ETV (WRLK), UNC-TV (WUNC), PBS Hawaii - Oceanic Cable Channel 10 (KHET), Idaho Public Television (KAID), KSPS (KSPS), OPB (KOPB), KWSU/Channel 10 & KTNW/Channel 31 (KWSU), WILL-TV (WILL), Network Knowledge - WSEC/Springfield (WSEC), WTTW11 (WTTW), Iowa Public Television/IPTV (KDIN), Nine Network (KETC), PBS39 Fort Wayne (WFWA), WFYI Indianapolis (WFYI), Milwaukee Public Television (WMVS), WNIN (WNIN), WNIT Public Television (WNIT), WPT (WPNE), WVUT/Channel 22 (WVUT), WEIU/Channel 51 (WEIU), WQPT-TV (WQPT), WYCC PBS Chicago (WYCC), WIPB-TV (WIPB), WTIU (WTIU), CET (WCET), ThinkTVNetwork (WPTD), WBGU-TV (WBGU), WGVU TV (WGVU), NET1 (KUON), Pioneer Public Television (KWCM), SDPB Television (KUSD), TPT (KTCA), KSMQ (KSMQ), KPTS/Channel 8 (KPTS), KTWU/Channel 11 (KTWU), East Tennessee PBS (WSJK), WCTE-TV (WCTE), WLJT, Channel 11 (WLJT), WOSU TV (WOSU), WOUB/WOUC (WOUB), WVPB (WVPB), WKYU-PBS (WKYU), KERA 13 (KERA), MPBN (WCBB), Mountain Lake PBS (WCFE), NHPTV (WENH), Vermont PBS (WETK), witf (WITF), WQED Multimedia (WQED), WMHT Educational Telecommunications (WMHT), Q-TV (WDCQ), WTVS Detroit Public TV (WTVS), CMU Public Television (WCMU), WKAR-TV (WKAR), WNMU-TV Public TV 13 (WNMU), WDSE - WRPT (WDSE), WGTE TV (WGTE), Lakeland Public Television (KAWE), KMOS-TV - Channels 6.1, 6.2 and 6.3 (KMOS), MontanaPBS (KUSM), KRWG/Channel 22 (KRWG), KACV (KACV), KCOS/Channel 13 (KCOS), WCNY/Channel 24 (WCNY), WNED (WNED), WPBS (WPBS), WSKG Public TV (WSKG), WXXI (WXXI), WPSU (WPSU), WVIA Public Media Studios (WVIA), WTVI (WTVI), Western Reserve PBS (WNEO), WVIZ/PBS ideastream (WVIZ), KCTS 9 (KCTS), Basin PBS (KPBT), KUHT / Channel 8 (KUHT), KLRN (KLRN), KLRU (KLRU), WTJX Channel 12 (WTJX), WCVE PBS (WCVE), KBTC Public Television (KBTC)
|
||||||
- **Periscope**: Periscope
|
- **Periscope**: Periscope
|
||||||
- **PhilharmonieDeParis**: Philharmonie de Paris
|
- **PhilharmonieDeParis**: Philharmonie de Paris
|
||||||
- **Phoenix**
|
- **Phoenix**
|
||||||
@@ -423,7 +435,6 @@
|
|||||||
- **qqmusic:playlist**: QQ音乐 - 歌单
|
- **qqmusic:playlist**: QQ音乐 - 歌单
|
||||||
- **qqmusic:singer**: QQ音乐 - 歌手
|
- **qqmusic:singer**: QQ音乐 - 歌手
|
||||||
- **qqmusic:toplist**: QQ音乐 - 排行榜
|
- **qqmusic:toplist**: QQ音乐 - 排行榜
|
||||||
- **Quickscope**: Quick Scope
|
|
||||||
- **QuickVid**
|
- **QuickVid**
|
||||||
- **R7**
|
- **R7**
|
||||||
- **radio.de**
|
- **radio.de**
|
||||||
@@ -476,6 +487,8 @@
|
|||||||
- **Shared**: shared.sx and vivo.sx
|
- **Shared**: shared.sx and vivo.sx
|
||||||
- **ShareSix**
|
- **ShareSix**
|
||||||
- **Sina**
|
- **Sina**
|
||||||
|
- **skynewsarabia:video**
|
||||||
|
- **skynewsarabia:video**
|
||||||
- **Slideshare**
|
- **Slideshare**
|
||||||
- **Slutload**
|
- **Slutload**
|
||||||
- **smotri**: Smotri.com
|
- **smotri**: Smotri.com
|
||||||
@@ -490,6 +503,7 @@
|
|||||||
- **soompi:show**
|
- **soompi:show**
|
||||||
- **soundcloud**
|
- **soundcloud**
|
||||||
- **soundcloud:playlist**
|
- **soundcloud:playlist**
|
||||||
|
- **soundcloud:search**: Soundcloud search
|
||||||
- **soundcloud:set**
|
- **soundcloud:set**
|
||||||
- **soundcloud:user**
|
- **soundcloud:user**
|
||||||
- **soundgasm**
|
- **soundgasm**
|
||||||
@@ -616,7 +630,6 @@
|
|||||||
- **video.mit.edu**
|
- **video.mit.edu**
|
||||||
- **VideoDetective**
|
- **VideoDetective**
|
||||||
- **videofy.me**
|
- **videofy.me**
|
||||||
- **videolectures.net**
|
|
||||||
- **VideoMega**
|
- **VideoMega**
|
||||||
- **VideoPremium**
|
- **VideoPremium**
|
||||||
- **VideoTt**: video.tt - Your True Tube
|
- **VideoTt**: video.tt - Your True Tube
|
||||||
@@ -626,6 +639,7 @@
|
|||||||
- **vier**
|
- **vier**
|
||||||
- **vier:videos**
|
- **vier:videos**
|
||||||
- **Viewster**
|
- **Viewster**
|
||||||
|
- **Viidea**
|
||||||
- **viki**
|
- **viki**
|
||||||
- **viki:channel**
|
- **viki:channel**
|
||||||
- **vimeo**
|
- **vimeo**
|
||||||
@@ -660,6 +674,7 @@
|
|||||||
- **WebOfStories**
|
- **WebOfStories**
|
||||||
- **WebOfStoriesPlaylist**
|
- **WebOfStoriesPlaylist**
|
||||||
- **Weibo**
|
- **Weibo**
|
||||||
|
- **wholecloud**: WholeCloud
|
||||||
- **Wimp**
|
- **Wimp**
|
||||||
- **Wistia**
|
- **Wistia**
|
||||||
- **WNL**
|
- **WNL**
|
||||||
@@ -668,6 +683,7 @@
|
|||||||
- **WSJ**: Wall Street Journal
|
- **WSJ**: Wall Street Journal
|
||||||
- **XBef**
|
- **XBef**
|
||||||
- **XboxClips**
|
- **XboxClips**
|
||||||
|
- **XFileShare**: XFileShare based sites: GorillaVid.in, daclips.in, movpod.in, fastvideo.in, realvid.net, filehoot.com and vidto.me
|
||||||
- **XHamster**
|
- **XHamster**
|
||||||
- **XHamsterEmbed**
|
- **XHamsterEmbed**
|
||||||
- **XMinus**
|
- **XMinus**
|
||||||
@@ -702,6 +718,7 @@
|
|||||||
- **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)
|
||||||
|
- **youtube:user:playlists**: YouTube.com user playlists
|
||||||
- **youtube:watchlater**: Youtube watch later list, ":ytwatchlater" for short (requires authentication)
|
- **youtube:watchlater**: Youtube watch later list, ":ytwatchlater" for short (requires authentication)
|
||||||
- **Zapiks**
|
- **Zapiks**
|
||||||
- **ZDF**
|
- **ZDF**
|
||||||
|
2
setup.py
2
setup.py
@@ -28,7 +28,7 @@ py2exe_options = {
|
|||||||
"compressed": 1,
|
"compressed": 1,
|
||||||
"optimize": 2,
|
"optimize": 2,
|
||||||
"dist_dir": '.',
|
"dist_dir": '.',
|
||||||
"dll_excludes": ['w9xpopen.exe'],
|
"dll_excludes": ['w9xpopen.exe', 'crypt32.dll'],
|
||||||
}
|
}
|
||||||
|
|
||||||
py2exe_console = [{
|
py2exe_console = [{
|
||||||
|
@@ -121,8 +121,8 @@ class TestAllURLsMatching(unittest.TestCase):
|
|||||||
|
|
||||||
def test_pbs(self):
|
def test_pbs(self):
|
||||||
# https://github.com/rg3/youtube-dl/issues/2350
|
# https://github.com/rg3/youtube-dl/issues/2350
|
||||||
self.assertMatch('http://video.pbs.org/viralplayer/2365173446/', ['PBS'])
|
self.assertMatch('http://video.pbs.org/viralplayer/2365173446/', ['pbs'])
|
||||||
self.assertMatch('http://video.pbs.org/widget/partnerplayer/980042464/', ['PBS'])
|
self.assertMatch('http://video.pbs.org/widget/partnerplayer/980042464/', ['pbs'])
|
||||||
|
|
||||||
def test_yahoo_https(self):
|
def test_yahoo_https(self):
|
||||||
# https://github.com/rg3/youtube-dl/issues/2701
|
# https://github.com/rg3/youtube-dl/issues/2701
|
||||||
|
@@ -13,8 +13,10 @@ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|||||||
from youtube_dl.utils import get_filesystem_encoding
|
from youtube_dl.utils import get_filesystem_encoding
|
||||||
from youtube_dl.compat import (
|
from youtube_dl.compat import (
|
||||||
compat_getenv,
|
compat_getenv,
|
||||||
|
compat_etree_fromstring,
|
||||||
compat_expanduser,
|
compat_expanduser,
|
||||||
compat_shlex_split,
|
compat_shlex_split,
|
||||||
|
compat_str,
|
||||||
compat_urllib_parse_unquote,
|
compat_urllib_parse_unquote,
|
||||||
compat_urllib_parse_unquote_plus,
|
compat_urllib_parse_unquote_plus,
|
||||||
)
|
)
|
||||||
@@ -71,5 +73,20 @@ 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'])
|
||||||
|
|
||||||
|
def test_compat_etree_fromstring(self):
|
||||||
|
xml = '''
|
||||||
|
<root foo="bar" spam="中文">
|
||||||
|
<normal>foo</normal>
|
||||||
|
<chinese>中文</chinese>
|
||||||
|
<foo><bar>spam</bar></foo>
|
||||||
|
</root>
|
||||||
|
'''
|
||||||
|
doc = compat_etree_fromstring(xml.encode('utf-8'))
|
||||||
|
self.assertTrue(isinstance(doc.attrib['foo'], compat_str))
|
||||||
|
self.assertTrue(isinstance(doc.attrib['spam'], compat_str))
|
||||||
|
self.assertTrue(isinstance(doc.find('normal').text, compat_str))
|
||||||
|
self.assertTrue(isinstance(doc.find('chinese').text, compat_str))
|
||||||
|
self.assertTrue(isinstance(doc.find('foo/bar').text, compat_str))
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@@ -102,7 +102,7 @@ def generator(test_case):
|
|||||||
|
|
||||||
params = get_params(test_case.get('params', {}))
|
params = get_params(test_case.get('params', {}))
|
||||||
if is_playlist and 'playlist' not in test_case:
|
if is_playlist and 'playlist' not in test_case:
|
||||||
params.setdefault('extract_flat', True)
|
params.setdefault('extract_flat', 'in_playlist')
|
||||||
params.setdefault('skip_download', True)
|
params.setdefault('skip_download', True)
|
||||||
|
|
||||||
ydl = YoutubeDL(params, auto_init=False)
|
ydl = YoutubeDL(params, auto_init=False)
|
||||||
|
@@ -19,6 +19,9 @@ class TestJSInterpreter(unittest.TestCase):
|
|||||||
jsi = JSInterpreter('function x3(){return 42;}')
|
jsi = JSInterpreter('function x3(){return 42;}')
|
||||||
self.assertEqual(jsi.call_function('x3'), 42)
|
self.assertEqual(jsi.call_function('x3'), 42)
|
||||||
|
|
||||||
|
jsi = JSInterpreter('var x5 = function(){return 42;}')
|
||||||
|
self.assertEqual(jsi.call_function('x5'), 42)
|
||||||
|
|
||||||
def test_calc(self):
|
def test_calc(self):
|
||||||
jsi = JSInterpreter('function x4(a){return 2*a+1;}')
|
jsi = JSInterpreter('function x4(a){return 2*a+1;}')
|
||||||
self.assertEqual(jsi.call_function('x4', 3), 7)
|
self.assertEqual(jsi.call_function('x4', 3), 7)
|
||||||
|
@@ -28,6 +28,7 @@ from youtube_dl.extractor import (
|
|||||||
ThePlatformFeedIE,
|
ThePlatformFeedIE,
|
||||||
RTVEALaCartaIE,
|
RTVEALaCartaIE,
|
||||||
FunnyOrDieIE,
|
FunnyOrDieIE,
|
||||||
|
DemocracynowIE,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -346,5 +347,25 @@ class TestFunnyOrDieSubtitles(BaseTestSubtitles):
|
|||||||
self.assertEqual(md5(subtitles['en']), 'c5593c193eacd353596c11c2d4f9ecc4')
|
self.assertEqual(md5(subtitles['en']), 'c5593c193eacd353596c11c2d4f9ecc4')
|
||||||
|
|
||||||
|
|
||||||
|
class TestDemocracynowSubtitles(BaseTestSubtitles):
|
||||||
|
url = 'http://www.democracynow.org/shows/2015/7/3'
|
||||||
|
IE = DemocracynowIE
|
||||||
|
|
||||||
|
def test_allsubtitles(self):
|
||||||
|
self.DL.params['writesubtitles'] = True
|
||||||
|
self.DL.params['allsubtitles'] = True
|
||||||
|
subtitles = self.getSubtitles()
|
||||||
|
self.assertEqual(set(subtitles.keys()), set(['en']))
|
||||||
|
self.assertEqual(md5(subtitles['en']), 'acaca989e24a9e45a6719c9b3d60815c')
|
||||||
|
|
||||||
|
def test_subtitles_in_page(self):
|
||||||
|
self.url = 'http://www.democracynow.org/2015/7/3/this_flag_comes_down_today_bree'
|
||||||
|
self.DL.params['writesubtitles'] = True
|
||||||
|
self.DL.params['allsubtitles'] = True
|
||||||
|
subtitles = self.getSubtitles()
|
||||||
|
self.assertEqual(set(subtitles.keys()), set(['en']))
|
||||||
|
self.assertEqual(md5(subtitles['en']), 'acaca989e24a9e45a6719c9b3d60815c')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@@ -21,6 +21,7 @@ from youtube_dl.utils import (
|
|||||||
clean_html,
|
clean_html,
|
||||||
DateRange,
|
DateRange,
|
||||||
detect_exe_version,
|
detect_exe_version,
|
||||||
|
determine_ext,
|
||||||
encodeFilename,
|
encodeFilename,
|
||||||
escape_rfc3986,
|
escape_rfc3986,
|
||||||
escape_url,
|
escape_url,
|
||||||
@@ -68,6 +69,9 @@ from youtube_dl.utils import (
|
|||||||
cli_valueless_option,
|
cli_valueless_option,
|
||||||
cli_bool_option,
|
cli_bool_option,
|
||||||
)
|
)
|
||||||
|
from youtube_dl.compat import (
|
||||||
|
compat_etree_fromstring,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestUtil(unittest.TestCase):
|
class TestUtil(unittest.TestCase):
|
||||||
@@ -207,8 +211,8 @@ class TestUtil(unittest.TestCase):
|
|||||||
self.assertEqual(unescapeHTML('%20;'), '%20;')
|
self.assertEqual(unescapeHTML('%20;'), '%20;')
|
||||||
self.assertEqual(unescapeHTML('/'), '/')
|
self.assertEqual(unescapeHTML('/'), '/')
|
||||||
self.assertEqual(unescapeHTML('/'), '/')
|
self.assertEqual(unescapeHTML('/'), '/')
|
||||||
self.assertEqual(
|
self.assertEqual(unescapeHTML('é'), 'é')
|
||||||
unescapeHTML('é'), 'é')
|
self.assertEqual(unescapeHTML('�'), '�')
|
||||||
|
|
||||||
def test_daterange(self):
|
def test_daterange(self):
|
||||||
_20century = DateRange("19000101", "20000101")
|
_20century = DateRange("19000101", "20000101")
|
||||||
@@ -233,6 +237,14 @@ class TestUtil(unittest.TestCase):
|
|||||||
unified_strdate('2/2/2015 6:47:40 PM', day_first=False),
|
unified_strdate('2/2/2015 6:47:40 PM', day_first=False),
|
||||||
'20150202')
|
'20150202')
|
||||||
self.assertEqual(unified_strdate('25-09-2014'), '20140925')
|
self.assertEqual(unified_strdate('25-09-2014'), '20140925')
|
||||||
|
self.assertEqual(unified_strdate('UNKNOWN DATE FORMAT'), None)
|
||||||
|
|
||||||
|
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/?download', None), None)
|
||||||
|
self.assertEqual(determine_ext('http://example.com/foo/bar.nonext/?download', None), None)
|
||||||
|
self.assertEqual(determine_ext('http://example.com/foo/bar/mp4?download', None), None)
|
||||||
|
self.assertEqual(determine_ext('http://example.com/foo/bar.m3u8//?download'), 'm3u8')
|
||||||
|
|
||||||
def test_find_xpath_attr(self):
|
def test_find_xpath_attr(self):
|
||||||
testxml = '''<root>
|
testxml = '''<root>
|
||||||
@@ -242,7 +254,7 @@ class TestUtil(unittest.TestCase):
|
|||||||
<node x="b" y="d" />
|
<node x="b" y="d" />
|
||||||
<node x="" />
|
<node x="" />
|
||||||
</root>'''
|
</root>'''
|
||||||
doc = xml.etree.ElementTree.fromstring(testxml)
|
doc = compat_etree_fromstring(testxml)
|
||||||
|
|
||||||
self.assertEqual(find_xpath_attr(doc, './/fourohfour', 'n'), None)
|
self.assertEqual(find_xpath_attr(doc, './/fourohfour', 'n'), None)
|
||||||
self.assertEqual(find_xpath_attr(doc, './/fourohfour', 'n', 'v'), None)
|
self.assertEqual(find_xpath_attr(doc, './/fourohfour', 'n', 'v'), None)
|
||||||
@@ -263,7 +275,7 @@ class TestUtil(unittest.TestCase):
|
|||||||
<url>http://server.com/download.mp3</url>
|
<url>http://server.com/download.mp3</url>
|
||||||
</media:song>
|
</media:song>
|
||||||
</root>'''
|
</root>'''
|
||||||
doc = xml.etree.ElementTree.fromstring(testxml)
|
doc = compat_etree_fromstring(testxml)
|
||||||
find = lambda p: doc.find(xpath_with_ns(p, {'media': 'http://example.com/'}))
|
find = lambda p: doc.find(xpath_with_ns(p, {'media': 'http://example.com/'}))
|
||||||
self.assertTrue(find('media:song') is not None)
|
self.assertTrue(find('media:song') is not None)
|
||||||
self.assertEqual(find('media:song/media:author').text, 'The Author')
|
self.assertEqual(find('media:song/media:author').text, 'The Author')
|
||||||
@@ -275,9 +287,16 @@ class TestUtil(unittest.TestCase):
|
|||||||
p = xml.etree.ElementTree.SubElement(div, 'p')
|
p = xml.etree.ElementTree.SubElement(div, 'p')
|
||||||
p.text = 'Foo'
|
p.text = 'Foo'
|
||||||
self.assertEqual(xpath_element(doc, 'div/p'), p)
|
self.assertEqual(xpath_element(doc, 'div/p'), p)
|
||||||
|
self.assertEqual(xpath_element(doc, ['div/p']), p)
|
||||||
|
self.assertEqual(xpath_element(doc, ['div/bar', 'div/p']), p)
|
||||||
self.assertEqual(xpath_element(doc, 'div/bar', default='default'), 'default')
|
self.assertEqual(xpath_element(doc, 'div/bar', default='default'), 'default')
|
||||||
|
self.assertEqual(xpath_element(doc, ['div/bar'], default='default'), 'default')
|
||||||
self.assertTrue(xpath_element(doc, 'div/bar') is None)
|
self.assertTrue(xpath_element(doc, 'div/bar') is None)
|
||||||
|
self.assertTrue(xpath_element(doc, ['div/bar']) is None)
|
||||||
|
self.assertTrue(xpath_element(doc, ['div/bar'], 'div/baz') is None)
|
||||||
self.assertRaises(ExtractorError, xpath_element, doc, 'div/bar', fatal=True)
|
self.assertRaises(ExtractorError, xpath_element, doc, 'div/bar', fatal=True)
|
||||||
|
self.assertRaises(ExtractorError, xpath_element, doc, ['div/bar'], fatal=True)
|
||||||
|
self.assertRaises(ExtractorError, xpath_element, doc, ['div/bar', 'div/baz'], fatal=True)
|
||||||
|
|
||||||
def test_xpath_text(self):
|
def test_xpath_text(self):
|
||||||
testxml = '''<root>
|
testxml = '''<root>
|
||||||
@@ -285,7 +304,7 @@ class TestUtil(unittest.TestCase):
|
|||||||
<p>Foo</p>
|
<p>Foo</p>
|
||||||
</div>
|
</div>
|
||||||
</root>'''
|
</root>'''
|
||||||
doc = xml.etree.ElementTree.fromstring(testxml)
|
doc = compat_etree_fromstring(testxml)
|
||||||
self.assertEqual(xpath_text(doc, 'div/p'), 'Foo')
|
self.assertEqual(xpath_text(doc, 'div/p'), 'Foo')
|
||||||
self.assertEqual(xpath_text(doc, 'div/bar', default='default'), 'default')
|
self.assertEqual(xpath_text(doc, 'div/bar', default='default'), 'default')
|
||||||
self.assertTrue(xpath_text(doc, 'div/bar') is None)
|
self.assertTrue(xpath_text(doc, 'div/bar') is None)
|
||||||
@@ -297,7 +316,7 @@ class TestUtil(unittest.TestCase):
|
|||||||
<p x="a">Foo</p>
|
<p x="a">Foo</p>
|
||||||
</div>
|
</div>
|
||||||
</root>'''
|
</root>'''
|
||||||
doc = xml.etree.ElementTree.fromstring(testxml)
|
doc = compat_etree_fromstring(testxml)
|
||||||
self.assertEqual(xpath_attr(doc, 'div/p', 'x'), 'a')
|
self.assertEqual(xpath_attr(doc, 'div/p', 'x'), 'a')
|
||||||
self.assertEqual(xpath_attr(doc, 'div/bar', 'x'), None)
|
self.assertEqual(xpath_attr(doc, 'div/bar', 'x'), None)
|
||||||
self.assertEqual(xpath_attr(doc, 'div/p', 'y'), None)
|
self.assertEqual(xpath_attr(doc, 'div/p', 'y'), None)
|
||||||
@@ -425,6 +444,8 @@ class TestUtil(unittest.TestCase):
|
|||||||
self.assertEqual(parse_iso8601('2014-03-23T22:04:26+0000'), 1395612266)
|
self.assertEqual(parse_iso8601('2014-03-23T22:04:26+0000'), 1395612266)
|
||||||
self.assertEqual(parse_iso8601('2014-03-23T22:04:26Z'), 1395612266)
|
self.assertEqual(parse_iso8601('2014-03-23T22:04:26Z'), 1395612266)
|
||||||
self.assertEqual(parse_iso8601('2014-03-23T22:04:26.1234Z'), 1395612266)
|
self.assertEqual(parse_iso8601('2014-03-23T22:04:26.1234Z'), 1395612266)
|
||||||
|
self.assertEqual(parse_iso8601('2015-09-29T08:27:31.727'), 1443515251)
|
||||||
|
self.assertEqual(parse_iso8601('2015-09-29T08-27-31.727'), None)
|
||||||
|
|
||||||
def test_strip_jsonp(self):
|
def test_strip_jsonp(self):
|
||||||
stripped = strip_jsonp('cb ([ {"id":"532cb",\n\n\n"x":\n3}\n]\n);')
|
stripped = strip_jsonp('cb ([ {"id":"532cb",\n\n\n"x":\n3}\n]\n);')
|
||||||
|
@@ -28,6 +28,7 @@ if os.name == 'nt':
|
|||||||
import ctypes
|
import ctypes
|
||||||
|
|
||||||
from .compat import (
|
from .compat import (
|
||||||
|
compat_basestring,
|
||||||
compat_cookiejar,
|
compat_cookiejar,
|
||||||
compat_expanduser,
|
compat_expanduser,
|
||||||
compat_get_terminal_size,
|
compat_get_terminal_size,
|
||||||
@@ -63,6 +64,7 @@ from .utils import (
|
|||||||
SameFileError,
|
SameFileError,
|
||||||
sanitize_filename,
|
sanitize_filename,
|
||||||
sanitize_path,
|
sanitize_path,
|
||||||
|
sanitized_Request,
|
||||||
std_headers,
|
std_headers,
|
||||||
subtitles_filename,
|
subtitles_filename,
|
||||||
UnavailableVideoError,
|
UnavailableVideoError,
|
||||||
@@ -156,7 +158,7 @@ class YoutubeDL(object):
|
|||||||
writethumbnail: Write the thumbnail image to a file
|
writethumbnail: Write the thumbnail image to a file
|
||||||
write_all_thumbnails: Write all thumbnail formats to files
|
write_all_thumbnails: Write all thumbnail formats to files
|
||||||
writesubtitles: Write the video subtitles to a file
|
writesubtitles: Write the video subtitles to a file
|
||||||
writeautomaticsub: Write the automatic subtitles to a file
|
writeautomaticsub: Write the automatically generated subtitles to a file
|
||||||
allsubtitles: Downloads all the subtitles of the video
|
allsubtitles: Downloads all the subtitles of the video
|
||||||
(requires writesubtitles or writeautomaticsub)
|
(requires writesubtitles or writeautomaticsub)
|
||||||
listsubtitles: Lists all available subtitles for the video
|
listsubtitles: Lists all available subtitles for the video
|
||||||
@@ -572,7 +574,7 @@ class YoutubeDL(object):
|
|||||||
if v is not None)
|
if v is not None)
|
||||||
template_dict = collections.defaultdict(lambda: 'NA', template_dict)
|
template_dict = collections.defaultdict(lambda: 'NA', template_dict)
|
||||||
|
|
||||||
outtmpl = sanitize_path(self.params.get('outtmpl', DEFAULT_OUTTMPL))
|
outtmpl = self.params.get('outtmpl', DEFAULT_OUTTMPL)
|
||||||
tmpl = compat_expanduser(outtmpl)
|
tmpl = compat_expanduser(outtmpl)
|
||||||
filename = tmpl % template_dict
|
filename = tmpl % template_dict
|
||||||
# Temporary fix for #4787
|
# Temporary fix for #4787
|
||||||
@@ -580,7 +582,7 @@ class YoutubeDL(object):
|
|||||||
# to workaround encoding issues with subprocess on python2 @ Windows
|
# to workaround encoding issues with subprocess on python2 @ Windows
|
||||||
if sys.version_info < (3, 0) and sys.platform == 'win32':
|
if sys.version_info < (3, 0) and sys.platform == 'win32':
|
||||||
filename = encodeFilename(filename, True).decode(preferredencoding())
|
filename = encodeFilename(filename, True).decode(preferredencoding())
|
||||||
return filename
|
return sanitize_path(filename)
|
||||||
except ValueError as err:
|
except ValueError as err:
|
||||||
self.report_error('Error in output template: ' + str(err) + ' (encoding: ' + repr(preferredencoding()) + ')')
|
self.report_error('Error in output template: ' + str(err) + ' (encoding: ' + repr(preferredencoding()) + ')')
|
||||||
return None
|
return None
|
||||||
@@ -833,6 +835,7 @@ class YoutubeDL(object):
|
|||||||
extra_info=extra)
|
extra_info=extra)
|
||||||
playlist_results.append(entry_result)
|
playlist_results.append(entry_result)
|
||||||
ie_result['entries'] = playlist_results
|
ie_result['entries'] = playlist_results
|
||||||
|
self.to_screen('[download] Finished downloading playlist: %s' % playlist)
|
||||||
return ie_result
|
return ie_result
|
||||||
elif result_type == 'compat_list':
|
elif result_type == 'compat_list':
|
||||||
self.report_warning(
|
self.report_warning(
|
||||||
@@ -937,7 +940,7 @@ class YoutubeDL(object):
|
|||||||
filter_parts.append(string)
|
filter_parts.append(string)
|
||||||
|
|
||||||
def _remove_unused_ops(tokens):
|
def _remove_unused_ops(tokens):
|
||||||
# Remove operators that we don't use and join them with the sourrounding strings
|
# Remove operators that we don't use and join them with the surrounding strings
|
||||||
# for example: 'mp4' '-' 'baseline' '-' '16x9' is converted to 'mp4-baseline-16x9'
|
# for example: 'mp4' '-' 'baseline' '-' '16x9' is converted to 'mp4-baseline-16x9'
|
||||||
ALLOWED_OPS = ('/', '+', ',', '(', ')')
|
ALLOWED_OPS = ('/', '+', ',', '(', ')')
|
||||||
last_string, last_start, last_end, last_line = None, None, None, None
|
last_string, last_start, last_end, last_line = None, None, None, None
|
||||||
@@ -1107,6 +1110,12 @@ class YoutubeDL(object):
|
|||||||
'contain the video, try using '
|
'contain the video, try using '
|
||||||
'"-f %s+%s"' % (format_2, format_1))
|
'"-f %s+%s"' % (format_2, format_1))
|
||||||
return
|
return
|
||||||
|
# Formats must be opposite (video+audio)
|
||||||
|
if formats_info[0].get('acodec') == 'none' and formats_info[1].get('acodec') == 'none':
|
||||||
|
self.report_error(
|
||||||
|
'Both formats %s and %s are video-only, you must specify "-f video+audio"'
|
||||||
|
% (format_1, format_2))
|
||||||
|
return
|
||||||
output_ext = (
|
output_ext = (
|
||||||
formats_info[0]['ext']
|
formats_info[0]['ext']
|
||||||
if self.params.get('merge_output_format') is None
|
if self.params.get('merge_output_format') is None
|
||||||
@@ -1186,7 +1195,7 @@ class YoutubeDL(object):
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
def _calc_cookies(self, info_dict):
|
def _calc_cookies(self, info_dict):
|
||||||
pr = compat_urllib_request.Request(info_dict['url'])
|
pr = sanitized_Request(info_dict['url'])
|
||||||
self.cookiejar.add_cookie_header(pr)
|
self.cookiejar.add_cookie_header(pr)
|
||||||
return pr.get_header('Cookie')
|
return pr.get_header('Cookie')
|
||||||
|
|
||||||
@@ -1870,6 +1879,8 @@ class YoutubeDL(object):
|
|||||||
|
|
||||||
def urlopen(self, req):
|
def urlopen(self, req):
|
||||||
""" Start an HTTP download """
|
""" Start an HTTP download """
|
||||||
|
if isinstance(req, compat_basestring):
|
||||||
|
req = sanitized_Request(req)
|
||||||
return self._opener.open(req, timeout=self._socket_timeout)
|
return self._opener.open(req, timeout=self._socket_timeout)
|
||||||
|
|
||||||
def print_debug_header(self):
|
def print_debug_header(self):
|
||||||
|
@@ -377,7 +377,7 @@ def _real_main(argv=None):
|
|||||||
with YoutubeDL(ydl_opts) as ydl:
|
with YoutubeDL(ydl_opts) as ydl:
|
||||||
# Update version
|
# Update version
|
||||||
if opts.update_self:
|
if opts.update_self:
|
||||||
update_self(ydl.to_screen, opts.verbose)
|
update_self(ydl.to_screen, opts.verbose, ydl._opener)
|
||||||
|
|
||||||
# Remove cache dir
|
# Remove cache dir
|
||||||
if opts.rm_cachedir:
|
if opts.rm_cachedir:
|
||||||
|
@@ -14,6 +14,7 @@ import socket
|
|||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import itertools
|
import itertools
|
||||||
|
import xml.etree.ElementTree
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -212,6 +213,43 @@ try:
|
|||||||
except ImportError: # Python 2.6
|
except ImportError: # Python 2.6
|
||||||
from xml.parsers.expat import ExpatError as compat_xml_parse_error
|
from xml.parsers.expat import ExpatError as compat_xml_parse_error
|
||||||
|
|
||||||
|
if sys.version_info[0] >= 3:
|
||||||
|
compat_etree_fromstring = xml.etree.ElementTree.fromstring
|
||||||
|
else:
|
||||||
|
# python 2.x tries to encode unicode strings with ascii (see the
|
||||||
|
# XMLParser._fixtext method)
|
||||||
|
etree = xml.etree.ElementTree
|
||||||
|
|
||||||
|
try:
|
||||||
|
_etree_iter = etree.Element.iter
|
||||||
|
except AttributeError: # Python <=2.6
|
||||||
|
def _etree_iter(root):
|
||||||
|
for el in root.findall('*'):
|
||||||
|
yield el
|
||||||
|
for sub in _etree_iter(el):
|
||||||
|
yield sub
|
||||||
|
|
||||||
|
# on 2.6 XML doesn't have a parser argument, function copied from CPython
|
||||||
|
# 2.7 source
|
||||||
|
def _XML(text, parser=None):
|
||||||
|
if not parser:
|
||||||
|
parser = etree.XMLParser(target=etree.TreeBuilder())
|
||||||
|
parser.feed(text)
|
||||||
|
return parser.close()
|
||||||
|
|
||||||
|
def _element_factory(*args, **kwargs):
|
||||||
|
el = etree.Element(*args, **kwargs)
|
||||||
|
for k, v in el.items():
|
||||||
|
if isinstance(v, bytes):
|
||||||
|
el.set(k, v.decode('utf-8'))
|
||||||
|
return el
|
||||||
|
|
||||||
|
def compat_etree_fromstring(text):
|
||||||
|
doc = _XML(text, parser=etree.XMLParser(target=etree.TreeBuilder(element_factory=_element_factory)))
|
||||||
|
for el in _etree_iter(doc):
|
||||||
|
if el.text is not None and isinstance(el.text, bytes):
|
||||||
|
el.text = el.text.decode('utf-8')
|
||||||
|
return doc
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from urllib.parse import parse_qs as compat_parse_qs
|
from urllib.parse import parse_qs as compat_parse_qs
|
||||||
@@ -507,6 +545,7 @@ __all__ = [
|
|||||||
'compat_chr',
|
'compat_chr',
|
||||||
'compat_cookiejar',
|
'compat_cookiejar',
|
||||||
'compat_cookies',
|
'compat_cookies',
|
||||||
|
'compat_etree_fromstring',
|
||||||
'compat_expanduser',
|
'compat_expanduser',
|
||||||
'compat_get_terminal_size',
|
'compat_get_terminal_size',
|
||||||
'compat_getenv',
|
'compat_getenv',
|
||||||
|
@@ -42,7 +42,7 @@ class FileDownloader(object):
|
|||||||
min_filesize: Skip files smaller than this size
|
min_filesize: Skip files smaller than this size
|
||||||
max_filesize: Skip files larger than this size
|
max_filesize: Skip files larger than this size
|
||||||
xattr_set_filesize: Set ytdl.filesize user xattribute with expected size.
|
xattr_set_filesize: Set ytdl.filesize user xattribute with expected size.
|
||||||
(experimenatal)
|
(experimental)
|
||||||
external_downloader_args: A list of additional command-line arguments for the
|
external_downloader_args: A list of additional command-line arguments for the
|
||||||
external downloader.
|
external downloader.
|
||||||
|
|
||||||
|
@@ -3,7 +3,7 @@ from __future__ import unicode_literals
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import FileDownloader
|
from .common import FileDownloader
|
||||||
from ..compat import compat_urllib_request
|
from ..utils import sanitized_Request
|
||||||
|
|
||||||
|
|
||||||
class DashSegmentsFD(FileDownloader):
|
class DashSegmentsFD(FileDownloader):
|
||||||
@@ -22,7 +22,7 @@ class DashSegmentsFD(FileDownloader):
|
|||||||
|
|
||||||
def append_url_to_file(outf, target_url, target_name, remaining_bytes=None):
|
def append_url_to_file(outf, target_url, target_name, remaining_bytes=None):
|
||||||
self.to_screen('[DashSegments] %s: Downloading %s' % (info_dict['id'], target_name))
|
self.to_screen('[DashSegments] %s: Downloading %s' % (info_dict['id'], target_name))
|
||||||
req = compat_urllib_request.Request(target_url)
|
req = sanitized_Request(target_url)
|
||||||
if remaining_bytes is not None:
|
if remaining_bytes is not None:
|
||||||
req.add_header('Range', 'bytes=0-%d' % (remaining_bytes - 1))
|
req.add_header('Range', 'bytes=0-%d' % (remaining_bytes - 1))
|
||||||
|
|
||||||
|
@@ -5,15 +5,17 @@ import io
|
|||||||
import itertools
|
import itertools
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import xml.etree.ElementTree as etree
|
|
||||||
|
|
||||||
from .fragment import FragmentFD
|
from .fragment import FragmentFD
|
||||||
from ..compat import (
|
from ..compat import (
|
||||||
|
compat_etree_fromstring,
|
||||||
compat_urlparse,
|
compat_urlparse,
|
||||||
compat_urllib_error,
|
compat_urllib_error,
|
||||||
|
compat_urllib_parse_urlparse,
|
||||||
)
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
encodeFilename,
|
encodeFilename,
|
||||||
|
fix_xml_ampersands,
|
||||||
sanitize_open,
|
sanitize_open,
|
||||||
struct_pack,
|
struct_pack,
|
||||||
struct_unpack,
|
struct_unpack,
|
||||||
@@ -285,9 +287,14 @@ class F4mFD(FragmentFD):
|
|||||||
man_url = info_dict['url']
|
man_url = info_dict['url']
|
||||||
requested_bitrate = info_dict.get('tbr')
|
requested_bitrate = info_dict.get('tbr')
|
||||||
self.to_screen('[%s] Downloading f4m manifest' % self.FD_NAME)
|
self.to_screen('[%s] Downloading f4m manifest' % self.FD_NAME)
|
||||||
manifest = self.ydl.urlopen(man_url).read()
|
urlh = self.ydl.urlopen(man_url)
|
||||||
|
man_url = urlh.geturl()
|
||||||
|
# Some manifests may be malformed, e.g. prosiebensat1 generated manifests
|
||||||
|
# (see https://github.com/rg3/youtube-dl/issues/6215#issuecomment-121704244
|
||||||
|
# and https://github.com/rg3/youtube-dl/issues/7823)
|
||||||
|
manifest = fix_xml_ampersands(urlh.read().decode('utf-8', 'ignore')).strip()
|
||||||
|
|
||||||
doc = etree.fromstring(manifest)
|
doc = compat_etree_fromstring(manifest)
|
||||||
formats = [(int(f.attrib.get('bitrate', -1)), f)
|
formats = [(int(f.attrib.get('bitrate', -1)), f)
|
||||||
for f in self._get_unencrypted_media(doc)]
|
for f in self._get_unencrypted_media(doc)]
|
||||||
if requested_bitrate is None:
|
if requested_bitrate is None:
|
||||||
@@ -329,20 +336,25 @@ class F4mFD(FragmentFD):
|
|||||||
if not live:
|
if not live:
|
||||||
write_metadata_tag(dest_stream, metadata)
|
write_metadata_tag(dest_stream, metadata)
|
||||||
|
|
||||||
|
base_url_parsed = compat_urllib_parse_urlparse(base_url)
|
||||||
|
|
||||||
self._start_frag_download(ctx)
|
self._start_frag_download(ctx)
|
||||||
|
|
||||||
frags_filenames = []
|
frags_filenames = []
|
||||||
while fragments_list:
|
while fragments_list:
|
||||||
seg_i, frag_i = fragments_list.pop(0)
|
seg_i, frag_i = fragments_list.pop(0)
|
||||||
name = 'Seg%d-Frag%d' % (seg_i, frag_i)
|
name = 'Seg%d-Frag%d' % (seg_i, frag_i)
|
||||||
url = base_url + name
|
query = []
|
||||||
|
if base_url_parsed.query:
|
||||||
|
query.append(base_url_parsed.query)
|
||||||
if akamai_pv:
|
if akamai_pv:
|
||||||
url += '?' + akamai_pv.strip(';')
|
query.append(akamai_pv.strip(';'))
|
||||||
if info_dict.get('extra_param_to_segment_url'):
|
if info_dict.get('extra_param_to_segment_url'):
|
||||||
url += info_dict.get('extra_param_to_segment_url')
|
query.append(info_dict['extra_param_to_segment_url'])
|
||||||
|
url_parsed = base_url_parsed._replace(path=base_url_parsed.path + name, query='&'.join(query))
|
||||||
frag_filename = '%s-%s' % (ctx['tmpfilename'], name)
|
frag_filename = '%s-%s' % (ctx['tmpfilename'], name)
|
||||||
try:
|
try:
|
||||||
success = ctx['dl'].download(frag_filename, {'url': url})
|
success = ctx['dl'].download(frag_filename, {'url': url_parsed.geturl()})
|
||||||
if not success:
|
if not success:
|
||||||
return False
|
return False
|
||||||
(down, frag_sanitized) = sanitize_open(frag_filename, 'rb')
|
(down, frag_sanitized) = sanitize_open(frag_filename, 'rb')
|
||||||
|
@@ -13,6 +13,7 @@ from ..utils import (
|
|||||||
encodeArgument,
|
encodeArgument,
|
||||||
encodeFilename,
|
encodeFilename,
|
||||||
sanitize_open,
|
sanitize_open,
|
||||||
|
handle_youtubedl_headers,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -33,9 +34,10 @@ class HlsFD(FileDownloader):
|
|||||||
if info_dict['http_headers'] and re.match(r'^https?://', url):
|
if info_dict['http_headers'] and re.match(r'^https?://', url):
|
||||||
# Trailing \r\n after each HTTP header is important to prevent warning from ffmpeg/avconv:
|
# Trailing \r\n after each HTTP header is important to prevent warning from ffmpeg/avconv:
|
||||||
# [http @ 00000000003d2fa0] No trailing CRLF found in HTTP header.
|
# [http @ 00000000003d2fa0] No trailing CRLF found in HTTP header.
|
||||||
|
headers = handle_youtubedl_headers(info_dict['http_headers'])
|
||||||
args += [
|
args += [
|
||||||
'-headers',
|
'-headers',
|
||||||
''.join('%s: %s\r\n' % (key, val) for key, val in info_dict['http_headers'].items())]
|
''.join('%s: %s\r\n' % (key, val) for key, val in headers.items())]
|
||||||
|
|
||||||
args += ['-i', url, '-f', 'mp4', '-c', 'copy', '-bsf:a', 'aac_adtstoasc']
|
args += ['-i', url, '-f', 'mp4', '-c', 'copy', '-bsf:a', 'aac_adtstoasc']
|
||||||
|
|
||||||
|
@@ -7,14 +7,12 @@ import time
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import FileDownloader
|
from .common import FileDownloader
|
||||||
from ..compat import (
|
from ..compat import compat_urllib_error
|
||||||
compat_urllib_request,
|
|
||||||
compat_urllib_error,
|
|
||||||
)
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ContentTooShortError,
|
ContentTooShortError,
|
||||||
encodeFilename,
|
encodeFilename,
|
||||||
sanitize_open,
|
sanitize_open,
|
||||||
|
sanitized_Request,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -29,8 +27,8 @@ class HttpFD(FileDownloader):
|
|||||||
add_headers = info_dict.get('http_headers')
|
add_headers = info_dict.get('http_headers')
|
||||||
if add_headers:
|
if add_headers:
|
||||||
headers.update(add_headers)
|
headers.update(add_headers)
|
||||||
basic_request = compat_urllib_request.Request(url, None, headers)
|
basic_request = sanitized_Request(url, None, headers)
|
||||||
request = compat_urllib_request.Request(url, None, headers)
|
request = sanitized_Request(url, None, headers)
|
||||||
|
|
||||||
is_test = self.params.get('test', False)
|
is_test = self.params.get('test', False)
|
||||||
|
|
||||||
|
@@ -117,7 +117,7 @@ class RtmpFD(FileDownloader):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# Download using rtmpdump. rtmpdump returns exit code 2 when
|
# Download using rtmpdump. rtmpdump returns exit code 2 when
|
||||||
# the connection was interrumpted and resuming appears to be
|
# the connection was interrupted and resuming appears to be
|
||||||
# possible. This is part of rtmpdump's normal usage, AFAIK.
|
# possible. This is part of rtmpdump's normal usage, AFAIK.
|
||||||
basic_args = [
|
basic_args = [
|
||||||
'rtmpdump', '--verbose', '-r', url,
|
'rtmpdump', '--verbose', '-r', url,
|
||||||
|
@@ -3,9 +3,15 @@ from __future__ import unicode_literals
|
|||||||
from .abc import ABCIE
|
from .abc import ABCIE
|
||||||
from .abc7news import Abc7NewsIE
|
from .abc7news import Abc7NewsIE
|
||||||
from .academicearth import AcademicEarthCourseIE
|
from .academicearth import AcademicEarthCourseIE
|
||||||
|
from .acast import (
|
||||||
|
ACastIE,
|
||||||
|
ACastChannelIE,
|
||||||
|
)
|
||||||
from .addanime import AddAnimeIE
|
from .addanime import AddAnimeIE
|
||||||
from .adobetv import (
|
from .adobetv import (
|
||||||
AdobeTVIE,
|
AdobeTVIE,
|
||||||
|
AdobeTVShowIE,
|
||||||
|
AdobeTVChannelIE,
|
||||||
AdobeTVVideoIE,
|
AdobeTVVideoIE,
|
||||||
)
|
)
|
||||||
from .adultswim import AdultSwimIE
|
from .adultswim import AdultSwimIE
|
||||||
@@ -38,6 +44,7 @@ from .arte import (
|
|||||||
)
|
)
|
||||||
from .atresplayer import AtresPlayerIE
|
from .atresplayer import AtresPlayerIE
|
||||||
from .atttechchannel import ATTTechChannelIE
|
from .atttechchannel import ATTTechChannelIE
|
||||||
|
from .audimedia import AudiMediaIE
|
||||||
from .audiomack import AudiomackIE, AudiomackAlbumIE
|
from .audiomack import AudiomackIE, AudiomackAlbumIE
|
||||||
from .azubu import AzubuIE
|
from .azubu import AzubuIE
|
||||||
from .baidu import BaiduVideoIE
|
from .baidu import BaiduVideoIE
|
||||||
@@ -60,7 +67,10 @@ from .bloomberg import BloombergIE
|
|||||||
from .bpb import BpbIE
|
from .bpb import BpbIE
|
||||||
from .br import BRIE
|
from .br import BRIE
|
||||||
from .breakcom import BreakIE
|
from .breakcom import BreakIE
|
||||||
from .brightcove import BrightcoveIE
|
from .brightcove import (
|
||||||
|
BrightcoveLegacyIE,
|
||||||
|
BrightcoveNewIE,
|
||||||
|
)
|
||||||
from .buzzfeed import BuzzFeedIE
|
from .buzzfeed import BuzzFeedIE
|
||||||
from .byutv import BYUtvIE
|
from .byutv import BYUtvIE
|
||||||
from .c56 import C56IE
|
from .c56 import C56IE
|
||||||
@@ -90,6 +100,7 @@ from .cliphunter import CliphunterIE
|
|||||||
from .clipsyndicate import ClipsyndicateIE
|
from .clipsyndicate import ClipsyndicateIE
|
||||||
from .cloudy import CloudyIE
|
from .cloudy import CloudyIE
|
||||||
from .clubic import ClubicIE
|
from .clubic import ClubicIE
|
||||||
|
from .clyp import ClypIE
|
||||||
from .cmt import CMTIE
|
from .cmt import CMTIE
|
||||||
from .cnet import CNETIE
|
from .cnet import CNETIE
|
||||||
from .cnn import (
|
from .cnn import (
|
||||||
@@ -123,10 +134,12 @@ from .dbtv import DBTVIE
|
|||||||
from .dcn import DCNIE
|
from .dcn import DCNIE
|
||||||
from .dctp import DctpTvIE
|
from .dctp import DctpTvIE
|
||||||
from .deezer import DeezerPlaylistIE
|
from .deezer import DeezerPlaylistIE
|
||||||
|
from .democracynow import DemocracynowIE
|
||||||
from .dfb import DFBIE
|
from .dfb import DFBIE
|
||||||
from .dhm import DHMIE
|
from .dhm import DHMIE
|
||||||
from .dotsub import DotsubIE
|
from .dotsub import DotsubIE
|
||||||
from .douyutv import DouyuTVIE
|
from .douyutv import DouyuTVIE
|
||||||
|
from .dplay import DPlayIE
|
||||||
from .dramafever import (
|
from .dramafever import (
|
||||||
DramaFeverIE,
|
DramaFeverIE,
|
||||||
DramaFeverSeriesIE,
|
DramaFeverSeriesIE,
|
||||||
@@ -193,7 +206,9 @@ from .francetv import (
|
|||||||
from .freesound import FreesoundIE
|
from .freesound import FreesoundIE
|
||||||
from .freespeech import FreespeechIE
|
from .freespeech import FreespeechIE
|
||||||
from .freevideo import FreeVideoIE
|
from .freevideo import FreeVideoIE
|
||||||
|
from .funimation import FunimationIE
|
||||||
from .funnyordie import FunnyOrDieIE
|
from .funnyordie import FunnyOrDieIE
|
||||||
|
from .gameinformer import GameInformerIE
|
||||||
from .gamekings import GamekingsIE
|
from .gamekings import GamekingsIE
|
||||||
from .gameone import (
|
from .gameone import (
|
||||||
GameOneIE,
|
GameOneIE,
|
||||||
@@ -210,13 +225,15 @@ from .gfycat import GfycatIE
|
|||||||
from .giantbomb import GiantBombIE
|
from .giantbomb import GiantBombIE
|
||||||
from .giga import GigaIE
|
from .giga import GigaIE
|
||||||
from .glide import GlideIE
|
from .glide import GlideIE
|
||||||
from .globo import GloboIE
|
from .globo import (
|
||||||
|
GloboIE,
|
||||||
|
GloboArticleIE,
|
||||||
|
)
|
||||||
from .godtube import GodTubeIE
|
from .godtube import GodTubeIE
|
||||||
from .goldenmoustache import GoldenMoustacheIE
|
from .goldenmoustache import GoldenMoustacheIE
|
||||||
from .golem import GolemIE
|
from .golem import GolemIE
|
||||||
from .googleplus import GooglePlusIE
|
from .googleplus import GooglePlusIE
|
||||||
from .googlesearch import GoogleSearchIE
|
from .googlesearch import GoogleSearchIE
|
||||||
from .gorillavid import GorillaVidIE
|
|
||||||
from .goshgay import GoshgayIE
|
from .goshgay import GoshgayIE
|
||||||
from .groupon import GrouponIE
|
from .groupon import GrouponIE
|
||||||
from .hark import HarkIE
|
from .hark import HarkIE
|
||||||
@@ -341,7 +358,6 @@ from .motherless import MotherlessIE
|
|||||||
from .motorsport import MotorsportIE
|
from .motorsport import MotorsportIE
|
||||||
from .movieclips import MovieClipsIE
|
from .movieclips import MovieClipsIE
|
||||||
from .moviezine import MoviezineIE
|
from .moviezine import MoviezineIE
|
||||||
from .movshare import MovShareIE
|
|
||||||
from .mtv import (
|
from .mtv import (
|
||||||
MTVIE,
|
MTVIE,
|
||||||
MTVServicesEmbeddedIE,
|
MTVServicesEmbeddedIE,
|
||||||
@@ -407,14 +423,22 @@ from .noco import NocoIE
|
|||||||
from .normalboots import NormalbootsIE
|
from .normalboots import NormalbootsIE
|
||||||
from .nosvideo import NosVideoIE
|
from .nosvideo import NosVideoIE
|
||||||
from .nova import NovaIE
|
from .nova import NovaIE
|
||||||
from .novamov import NovaMovIE
|
from .novamov import (
|
||||||
|
NovaMovIE,
|
||||||
|
WholeCloudIE,
|
||||||
|
NowVideoIE,
|
||||||
|
VideoWeedIE,
|
||||||
|
CloudTimeIE,
|
||||||
|
)
|
||||||
from .nowness import (
|
from .nowness import (
|
||||||
NownessIE,
|
NownessIE,
|
||||||
NownessPlaylistIE,
|
NownessPlaylistIE,
|
||||||
NownessSeriesIE,
|
NownessSeriesIE,
|
||||||
)
|
)
|
||||||
from .nowtv import NowTVIE
|
from .nowtv import (
|
||||||
from .nowvideo import NowVideoIE
|
NowTVIE,
|
||||||
|
NowTVListIE,
|
||||||
|
)
|
||||||
from .npo import (
|
from .npo import (
|
||||||
NPOIE,
|
NPOIE,
|
||||||
NPOLiveIE,
|
NPOLiveIE,
|
||||||
@@ -451,10 +475,7 @@ from .orf import (
|
|||||||
from .parliamentliveuk import ParliamentLiveUKIE
|
from .parliamentliveuk import ParliamentLiveUKIE
|
||||||
from .patreon import PatreonIE
|
from .patreon import PatreonIE
|
||||||
from .pbs import PBSIE
|
from .pbs import PBSIE
|
||||||
from .periscope import (
|
from .periscope import PeriscopeIE
|
||||||
PeriscopeIE,
|
|
||||||
QuickscopeIE,
|
|
||||||
)
|
|
||||||
from .philharmoniedeparis import PhilharmonieDeParisIE
|
from .philharmoniedeparis import PhilharmonieDeParisIE
|
||||||
from .phoenix import PhoenixIE
|
from .phoenix import PhoenixIE
|
||||||
from .photobucket import PhotobucketIE
|
from .photobucket import PhotobucketIE
|
||||||
@@ -546,6 +567,10 @@ from .shahid import ShahidIE
|
|||||||
from .shared import SharedIE
|
from .shared import SharedIE
|
||||||
from .sharesix import ShareSixIE
|
from .sharesix import ShareSixIE
|
||||||
from .sina import SinaIE
|
from .sina import SinaIE
|
||||||
|
from .skynewsarabia import (
|
||||||
|
SkyNewsArabiaIE,
|
||||||
|
SkyNewsArabiaArticleIE,
|
||||||
|
)
|
||||||
from .slideshare import SlideshareIE
|
from .slideshare import SlideshareIE
|
||||||
from .slutload import SlutloadIE
|
from .slutload import SlutloadIE
|
||||||
from .smotri import (
|
from .smotri import (
|
||||||
@@ -568,7 +593,8 @@ from .soundcloud import (
|
|||||||
SoundcloudIE,
|
SoundcloudIE,
|
||||||
SoundcloudSetIE,
|
SoundcloudSetIE,
|
||||||
SoundcloudUserIE,
|
SoundcloudUserIE,
|
||||||
SoundcloudPlaylistIE
|
SoundcloudPlaylistIE,
|
||||||
|
SoundcloudSearchIE
|
||||||
)
|
)
|
||||||
from .soundgasm import (
|
from .soundgasm import (
|
||||||
SoundgasmIE,
|
SoundgasmIE,
|
||||||
@@ -719,16 +745,15 @@ from .vh1 import VH1IE
|
|||||||
from .vice import ViceIE
|
from .vice import ViceIE
|
||||||
from .viddler import ViddlerIE
|
from .viddler import ViddlerIE
|
||||||
from .videodetective import VideoDetectiveIE
|
from .videodetective import VideoDetectiveIE
|
||||||
from .videolecturesnet import VideoLecturesNetIE
|
|
||||||
from .videofyme import VideofyMeIE
|
from .videofyme import VideofyMeIE
|
||||||
from .videomega import VideoMegaIE
|
from .videomega import VideoMegaIE
|
||||||
from .videopremium import VideoPremiumIE
|
from .videopremium import VideoPremiumIE
|
||||||
from .videott import VideoTtIE
|
from .videott import VideoTtIE
|
||||||
from .videoweed import VideoWeedIE
|
|
||||||
from .vidme import VidmeIE
|
from .vidme import VidmeIE
|
||||||
from .vidzi import VidziIE
|
from .vidzi import VidziIE
|
||||||
from .vier import VierIE, VierVideosIE
|
from .vier import VierIE, VierVideosIE
|
||||||
from .viewster import ViewsterIE
|
from .viewster import ViewsterIE
|
||||||
|
from .viidea import ViideaIE
|
||||||
from .vimeo import (
|
from .vimeo import (
|
||||||
VimeoIE,
|
VimeoIE,
|
||||||
VimeoAlbumIE,
|
VimeoAlbumIE,
|
||||||
@@ -781,6 +806,7 @@ from .wrzuta import WrzutaIE
|
|||||||
from .wsj import WSJIE
|
from .wsj import WSJIE
|
||||||
from .xbef import XBefIE
|
from .xbef import XBefIE
|
||||||
from .xboxclips import XboxClipsIE
|
from .xboxclips import XboxClipsIE
|
||||||
|
from .xfileshare import XFileShareIE
|
||||||
from .xhamster import (
|
from .xhamster import (
|
||||||
XHamsterIE,
|
XHamsterIE,
|
||||||
XHamsterEmbedIE,
|
XHamsterEmbedIE,
|
||||||
@@ -824,6 +850,7 @@ from .youtube import (
|
|||||||
YoutubeTruncatedIDIE,
|
YoutubeTruncatedIDIE,
|
||||||
YoutubeTruncatedURLIE,
|
YoutubeTruncatedURLIE,
|
||||||
YoutubeUserIE,
|
YoutubeUserIE,
|
||||||
|
YoutubeUserPlaylistsIE,
|
||||||
YoutubeWatchLaterIE,
|
YoutubeWatchLaterIE,
|
||||||
)
|
)
|
||||||
from .zapiks import ZapiksIE
|
from .zapiks import ZapiksIE
|
||||||
|
@@ -12,7 +12,7 @@ from ..utils import (
|
|||||||
|
|
||||||
class ABCIE(InfoExtractor):
|
class ABCIE(InfoExtractor):
|
||||||
IE_NAME = 'abc.net.au'
|
IE_NAME = 'abc.net.au'
|
||||||
_VALID_URL = r'http://www\.abc\.net\.au/news/[^/]+/[^/]+/(?P<id>\d+)'
|
_VALID_URL = r'http://www\.abc\.net\.au/news/(?:[^/]+/){1,2}(?P<id>\d+)'
|
||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.abc.net.au/news/2014-11-05/australia-to-staff-ebola-treatment-centre-in-sierra-leone/5868334',
|
'url': 'http://www.abc.net.au/news/2014-11-05/australia-to-staff-ebola-treatment-centre-in-sierra-leone/5868334',
|
||||||
@@ -36,6 +36,18 @@ class ABCIE(InfoExtractor):
|
|||||||
'title': 'Marriage Equality: Warren Entsch introduces same sex marriage bill',
|
'title': 'Marriage Equality: Warren Entsch introduces same sex marriage bill',
|
||||||
},
|
},
|
||||||
'add_ie': ['Youtube'],
|
'add_ie': ['Youtube'],
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.abc.net.au/news/2015-10-23/nab-lifts-interest-rates-following-westpac-and-cba/6880080',
|
||||||
|
'md5': 'b96eee7c9edf4fc5a358a0252881cc1f',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '6880080',
|
||||||
|
'ext': 'mp3',
|
||||||
|
'title': 'NAB lifts interest rates, following Westpac and CBA',
|
||||||
|
'description': 'md5:f13d8edc81e462fce4a0437c7dc04728',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.abc.net.au/news/2015-10-19/6866214',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
@@ -43,7 +55,7 @@ class ABCIE(InfoExtractor):
|
|||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
mobj = re.search(
|
mobj = re.search(
|
||||||
r'inline(?P<type>Video|YouTube)Data\.push\((?P<json_data>[^)]+)\);',
|
r'inline(?P<type>Video|Audio|YouTube)Data\.push\((?P<json_data>[^)]+)\);',
|
||||||
webpage)
|
webpage)
|
||||||
if mobj is None:
|
if mobj is None:
|
||||||
raise ExtractorError('Unable to extract video urls')
|
raise ExtractorError('Unable to extract video urls')
|
||||||
@@ -60,11 +72,13 @@ class ABCIE(InfoExtractor):
|
|||||||
|
|
||||||
formats = [{
|
formats = [{
|
||||||
'url': url_info['url'],
|
'url': url_info['url'],
|
||||||
|
'vcodec': url_info.get('codec') if mobj.group('type') == 'Video' else 'none',
|
||||||
'width': int_or_none(url_info.get('width')),
|
'width': int_or_none(url_info.get('width')),
|
||||||
'height': int_or_none(url_info.get('height')),
|
'height': int_or_none(url_info.get('height')),
|
||||||
'tbr': int_or_none(url_info.get('bitrate')),
|
'tbr': int_or_none(url_info.get('bitrate')),
|
||||||
'filesize': int_or_none(url_info.get('filesize')),
|
'filesize': int_or_none(url_info.get('filesize')),
|
||||||
} for url_info in urls_info]
|
} for url_info in urls_info]
|
||||||
|
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
70
youtube_dl/extractor/acast.py
Normal file
70
youtube_dl/extractor/acast.py
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..compat import compat_str
|
||||||
|
from ..utils import int_or_none
|
||||||
|
|
||||||
|
|
||||||
|
class ACastBaseIE(InfoExtractor):
|
||||||
|
_API_BASE_URL = 'https://www.acast.com/api/'
|
||||||
|
|
||||||
|
|
||||||
|
class ACastIE(ACastBaseIE):
|
||||||
|
IE_NAME = 'acast'
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?acast\.com/(?P<channel>[^/]+)/(?P<id>[^/#?]+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'https://www.acast.com/condenasttraveler/-where-are-you-taipei-101-taiwan',
|
||||||
|
'md5': 'ada3de5a1e3a2a381327d749854788bb',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '57de3baa-4bb0-487e-9418-2692c1277a34',
|
||||||
|
'ext': 'mp3',
|
||||||
|
'title': '"Where Are You?": Taipei 101, Taiwan',
|
||||||
|
'timestamp': 1196172000000,
|
||||||
|
'description': 'md5:0c5d8201dfea2b93218ea986c91eee6e',
|
||||||
|
'duration': 211,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
channel, display_id = re.match(self._VALID_URL, url).groups()
|
||||||
|
cast_data = self._download_json(self._API_BASE_URL + 'channels/%s/acasts/%s/playback' % (channel, display_id), display_id)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': compat_str(cast_data['id']),
|
||||||
|
'display_id': display_id,
|
||||||
|
'url': cast_data['blings'][0]['audio'],
|
||||||
|
'title': cast_data['name'],
|
||||||
|
'description': cast_data.get('description'),
|
||||||
|
'thumbnail': cast_data.get('image'),
|
||||||
|
'timestamp': int_or_none(cast_data.get('publishingDate')),
|
||||||
|
'duration': int_or_none(cast_data.get('duration')),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ACastChannelIE(ACastBaseIE):
|
||||||
|
IE_NAME = 'acast:channel'
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?acast\.com/(?P<id>[^/#?]+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'https://www.acast.com/condenasttraveler',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '50544219-29bb-499e-a083-6087f4cb7797',
|
||||||
|
'title': 'Condé Nast Traveler Podcast',
|
||||||
|
'description': 'md5:98646dee22a5b386626ae31866638fbd',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 20,
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def suitable(cls, url):
|
||||||
|
return False if ACastIE.suitable(url) else super(ACastChannelIE, cls).suitable(url)
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
display_id = self._match_id(url)
|
||||||
|
channel_data = self._download_json(self._API_BASE_URL + 'channels/%s' % display_id, display_id)
|
||||||
|
casts = self._download_json(self._API_BASE_URL + 'channels/%s/acasts' % display_id, display_id)
|
||||||
|
entries = [self.url_result('https://www.acast.com/%s/%s' % (display_id, cast['url']), 'ACast') for cast in casts]
|
||||||
|
|
||||||
|
return self.playlist_result(entries, compat_str(channel_data['id']), channel_data['name'], channel_data.get('description'))
|
@@ -1,23 +1,32 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from ..compat import compat_str
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
parse_duration,
|
parse_duration,
|
||||||
unified_strdate,
|
unified_strdate,
|
||||||
str_to_int,
|
str_to_int,
|
||||||
|
int_or_none,
|
||||||
float_or_none,
|
float_or_none,
|
||||||
ISO639Utils,
|
ISO639Utils,
|
||||||
|
determine_ext,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class AdobeTVIE(InfoExtractor):
|
class AdobeTVBaseIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://tv\.adobe\.com/watch/[^/]+/(?P<id>[^/]+)'
|
_API_BASE_URL = 'http://tv.adobe.com/api/v4/'
|
||||||
|
|
||||||
|
|
||||||
|
class AdobeTVIE(AdobeTVBaseIE):
|
||||||
|
_VALID_URL = r'https?://tv\.adobe\.com/(?:(?P<language>fr|de|es|jp)/)?watch/(?P<show_urlname>[^/]+)/(?P<id>[^/]+)'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://tv.adobe.com/watch/the-complete-picture-with-julieanne-kost/quick-tip-how-to-draw-a-circle-around-an-object-in-photoshop/',
|
'url': 'http://tv.adobe.com/watch/the-complete-picture-with-julieanne-kost/quick-tip-how-to-draw-a-circle-around-an-object-in-photoshop/',
|
||||||
'md5': '9bc5727bcdd55251f35ad311ca74fa1e',
|
'md5': '9bc5727bcdd55251f35ad311ca74fa1e',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'quick-tip-how-to-draw-a-circle-around-an-object-in-photoshop',
|
'id': '10981',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Quick Tip - How to Draw a Circle Around an Object in Photoshop',
|
'title': 'Quick Tip - How to Draw a Circle Around an Object in Photoshop',
|
||||||
'description': 'md5:99ec318dc909d7ba2a1f2b038f7d2311',
|
'description': 'md5:99ec318dc909d7ba2a1f2b038f7d2311',
|
||||||
@@ -29,50 +38,106 @@ class AdobeTVIE(InfoExtractor):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
language, show_urlname, urlname = re.match(self._VALID_URL, url).groups()
|
||||||
webpage = self._download_webpage(url, video_id)
|
if not language:
|
||||||
|
language = 'en'
|
||||||
|
|
||||||
player = self._parse_json(
|
video_data = self._download_json(
|
||||||
self._search_regex(r'html5player:\s*({.+?})\s*\n', webpage, 'player'),
|
self._API_BASE_URL + 'episode/get/?language=%s&show_urlname=%s&urlname=%s&disclosure=standard' % (language, show_urlname, urlname),
|
||||||
video_id)
|
urlname)['data'][0]
|
||||||
|
|
||||||
title = player.get('title') or self._search_regex(
|
|
||||||
r'data-title="([^"]+)"', webpage, 'title')
|
|
||||||
description = self._og_search_description(webpage)
|
|
||||||
thumbnail = self._og_search_thumbnail(webpage)
|
|
||||||
|
|
||||||
upload_date = unified_strdate(
|
|
||||||
self._html_search_meta('datepublished', webpage, 'upload date'))
|
|
||||||
|
|
||||||
duration = parse_duration(
|
|
||||||
self._html_search_meta('duration', webpage, 'duration') or
|
|
||||||
self._search_regex(
|
|
||||||
r'Runtime:\s*(\d{2}:\d{2}:\d{2})',
|
|
||||||
webpage, 'duration', fatal=False))
|
|
||||||
|
|
||||||
view_count = str_to_int(self._search_regex(
|
|
||||||
r'<div class="views">\s*Views?:\s*([\d,.]+)\s*</div>',
|
|
||||||
webpage, 'view count'))
|
|
||||||
|
|
||||||
formats = [{
|
formats = [{
|
||||||
'url': source['src'],
|
'url': source['url'],
|
||||||
'format_id': source.get('quality') or source['src'].split('-')[-1].split('.')[0] or None,
|
'format_id': source.get('quality_level') or source['url'].split('-')[-1].split('.')[0] or None,
|
||||||
'tbr': source.get('bitrate'),
|
'width': int_or_none(source.get('width')),
|
||||||
} for source in player['sources']]
|
'height': int_or_none(source.get('height')),
|
||||||
|
'tbr': int_or_none(source.get('video_data_rate')),
|
||||||
|
} for source in video_data['videos']]
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': compat_str(video_data['id']),
|
||||||
'title': title,
|
'title': video_data['title'],
|
||||||
'description': description,
|
'description': video_data.get('description'),
|
||||||
'thumbnail': thumbnail,
|
'thumbnail': video_data.get('thumbnail'),
|
||||||
'upload_date': upload_date,
|
'upload_date': unified_strdate(video_data.get('start_date')),
|
||||||
'duration': duration,
|
'duration': parse_duration(video_data.get('duration')),
|
||||||
'view_count': view_count,
|
'view_count': str_to_int(video_data.get('playcount')),
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class AdobeTVPlaylistBaseIE(AdobeTVBaseIE):
|
||||||
|
def _parse_page_data(self, page_data):
|
||||||
|
return [self.url_result(self._get_element_url(element_data)) for element_data in page_data]
|
||||||
|
|
||||||
|
def _extract_playlist_entries(self, url, display_id):
|
||||||
|
page = self._download_json(url, display_id)
|
||||||
|
entries = self._parse_page_data(page['data'])
|
||||||
|
for page_num in range(2, page['paging']['pages'] + 1):
|
||||||
|
entries.extend(self._parse_page_data(
|
||||||
|
self._download_json(url + '&page=%d' % page_num, display_id)['data']))
|
||||||
|
return entries
|
||||||
|
|
||||||
|
|
||||||
|
class AdobeTVShowIE(AdobeTVPlaylistBaseIE):
|
||||||
|
_VALID_URL = r'https?://tv\.adobe\.com/(?:(?P<language>fr|de|es|jp)/)?show/(?P<id>[^/]+)'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://tv.adobe.com/show/the-complete-picture-with-julieanne-kost',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '36',
|
||||||
|
'title': 'The Complete Picture with Julieanne Kost',
|
||||||
|
'description': 'md5:fa50867102dcd1aa0ddf2ab039311b27',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 136,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_element_url(self, element_data):
|
||||||
|
return element_data['urls'][0]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
language, show_urlname = re.match(self._VALID_URL, url).groups()
|
||||||
|
if not language:
|
||||||
|
language = 'en'
|
||||||
|
query = 'language=%s&show_urlname=%s' % (language, show_urlname)
|
||||||
|
|
||||||
|
show_data = self._download_json(self._API_BASE_URL + 'show/get/?%s' % query, show_urlname)['data'][0]
|
||||||
|
|
||||||
|
return self.playlist_result(
|
||||||
|
self._extract_playlist_entries(self._API_BASE_URL + 'episode/?%s' % query, show_urlname),
|
||||||
|
compat_str(show_data['id']),
|
||||||
|
show_data['show_name'],
|
||||||
|
show_data['show_description'])
|
||||||
|
|
||||||
|
|
||||||
|
class AdobeTVChannelIE(AdobeTVPlaylistBaseIE):
|
||||||
|
_VALID_URL = r'https?://tv\.adobe\.com/(?:(?P<language>fr|de|es|jp)/)?channel/(?P<id>[^/]+)(?:/(?P<category_urlname>[^/]+))?'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://tv.adobe.com/channel/development',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'development',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 96,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_element_url(self, element_data):
|
||||||
|
return element_data['url']
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
language, channel_urlname, category_urlname = re.match(self._VALID_URL, url).groups()
|
||||||
|
if not language:
|
||||||
|
language = 'en'
|
||||||
|
query = 'language=%s&channel_urlname=%s' % (language, channel_urlname)
|
||||||
|
if category_urlname:
|
||||||
|
query += '&category_urlname=%s' % category_urlname
|
||||||
|
|
||||||
|
return self.playlist_result(
|
||||||
|
self._extract_playlist_entries(self._API_BASE_URL + 'show/?%s' % query, channel_urlname),
|
||||||
|
channel_urlname)
|
||||||
|
|
||||||
|
|
||||||
class AdobeTVVideoIE(InfoExtractor):
|
class AdobeTVVideoIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://video\.tv\.adobe\.com/v/(?P<id>\d+)'
|
_VALID_URL = r'https?://video\.tv\.adobe\.com/v/(?P<id>\d+)'
|
||||||
|
|
||||||
@@ -91,28 +156,25 @@ class AdobeTVVideoIE(InfoExtractor):
|
|||||||
|
|
||||||
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(url + '?format=json', video_id)
|
||||||
webpage = self._download_webpage(url, video_id)
|
|
||||||
|
|
||||||
player_params = self._parse_json(self._search_regex(
|
|
||||||
r'var\s+bridge\s*=\s*([^;]+);', webpage, 'player parameters'),
|
|
||||||
video_id)
|
|
||||||
|
|
||||||
formats = [{
|
formats = [{
|
||||||
|
'format_id': '%s-%s' % (determine_ext(source['src']), source.get('height')),
|
||||||
'url': source['src'],
|
'url': source['src'],
|
||||||
'width': source.get('width'),
|
'width': int_or_none(source.get('width')),
|
||||||
'height': source.get('height'),
|
'height': int_or_none(source.get('height')),
|
||||||
'tbr': source.get('bitrate'),
|
'tbr': int_or_none(source.get('bitrate')),
|
||||||
} for source in player_params['sources']]
|
} for source in video_data['sources']]
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
# For both metadata and downloaded files the duration varies among
|
# For both metadata and downloaded files the duration varies among
|
||||||
# formats. I just pick the max one
|
# formats. I just pick the max one
|
||||||
duration = max(filter(None, [
|
duration = max(filter(None, [
|
||||||
float_or_none(source.get('duration'), scale=1000)
|
float_or_none(source.get('duration'), scale=1000)
|
||||||
for source in player_params['sources']]))
|
for source in video_data['sources']]))
|
||||||
|
|
||||||
subtitles = {}
|
subtitles = {}
|
||||||
for translation in player_params.get('translations', []):
|
for translation in video_data.get('translations', []):
|
||||||
lang_id = translation.get('language_w3c') or ISO639Utils.long2short(translation['language_medium'])
|
lang_id = translation.get('language_w3c') or ISO639Utils.long2short(translation['language_medium'])
|
||||||
if lang_id not in subtitles:
|
if lang_id not in subtitles:
|
||||||
subtitles[lang_id] = []
|
subtitles[lang_id] = []
|
||||||
@@ -124,8 +186,9 @@ class AdobeTVVideoIE(InfoExtractor):
|
|||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
'title': player_params['title'],
|
'title': video_data['title'],
|
||||||
'description': self._og_search_description(webpage),
|
'description': video_data.get('description'),
|
||||||
|
'thumbnail': video_data['video'].get('poster'),
|
||||||
'duration': duration,
|
'duration': duration,
|
||||||
'subtitles': subtitles,
|
'subtitles': subtitles,
|
||||||
}
|
}
|
||||||
|
@@ -15,7 +15,7 @@ class AlJazeeraIE(InfoExtractor):
|
|||||||
'description': 'As a birth attendant advocating for family planning, Remy is on the frontline of Tondo\'s battle with overcrowding.',
|
'description': 'As a birth attendant advocating for family planning, Remy is on the frontline of Tondo\'s battle with overcrowding.',
|
||||||
'uploader': 'Al Jazeera English',
|
'uploader': 'Al Jazeera English',
|
||||||
},
|
},
|
||||||
'add_ie': ['Brightcove'],
|
'add_ie': ['BrightcoveLegacy'],
|
||||||
'skip': 'Not accessible from Travis CI server',
|
'skip': 'Not accessible from Travis CI server',
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,5 +32,5 @@ class AlJazeeraIE(InfoExtractor):
|
|||||||
'playerKey=AQ~~%2CAAAAmtVJIFk~%2CTVGOQ5ZTwJbeMWnq5d_H4MOM57xfzApc'
|
'playerKey=AQ~~%2CAAAAmtVJIFk~%2CTVGOQ5ZTwJbeMWnq5d_H4MOM57xfzApc'
|
||||||
'&%40videoPlayer={0}'.format(brightcove_id)
|
'&%40videoPlayer={0}'.format(brightcove_id)
|
||||||
),
|
),
|
||||||
'ie_key': 'Brightcove',
|
'ie_key': 'BrightcoveLegacy',
|
||||||
}
|
}
|
||||||
|
@@ -26,8 +26,8 @@ class AnitubeIE(InfoExtractor):
|
|||||||
video_id = mobj.group('id')
|
video_id = mobj.group('id')
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
key = self._html_search_regex(
|
key = self._search_regex(
|
||||||
r'http://www\.anitube\.se/embed/([A-Za-z0-9_-]*)', webpage, 'key')
|
r'src=["\']https?://[^/]+/embed/([A-Za-z0-9_-]+)', webpage, 'key')
|
||||||
|
|
||||||
config_xml = self._download_xml(
|
config_xml = self._download_xml(
|
||||||
'http://www.anitube.se/nuevo/econfig.php?key=%s' % key, key)
|
'http://www.anitube.se/nuevo/econfig.php?key=%s' % key, key)
|
||||||
|
@@ -14,8 +14,8 @@ from ..utils import (
|
|||||||
parse_duration,
|
parse_duration,
|
||||||
unified_strdate,
|
unified_strdate,
|
||||||
xpath_text,
|
xpath_text,
|
||||||
parse_xml,
|
|
||||||
)
|
)
|
||||||
|
from ..compat import compat_etree_fromstring
|
||||||
|
|
||||||
|
|
||||||
class ARDMediathekIE(InfoExtractor):
|
class ARDMediathekIE(InfoExtractor):
|
||||||
@@ -161,7 +161,7 @@ class ARDMediathekIE(InfoExtractor):
|
|||||||
raise ExtractorError('This program is only suitable for those aged 12 and older. Video %s is therefore only available between 20 pm and 6 am.' % video_id, expected=True)
|
raise ExtractorError('This program is only suitable for those aged 12 and older. Video %s is therefore only available between 20 pm and 6 am.' % video_id, expected=True)
|
||||||
|
|
||||||
if re.search(r'[\?&]rss($|[=&])', url):
|
if re.search(r'[\?&]rss($|[=&])', url):
|
||||||
doc = parse_xml(webpage)
|
doc = compat_etree_fromstring(webpage.encode('utf-8'))
|
||||||
if doc.tag == 'rss':
|
if doc.tag == 'rss':
|
||||||
return GenericIE()._extract_rss(url, video_id, doc)
|
return GenericIE()._extract_rss(url, video_id, doc)
|
||||||
|
|
||||||
|
@@ -7,11 +7,11 @@ from .common import InfoExtractor
|
|||||||
from ..compat import (
|
from ..compat import (
|
||||||
compat_str,
|
compat_str,
|
||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
compat_urllib_request,
|
|
||||||
)
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
int_or_none,
|
int_or_none,
|
||||||
float_or_none,
|
float_or_none,
|
||||||
|
sanitized_Request,
|
||||||
xpath_text,
|
xpath_text,
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
)
|
)
|
||||||
@@ -63,7 +63,7 @@ class AtresPlayerIE(InfoExtractor):
|
|||||||
'j_password': password,
|
'j_password': password,
|
||||||
}
|
}
|
||||||
|
|
||||||
request = compat_urllib_request.Request(
|
request = sanitized_Request(
|
||||||
self._LOGIN_URL, compat_urllib_parse.urlencode(login_form).encode('utf-8'))
|
self._LOGIN_URL, compat_urllib_parse.urlencode(login_form).encode('utf-8'))
|
||||||
request.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
request.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
||||||
response = self._download_webpage(
|
response = self._download_webpage(
|
||||||
@@ -94,7 +94,7 @@ class AtresPlayerIE(InfoExtractor):
|
|||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
for fmt in ['windows', 'android_tablet']:
|
for fmt in ['windows', 'android_tablet']:
|
||||||
request = compat_urllib_request.Request(
|
request = sanitized_Request(
|
||||||
self._URL_VIDEO_TEMPLATE.format(fmt, episode_id, timestamp_shifted, token))
|
self._URL_VIDEO_TEMPLATE.format(fmt, episode_id, timestamp_shifted, token))
|
||||||
request.add_header('User-Agent', self._USER_AGENT)
|
request.add_header('User-Agent', self._USER_AGENT)
|
||||||
|
|
||||||
|
80
youtube_dl/extractor/audimedia.py
Normal file
80
youtube_dl/extractor/audimedia.py
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
int_or_none,
|
||||||
|
parse_iso8601,
|
||||||
|
sanitized_Request,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AudiMediaIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?audimedia\.tv/(?:en|de)/vid/(?P<id>[^/?#]+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'https://audimedia.tv/en/vid/60-seconds-of-audi-sport-104-2015-wec-bahrain-rookie-test',
|
||||||
|
'md5': '79a8b71c46d49042609795ab59779b66',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '1564',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': '60 Seconds of Audi Sport 104/2015 - WEC Bahrain, Rookie Test',
|
||||||
|
'description': 'md5:60e5d30a78ced725f7b8d34370762941',
|
||||||
|
'upload_date': '20151124',
|
||||||
|
'timestamp': 1448354940,
|
||||||
|
'duration': 74022,
|
||||||
|
'view_count': int,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# extracted from https://audimedia.tv/assets/embed/embedded-player.js (dataSourceAuthToken)
|
||||||
|
_AUTH_TOKEN = 'e25b42847dba18c6c8816d5d8ce94c326e06823ebf0859ed164b3ba169be97f2'
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
display_id = self._match_id(url)
|
||||||
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
|
||||||
|
raw_payload = self._search_regex(r'<script[^>]+class="amtv-embed"[^>]+id="([^"]+)"', webpage, 'raw payload')
|
||||||
|
_, stage_mode, video_id, lang = raw_payload.split('-')
|
||||||
|
|
||||||
|
# TODO: handle s and e stage_mode (live streams and ended live streams)
|
||||||
|
if stage_mode not in ('s', 'e'):
|
||||||
|
request = sanitized_Request(
|
||||||
|
'https://audimedia.tv/api/video/v1/videos/%s?embed[]=video_versions&embed[]=thumbnail_image&where[content_language_iso]=%s' % (video_id, lang),
|
||||||
|
headers={'X-Auth-Token': self._AUTH_TOKEN})
|
||||||
|
json_data = self._download_json(request, video_id)['results']
|
||||||
|
formats = []
|
||||||
|
|
||||||
|
stream_url_hls = json_data.get('stream_url_hls')
|
||||||
|
if stream_url_hls:
|
||||||
|
m3u8_formats = self._extract_m3u8_formats(stream_url_hls, video_id, 'mp4', entry_protocol='m3u8_native', m3u8_id='hls', fatal=False)
|
||||||
|
if m3u8_formats:
|
||||||
|
formats.extend(m3u8_formats)
|
||||||
|
|
||||||
|
stream_url_hds = json_data.get('stream_url_hds')
|
||||||
|
if stream_url_hds:
|
||||||
|
f4m_formats = self._extract_f4m_formats(json_data.get('stream_url_hds') + '?hdcore=3.4.0', video_id, -1, f4m_id='hds', fatal=False)
|
||||||
|
if f4m_formats:
|
||||||
|
formats.extend(f4m_formats)
|
||||||
|
|
||||||
|
for video_version in json_data.get('video_versions'):
|
||||||
|
video_version_url = video_version.get('download_url') or video_version.get('stream_url')
|
||||||
|
if not video_version_url:
|
||||||
|
continue
|
||||||
|
formats.append({
|
||||||
|
'url': video_version_url,
|
||||||
|
'width': int_or_none(video_version.get('width')),
|
||||||
|
'height': int_or_none(video_version.get('height')),
|
||||||
|
'abr': int_or_none(video_version.get('audio_bitrate')),
|
||||||
|
'vbr': int_or_none(video_version.get('video_bitrate')),
|
||||||
|
})
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': json_data['title'],
|
||||||
|
'description': json_data.get('subtitle'),
|
||||||
|
'thumbnail': json_data.get('thumbnail_image', {}).get('file'),
|
||||||
|
'timestamp': parse_iso8601(json_data.get('publication_date')),
|
||||||
|
'duration': int_or_none(json_data.get('duration')),
|
||||||
|
'view_count': int_or_none(json_data.get('view_count')),
|
||||||
|
'formats': formats,
|
||||||
|
}
|
@@ -6,13 +6,13 @@ import itertools
|
|||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import (
|
||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
compat_urllib_request,
|
|
||||||
compat_str,
|
compat_str,
|
||||||
)
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
float_or_none,
|
float_or_none,
|
||||||
|
sanitized_Request,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ class BambuserIE(InfoExtractor):
|
|||||||
'pass': password,
|
'pass': password,
|
||||||
}
|
}
|
||||||
|
|
||||||
request = compat_urllib_request.Request(
|
request = sanitized_Request(
|
||||||
self._LOGIN_URL, compat_urllib_parse.urlencode(login_form).encode('utf-8'))
|
self._LOGIN_URL, compat_urllib_parse.urlencode(login_form).encode('utf-8'))
|
||||||
request.add_header('Referer', self._LOGIN_URL)
|
request.add_header('Referer', self._LOGIN_URL)
|
||||||
response = self._download_webpage(
|
response = self._download_webpage(
|
||||||
@@ -126,7 +126,7 @@ class BambuserChannelIE(InfoExtractor):
|
|||||||
'&sort=created&access_mode=0%2C1%2C2&limit={count}'
|
'&sort=created&access_mode=0%2C1%2C2&limit={count}'
|
||||||
'&method=broadcast&format=json&vid_older_than={last}'
|
'&method=broadcast&format=json&vid_older_than={last}'
|
||||||
).format(user=user, count=self._STEP, last=last_id)
|
).format(user=user, count=self._STEP, last=last_id)
|
||||||
req = compat_urllib_request.Request(req_url)
|
req = sanitized_Request(req_url)
|
||||||
# Without setting this header, we wouldn't get any result
|
# Without setting this header, we wouldn't get any result
|
||||||
req.add_header('Referer', 'http://bambuser.com/channel/%s' % user)
|
req.add_header('Referer', 'http://bambuser.com/channel/%s' % user)
|
||||||
data = self._download_json(
|
data = self._download_json(
|
||||||
|
@@ -2,7 +2,6 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import xml.etree.ElementTree
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
@@ -14,18 +13,22 @@ from ..utils import (
|
|||||||
remove_end,
|
remove_end,
|
||||||
unescapeHTML,
|
unescapeHTML,
|
||||||
)
|
)
|
||||||
from ..compat import compat_HTTPError
|
from ..compat import (
|
||||||
|
compat_etree_fromstring,
|
||||||
|
compat_HTTPError,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class BBCCoUkIE(InfoExtractor):
|
class BBCCoUkIE(InfoExtractor):
|
||||||
IE_NAME = 'bbc.co.uk'
|
IE_NAME = 'bbc.co.uk'
|
||||||
IE_DESC = 'BBC iPlayer'
|
IE_DESC = 'BBC iPlayer'
|
||||||
_VALID_URL = r'https?://(?:www\.)?bbc\.co\.uk/(?:(?:programmes/(?!articles/)|iplayer(?:/[^/]+)?/(?:episode/|playlist/))|music/clips[/#])(?P<id>[\da-z]{8})'
|
_ID_REGEX = r'[pb][\da-z]{7}'
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?bbc\.co\.uk/(?:(?:programmes/(?!articles/)|iplayer(?:/[^/]+)?/(?:episode/|playlist/))|music/clips[/#])(?P<id>%s)' % _ID_REGEX
|
||||||
|
|
||||||
_MEDIASELECTOR_URLS = [
|
_MEDIASELECTOR_URLS = [
|
||||||
# Provides HQ HLS streams with even better quality that pc mediaset but fails
|
# Provides HQ HLS streams with even better quality that pc mediaset but fails
|
||||||
# with geolocation in some cases when it's even not geo restricted at all (e.g.
|
# with geolocation in some cases when it's even not geo restricted at all (e.g.
|
||||||
# http://www.bbc.co.uk/programmes/b06bp7lf)
|
# http://www.bbc.co.uk/programmes/b06bp7lf). Also may fail with selectionunavailable.
|
||||||
'http://open.live.bbc.co.uk/mediaselector/5/select/version/2.0/mediaset/iptv-all/vpid/%s',
|
'http://open.live.bbc.co.uk/mediaselector/5/select/version/2.0/mediaset/iptv-all/vpid/%s',
|
||||||
'http://open.live.bbc.co.uk/mediaselector/5/select/version/2.0/mediaset/pc/vpid/%s',
|
'http://open.live.bbc.co.uk/mediaselector/5/select/version/2.0/mediaset/pc/vpid/%s',
|
||||||
]
|
]
|
||||||
@@ -44,9 +47,8 @@ class BBCCoUkIE(InfoExtractor):
|
|||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'b039d07m',
|
'id': 'b039d07m',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'title': 'Kaleidoscope, Leonard Cohen',
|
'title': 'Leonard Cohen, Kaleidoscope - BBC Radio 4',
|
||||||
'description': 'The Canadian poet and songwriter reflects on his musical career.',
|
'description': 'The Canadian poet and songwriter reflects on his musical career.',
|
||||||
'duration': 1740,
|
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
# rtmp download
|
# rtmp download
|
||||||
@@ -109,7 +111,8 @@ class BBCCoUkIE(InfoExtractor):
|
|||||||
'params': {
|
'params': {
|
||||||
# rtmp download
|
# rtmp download
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
}
|
},
|
||||||
|
'skip': 'Episode is no longer available on BBC iPlayer Radio',
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.bbc.co.uk/music/clips/p02frcc3',
|
'url': 'http://www.bbc.co.uk/music/clips/p02frcc3',
|
||||||
'note': 'Audio',
|
'note': 'Audio',
|
||||||
@@ -332,7 +335,7 @@ class BBCCoUkIE(InfoExtractor):
|
|||||||
return self._download_media_selector_url(
|
return self._download_media_selector_url(
|
||||||
mediaselector_url % programme_id, programme_id)
|
mediaselector_url % programme_id, programme_id)
|
||||||
except BBCCoUkIE.MediaSelectionError as e:
|
except BBCCoUkIE.MediaSelectionError as e:
|
||||||
if e.id in ('notukerror', 'geolocation'):
|
if e.id in ('notukerror', 'geolocation', 'selectionunavailable'):
|
||||||
last_exception = e
|
last_exception = e
|
||||||
continue
|
continue
|
||||||
self._raise_extractor_error(e)
|
self._raise_extractor_error(e)
|
||||||
@@ -343,8 +346,8 @@ class BBCCoUkIE(InfoExtractor):
|
|||||||
media_selection = self._download_xml(
|
media_selection = self._download_xml(
|
||||||
url, programme_id, 'Downloading media selection XML')
|
url, programme_id, 'Downloading media selection XML')
|
||||||
except ExtractorError as ee:
|
except ExtractorError as ee:
|
||||||
if isinstance(ee.cause, compat_HTTPError) and ee.cause.code == 403:
|
if isinstance(ee.cause, compat_HTTPError) and ee.cause.code in (403, 404):
|
||||||
media_selection = xml.etree.ElementTree.fromstring(ee.cause.read().decode('utf-8'))
|
media_selection = compat_etree_fromstring(ee.cause.read().decode('utf-8'))
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
return self._process_media_selector(media_selection, programme_id)
|
return self._process_media_selector(media_selection, programme_id)
|
||||||
@@ -451,6 +454,7 @@ class BBCCoUkIE(InfoExtractor):
|
|||||||
webpage = self._download_webpage(url, group_id, 'Downloading video page')
|
webpage = self._download_webpage(url, group_id, 'Downloading video page')
|
||||||
|
|
||||||
programme_id = None
|
programme_id = None
|
||||||
|
duration = None
|
||||||
|
|
||||||
tviplayer = self._search_regex(
|
tviplayer = self._search_regex(
|
||||||
r'mediator\.bind\(({.+?})\s*,\s*document\.getElementById',
|
r'mediator\.bind\(({.+?})\s*,\s*document\.getElementById',
|
||||||
@@ -463,14 +467,16 @@ class BBCCoUkIE(InfoExtractor):
|
|||||||
|
|
||||||
if not programme_id:
|
if not programme_id:
|
||||||
programme_id = self._search_regex(
|
programme_id = self._search_regex(
|
||||||
r'"vpid"\s*:\s*"([\da-z]{8})"', webpage, 'vpid', fatal=False, default=None)
|
r'"vpid"\s*:\s*"(%s)"' % self._ID_REGEX, webpage, 'vpid', fatal=False, default=None)
|
||||||
|
|
||||||
if programme_id:
|
if programme_id:
|
||||||
formats, subtitles = self._download_media_selector(programme_id)
|
formats, subtitles = self._download_media_selector(programme_id)
|
||||||
title = self._og_search_title(webpage)
|
title = self._og_search_title(webpage)
|
||||||
description = self._search_regex(
|
description = self._search_regex(
|
||||||
r'<p class="[^"]*medium-description[^"]*">([^<]+)</p>',
|
r'<p class="[^"]*medium-description[^"]*">([^<]+)</p>',
|
||||||
webpage, 'description', fatal=False)
|
webpage, 'description', default=None)
|
||||||
|
if not description:
|
||||||
|
description = self._html_search_meta('description', webpage)
|
||||||
else:
|
else:
|
||||||
programme_id, title, description, duration, formats, subtitles = self._download_playlist(group_id)
|
programme_id, title, description, duration, formats, subtitles = self._download_playlist(group_id)
|
||||||
|
|
||||||
@@ -584,6 +590,7 @@ class BBCIE(BBCCoUkIE):
|
|||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': '''Judge Mindy Glazer: "I'm sorry to see you here... I always wondered what happened to you"''',
|
'title': '''Judge Mindy Glazer: "I'm sorry to see you here... I always wondered what happened to you"''',
|
||||||
'duration': 56,
|
'duration': 56,
|
||||||
|
'description': '''Judge Mindy Glazer: "I'm sorry to see you here... I always wondered what happened to you"''',
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
@@ -726,6 +733,7 @@ class BBCIE(BBCCoUkIE):
|
|||||||
# article with multiple videos embedded with playlist.sxml (e.g.
|
# article with multiple videos embedded with playlist.sxml (e.g.
|
||||||
# http://www.bbc.com/sport/0/football/34475836)
|
# http://www.bbc.com/sport/0/football/34475836)
|
||||||
playlists = re.findall(r'<param[^>]+name="playlist"[^>]+value="([^"]+)"', webpage)
|
playlists = re.findall(r'<param[^>]+name="playlist"[^>]+value="([^"]+)"', webpage)
|
||||||
|
playlists.extend(re.findall(r'data-media-id="([^"]+/playlist\.sxml)"', webpage))
|
||||||
if playlists:
|
if playlists:
|
||||||
entries = [
|
entries = [
|
||||||
self._extract_from_playlist_sxml(playlist_url, playlist_id, timestamp)
|
self._extract_from_playlist_sxml(playlist_url, playlist_id, timestamp)
|
||||||
@@ -778,8 +786,9 @@ class BBCIE(BBCCoUkIE):
|
|||||||
|
|
||||||
# single video story (e.g. http://www.bbc.com/travel/story/20150625-sri-lankas-spicy-secret)
|
# single video story (e.g. http://www.bbc.com/travel/story/20150625-sri-lankas-spicy-secret)
|
||||||
programme_id = self._search_regex(
|
programme_id = self._search_regex(
|
||||||
[r'data-video-player-vpid="([\da-z]{8})"',
|
[r'data-video-player-vpid="(%s)"' % self._ID_REGEX,
|
||||||
r'<param[^>]+name="externalIdentifier"[^>]+value="([\da-z]{8})"'],
|
r'<param[^>]+name="externalIdentifier"[^>]+value="(%s)"' % self._ID_REGEX,
|
||||||
|
r'videoId\s*:\s*["\'](%s)["\']' % self._ID_REGEX],
|
||||||
webpage, 'vpid', default=None)
|
webpage, 'vpid', default=None)
|
||||||
|
|
||||||
if programme_id:
|
if programme_id:
|
||||||
@@ -814,7 +823,7 @@ class BBCIE(BBCCoUkIE):
|
|||||||
|
|
||||||
# Multiple video article (e.g.
|
# Multiple video article (e.g.
|
||||||
# http://www.bbc.co.uk/blogs/adamcurtis/entries/3662a707-0af9-3149-963f-47bea720b460)
|
# http://www.bbc.co.uk/blogs/adamcurtis/entries/3662a707-0af9-3149-963f-47bea720b460)
|
||||||
EMBED_URL = r'https?://(?:www\.)?bbc\.co\.uk/(?:[^/]+/)+[\da-z]{8}(?:\b[^"]+)?'
|
EMBED_URL = r'https?://(?:www\.)?bbc\.co\.uk/(?:[^/]+/)+%s(?:\b[^"]+)?' % self._ID_REGEX
|
||||||
entries = []
|
entries = []
|
||||||
for match in extract_all(r'new\s+SMP\(({.+?})\)'):
|
for match in extract_all(r'new\s+SMP\(({.+?})\)'):
|
||||||
embed_url = match.get('playerSettings', {}).get('externalEmbedUrl')
|
embed_url = match.get('playerSettings', {}).get('externalEmbedUrl')
|
||||||
|
@@ -1,6 +1,11 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from ..compat import (
|
||||||
|
compat_chr,
|
||||||
|
compat_ord,
|
||||||
|
compat_urllib_parse_unquote,
|
||||||
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
int_or_none,
|
int_or_none,
|
||||||
parse_iso8601,
|
parse_iso8601,
|
||||||
@@ -29,16 +34,49 @@ class BeegIE(InfoExtractor):
|
|||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
video = self._download_json(
|
video = self._download_json(
|
||||||
'http://beeg.com/api/v1/video/%s' % video_id, video_id)
|
'http://beeg.com/api/v5/video/%s' % video_id, video_id)
|
||||||
|
|
||||||
|
def split(o, e):
|
||||||
|
def cut(s, x):
|
||||||
|
n.append(s[:x])
|
||||||
|
return s[x:]
|
||||||
|
n = []
|
||||||
|
r = len(o) % e
|
||||||
|
if r > 0:
|
||||||
|
o = cut(o, r)
|
||||||
|
while len(o) > e:
|
||||||
|
o = cut(o, e)
|
||||||
|
n.append(o)
|
||||||
|
return n
|
||||||
|
|
||||||
|
def decrypt_key(key):
|
||||||
|
# Reverse engineered from http://static.beeg.com/cpl/1105.js
|
||||||
|
a = '5ShMcIQlssOd7zChAIOlmeTZDaUxULbJRnywYaiB'
|
||||||
|
e = compat_urllib_parse_unquote(key)
|
||||||
|
o = ''.join([
|
||||||
|
compat_chr(compat_ord(e[n]) - compat_ord(a[n % len(a)]) % 21)
|
||||||
|
for n in range(len(e))])
|
||||||
|
return ''.join(split(o, 3)[::-1])
|
||||||
|
|
||||||
|
def decrypt_url(encrypted_url):
|
||||||
|
encrypted_url = self._proto_relative_url(
|
||||||
|
encrypted_url.replace('{DATA_MARKERS}', ''), 'http:')
|
||||||
|
key = self._search_regex(
|
||||||
|
r'/key=(.*?)%2Cend=', encrypted_url, 'key', default=None)
|
||||||
|
if not key:
|
||||||
|
return encrypted_url
|
||||||
|
return encrypted_url.replace(key, decrypt_key(key))
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
for format_id, video_url in video.items():
|
for format_id, video_url in video.items():
|
||||||
|
if not video_url:
|
||||||
|
continue
|
||||||
height = self._search_regex(
|
height = self._search_regex(
|
||||||
r'^(\d+)[pP]$', format_id, 'height', default=None)
|
r'^(\d+)[pP]$', format_id, 'height', default=None)
|
||||||
if not height:
|
if not height:
|
||||||
continue
|
continue
|
||||||
formats.append({
|
formats.append({
|
||||||
'url': self._proto_relative_url(video_url.replace('{DATA_MARKERS}', ''), 'http:'),
|
'url': decrypt_url(video_url),
|
||||||
'format_id': format_id,
|
'format_id': format_id,
|
||||||
'height': int(height),
|
'height': int(height),
|
||||||
})
|
})
|
||||||
|
@@ -2,141 +2,109 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import itertools
|
|
||||||
import json
|
|
||||||
import xml.etree.ElementTree as ET
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from ..compat import compat_str
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
int_or_none,
|
int_or_none,
|
||||||
unified_strdate,
|
unescapeHTML,
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
|
xpath_text,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class BiliBiliIE(InfoExtractor):
|
class BiliBiliIE(InfoExtractor):
|
||||||
_VALID_URL = r'http://www\.bilibili\.(?:tv|com)/video/av(?P<id>[0-9]+)/'
|
_VALID_URL = r'http://www\.bilibili\.(?:tv|com)/video/av(?P<id>\d+)(?:/index_(?P<page_num>\d+).html)?'
|
||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.bilibili.tv/video/av1074402/',
|
'url': 'http://www.bilibili.tv/video/av1074402/',
|
||||||
'md5': '2c301e4dab317596e837c3e7633e7d86',
|
'md5': '2c301e4dab317596e837c3e7633e7d86',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '1074402_part1',
|
'id': '1554319',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'title': '【金坷垃】金泡沫',
|
'title': '【金坷垃】金泡沫',
|
||||||
'duration': 308,
|
'duration': 308313,
|
||||||
'upload_date': '20140420',
|
'upload_date': '20140420',
|
||||||
'thumbnail': 're:^https?://.+\.jpg',
|
'thumbnail': 're:^https?://.+\.jpg',
|
||||||
|
'description': 'md5:ce18c2a2d2193f0df2917d270f2e5923',
|
||||||
|
'timestamp': 1397983878,
|
||||||
|
'uploader': '菊子桑',
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.bilibili.com/video/av1041170/',
|
'url': 'http://www.bilibili.com/video/av1041170/',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '1041170',
|
'id': '1041170',
|
||||||
'title': '【BD1080P】刀语【诸神&异域】',
|
'title': '【BD1080P】刀语【诸神&异域】',
|
||||||
|
'description': '这是个神奇的故事~每个人不留弹幕不给走哦~切利哦!~',
|
||||||
|
'uploader': '枫叶逝去',
|
||||||
|
'timestamp': 1396501299,
|
||||||
},
|
},
|
||||||
'playlist_count': 9,
|
'playlist_count': 9,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
webpage = self._download_webpage(url, video_id)
|
video_id = mobj.group('id')
|
||||||
|
page_num = mobj.group('page_num') or '1'
|
||||||
|
|
||||||
if '(此视频不存在或被删除)' in webpage:
|
view_data = self._download_json(
|
||||||
raise ExtractorError(
|
'http://api.bilibili.com/view?type=json&appkey=8e9fc618fbd41e28&id=%s&page=%s' % (video_id, page_num),
|
||||||
'The video does not exist or was deleted', expected=True)
|
video_id)
|
||||||
|
if 'error' in view_data:
|
||||||
|
raise ExtractorError('%s said: %s' % (self.IE_NAME, view_data['error']), expected=True)
|
||||||
|
|
||||||
if '>你没有权限浏览! 由于版权相关问题 我们不对您所在的地区提供服务<' in webpage:
|
cid = view_data['cid']
|
||||||
raise ExtractorError(
|
title = unescapeHTML(view_data['title'])
|
||||||
'The video is not available in your region due to copyright reasons',
|
|
||||||
expected=True)
|
|
||||||
|
|
||||||
video_code = self._search_regex(
|
doc = self._download_xml(
|
||||||
r'(?s)<div itemprop="video".*?>(.*?)</div>', webpage, 'video code')
|
'http://interface.bilibili.com/v_cdn_play?appkey=8e9fc618fbd41e28&cid=%s' % cid,
|
||||||
|
cid,
|
||||||
|
'Downloading page %s/%s' % (page_num, view_data['pages'])
|
||||||
|
)
|
||||||
|
|
||||||
title = self._html_search_meta(
|
if xpath_text(doc, './result') == 'error':
|
||||||
'media:title', video_code, 'title', fatal=True)
|
raise ExtractorError('%s said: %s' % (self.IE_NAME, xpath_text(doc, './message')), expected=True)
|
||||||
duration_str = self._html_search_meta(
|
|
||||||
'duration', video_code, 'duration')
|
|
||||||
if duration_str is None:
|
|
||||||
duration = None
|
|
||||||
else:
|
|
||||||
duration_mobj = re.match(
|
|
||||||
r'^T(?:(?P<hours>[0-9]+)H)?(?P<minutes>[0-9]+)M(?P<seconds>[0-9]+)S$',
|
|
||||||
duration_str)
|
|
||||||
duration = (
|
|
||||||
int_or_none(duration_mobj.group('hours'), default=0) * 3600 +
|
|
||||||
int(duration_mobj.group('minutes')) * 60 +
|
|
||||||
int(duration_mobj.group('seconds')))
|
|
||||||
upload_date = unified_strdate(self._html_search_meta(
|
|
||||||
'uploadDate', video_code, fatal=False))
|
|
||||||
thumbnail = self._html_search_meta(
|
|
||||||
'thumbnailUrl', video_code, 'thumbnail', fatal=False)
|
|
||||||
|
|
||||||
cid = self._search_regex(r'cid=(\d+)', webpage, 'cid')
|
|
||||||
|
|
||||||
entries = []
|
entries = []
|
||||||
|
|
||||||
lq_page = self._download_webpage(
|
for durl in doc.findall('./durl'):
|
||||||
'http://interface.bilibili.com/v_cdn_play?appkey=1&cid=%s' % cid,
|
size = xpath_text(durl, ['./filesize', './size'])
|
||||||
video_id,
|
|
||||||
note='Downloading LQ video info'
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
err_info = json.loads(lq_page)
|
|
||||||
raise ExtractorError(
|
|
||||||
'BiliBili said: ' + err_info['error_text'], expected=True)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
lq_doc = ET.fromstring(lq_page)
|
|
||||||
lq_durls = lq_doc.findall('./durl')
|
|
||||||
|
|
||||||
hq_doc = self._download_xml(
|
|
||||||
'http://interface.bilibili.com/playurl?appkey=1&cid=%s' % cid,
|
|
||||||
video_id,
|
|
||||||
note='Downloading HQ video info',
|
|
||||||
fatal=False,
|
|
||||||
)
|
|
||||||
if hq_doc is not False:
|
|
||||||
hq_durls = hq_doc.findall('./durl')
|
|
||||||
assert len(lq_durls) == len(hq_durls)
|
|
||||||
else:
|
|
||||||
hq_durls = itertools.repeat(None)
|
|
||||||
|
|
||||||
i = 1
|
|
||||||
for lq_durl, hq_durl in zip(lq_durls, hq_durls):
|
|
||||||
formats = [{
|
formats = [{
|
||||||
'format_id': 'lq',
|
'url': durl.find('./url').text,
|
||||||
'quality': 1,
|
'filesize': int_or_none(size),
|
||||||
'url': lq_durl.find('./url').text,
|
'ext': 'flv',
|
||||||
'filesize': int_or_none(
|
|
||||||
lq_durl.find('./size'), get_attr='text'),
|
|
||||||
}]
|
}]
|
||||||
if hq_durl is not None:
|
backup_urls = durl.find('./backup_url')
|
||||||
formats.append({
|
if backup_urls is not None:
|
||||||
'format_id': 'hq',
|
for backup_url in backup_urls.findall('./url'):
|
||||||
'quality': 2,
|
formats.append({'url': backup_url.text})
|
||||||
'ext': 'flv',
|
formats.reverse()
|
||||||
'url': hq_durl.find('./url').text,
|
|
||||||
'filesize': int_or_none(
|
|
||||||
hq_durl.find('./size'), get_attr='text'),
|
|
||||||
})
|
|
||||||
self._sort_formats(formats)
|
|
||||||
|
|
||||||
entries.append({
|
entries.append({
|
||||||
'id': '%s_part%d' % (video_id, i),
|
'id': '%s_part%s' % (cid, xpath_text(durl, './order')),
|
||||||
'title': title,
|
'title': title,
|
||||||
|
'duration': int_or_none(xpath_text(durl, './length'), 1000),
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
'duration': duration,
|
|
||||||
'upload_date': upload_date,
|
|
||||||
'thumbnail': thumbnail,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
i += 1
|
info = {
|
||||||
|
'id': compat_str(cid),
|
||||||
return {
|
'title': title,
|
||||||
'_type': 'multi_video',
|
'description': view_data.get('description'),
|
||||||
'entries': entries,
|
'thumbnail': view_data.get('pic'),
|
||||||
'id': video_id,
|
'uploader': view_data.get('author'),
|
||||||
'title': title
|
'timestamp': int_or_none(view_data.get('created')),
|
||||||
|
'view_count': int_or_none(view_data.get('play')),
|
||||||
|
'duration': int_or_none(xpath_text(doc, './timelength')),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(entries) == 1:
|
||||||
|
entries[0].update(info)
|
||||||
|
return entries[0]
|
||||||
|
else:
|
||||||
|
info.update({
|
||||||
|
'_type': 'multi_video',
|
||||||
|
'id': video_id,
|
||||||
|
'entries': entries,
|
||||||
|
})
|
||||||
|
return info
|
||||||
|
@@ -4,14 +4,12 @@ import re
|
|||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
|
||||||
from ..compat import (
|
from ..compat import compat_urlparse
|
||||||
compat_urllib_request,
|
|
||||||
compat_urlparse,
|
|
||||||
)
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
clean_html,
|
clean_html,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
parse_iso8601,
|
parse_iso8601,
|
||||||
|
sanitized_Request,
|
||||||
unescapeHTML,
|
unescapeHTML,
|
||||||
xpath_text,
|
xpath_text,
|
||||||
xpath_with_ns,
|
xpath_with_ns,
|
||||||
@@ -219,7 +217,7 @@ class BlipTVIE(InfoExtractor):
|
|||||||
for lang, url in subtitles_urls.items():
|
for lang, url in subtitles_urls.items():
|
||||||
# For some weird reason, blip.tv serves a video instead of subtitles
|
# For some weird reason, blip.tv serves a video instead of subtitles
|
||||||
# when we request with a common UA
|
# when we request with a common UA
|
||||||
req = compat_urllib_request.Request(url)
|
req = sanitized_Request(url)
|
||||||
req.add_header('User-Agent', 'youtube-dl')
|
req.add_header('User-Agent', 'youtube-dl')
|
||||||
subtitles[lang] = [{
|
subtitles[lang] = [{
|
||||||
# The extension is 'srt' but it's actually an 'ass' file
|
# The extension is 'srt' but it's actually an 'ass' file
|
||||||
|
@@ -6,9 +6,9 @@ from .common import InfoExtractor
|
|||||||
|
|
||||||
|
|
||||||
class BloombergIE(InfoExtractor):
|
class BloombergIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://www\.bloomberg\.com/news/videos/[^/]+/(?P<id>[^/?#]+)'
|
_VALID_URL = r'https?://(?:www\.)?bloomberg\.com/(?:[^/]+/)*(?P<id>[^/?#]+)'
|
||||||
|
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
'url': 'http://www.bloomberg.com/news/videos/b/aaeae121-5949-481e-a1ce-4562db6f5df2',
|
'url': 'http://www.bloomberg.com/news/videos/b/aaeae121-5949-481e-a1ce-4562db6f5df2',
|
||||||
# The md5 checksum changes
|
# The md5 checksum changes
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@@ -17,22 +17,39 @@ class BloombergIE(InfoExtractor):
|
|||||||
'title': 'Shah\'s Presentation on Foreign-Exchange Strategies',
|
'title': 'Shah\'s Presentation on Foreign-Exchange Strategies',
|
||||||
'description': 'md5:a8ba0302912d03d246979735c17d2761',
|
'description': 'md5:a8ba0302912d03d246979735c17d2761',
|
||||||
},
|
},
|
||||||
}
|
}, {
|
||||||
|
'url': 'http://www.bloomberg.com/news/articles/2015-11-12/five-strange-things-that-have-been-happening-in-financial-markets',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.bloomberg.com/politics/videos/2015-11-25/karl-rove-on-jeb-bush-s-struggles-stopping-trump',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
name = self._match_id(url)
|
name = self._match_id(url)
|
||||||
webpage = self._download_webpage(url, name)
|
webpage = self._download_webpage(url, name)
|
||||||
video_id = self._search_regex(r'"bmmrId":"(.+?)"', webpage, 'id')
|
video_id = self._search_regex(
|
||||||
|
r'["\']bmmrId["\']\s*:\s*(["\'])(?P<url>.+?)\1',
|
||||||
|
webpage, 'id', group='url')
|
||||||
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(
|
||||||
'http://www.bloomberg.com/api/embed?id=%s' % video_id, video_id)
|
'http://www.bloomberg.com/api/embed?id=%s' % video_id, video_id)
|
||||||
formats = []
|
formats = []
|
||||||
for stream in embed_info['streams']:
|
for stream in embed_info['streams']:
|
||||||
if stream["muxing_format"] == "TS":
|
stream_url = stream.get('url')
|
||||||
formats.extend(self._extract_m3u8_formats(stream['url'], video_id))
|
if not stream_url:
|
||||||
|
continue
|
||||||
|
if stream['muxing_format'] == 'TS':
|
||||||
|
m3u8_formats = self._extract_m3u8_formats(
|
||||||
|
stream_url, video_id, 'mp4', m3u8_id='hls', fatal=False)
|
||||||
|
if m3u8_formats:
|
||||||
|
formats.extend(m3u8_formats)
|
||||||
else:
|
else:
|
||||||
formats.extend(self._extract_f4m_formats(stream['url'], video_id))
|
f4m_formats = self._extract_f4m_formats(
|
||||||
|
stream_url, video_id, f4m_id='hds', fatal=False)
|
||||||
|
if f4m_formats:
|
||||||
|
formats.extend(f4m_formats)
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@@ -3,15 +3,14 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import re
|
import re
|
||||||
import json
|
import json
|
||||||
import xml.etree.ElementTree
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import (
|
||||||
|
compat_etree_fromstring,
|
||||||
compat_parse_qs,
|
compat_parse_qs,
|
||||||
compat_str,
|
compat_str,
|
||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
compat_urllib_parse_urlparse,
|
compat_urllib_parse_urlparse,
|
||||||
compat_urllib_request,
|
|
||||||
compat_urlparse,
|
compat_urlparse,
|
||||||
compat_xml_parse_error,
|
compat_xml_parse_error,
|
||||||
)
|
)
|
||||||
@@ -20,12 +19,18 @@ from ..utils import (
|
|||||||
ExtractorError,
|
ExtractorError,
|
||||||
find_xpath_attr,
|
find_xpath_attr,
|
||||||
fix_xml_ampersands,
|
fix_xml_ampersands,
|
||||||
|
float_or_none,
|
||||||
|
js_to_json,
|
||||||
|
int_or_none,
|
||||||
|
parse_iso8601,
|
||||||
|
sanitized_Request,
|
||||||
unescapeHTML,
|
unescapeHTML,
|
||||||
unsmuggle_url,
|
unsmuggle_url,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class BrightcoveIE(InfoExtractor):
|
class BrightcoveLegacyIE(InfoExtractor):
|
||||||
|
IE_NAME = 'brightcove:legacy'
|
||||||
_VALID_URL = r'(?:https?://.*brightcove\.com/(services|viewer).*?\?|brightcove:)(?P<query>.*)'
|
_VALID_URL = r'(?:https?://.*brightcove\.com/(services|viewer).*?\?|brightcove:)(?P<query>.*)'
|
||||||
_FEDERATED_URL_TEMPLATE = 'http://c.brightcove.com/services/viewer/htmlFederated?%s'
|
_FEDERATED_URL_TEMPLATE = 'http://c.brightcove.com/services/viewer/htmlFederated?%s'
|
||||||
|
|
||||||
@@ -119,7 +124,7 @@ class BrightcoveIE(InfoExtractor):
|
|||||||
object_str = fix_xml_ampersands(object_str)
|
object_str = fix_xml_ampersands(object_str)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
object_doc = xml.etree.ElementTree.fromstring(object_str.encode('utf-8'))
|
object_doc = compat_etree_fromstring(object_str.encode('utf-8'))
|
||||||
except compat_xml_parse_error:
|
except compat_xml_parse_error:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -245,7 +250,7 @@ class BrightcoveIE(InfoExtractor):
|
|||||||
|
|
||||||
def _get_video_info(self, video_id, query_str, query, referer=None):
|
def _get_video_info(self, video_id, query_str, query, referer=None):
|
||||||
request_url = self._FEDERATED_URL_TEMPLATE % query_str
|
request_url = self._FEDERATED_URL_TEMPLATE % query_str
|
||||||
req = compat_urllib_request.Request(request_url)
|
req = sanitized_Request(request_url)
|
||||||
linkBase = query.get('linkBaseURL')
|
linkBase = query.get('linkBaseURL')
|
||||||
if linkBase is not None:
|
if linkBase is not None:
|
||||||
referer = linkBase[0]
|
referer = linkBase[0]
|
||||||
@@ -346,3 +351,172 @@ class BrightcoveIE(InfoExtractor):
|
|||||||
if 'url' not in info and not info.get('formats'):
|
if 'url' not in info and not info.get('formats'):
|
||||||
raise ExtractorError('Unable to extract video url for %s' % info['id'])
|
raise ExtractorError('Unable to extract video url for %s' % info['id'])
|
||||||
return info
|
return info
|
||||||
|
|
||||||
|
|
||||||
|
class BrightcoveNewIE(InfoExtractor):
|
||||||
|
IE_NAME = 'brightcove:new'
|
||||||
|
_VALID_URL = r'https?://players\.brightcove\.net/(?P<account_id>\d+)/(?P<player_id>[^/]+)_(?P<embed>[^/]+)/index\.html\?.*videoId=(?P<video_id>\d+)'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://players.brightcove.net/929656772001/e41d32dc-ec74-459e-a845-6c69f7b724ea_default/index.html?videoId=4463358922001',
|
||||||
|
'md5': 'c8100925723840d4b0d243f7025703be',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '4463358922001',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Meet the man behind Popcorn Time',
|
||||||
|
'description': 'md5:eac376a4fe366edc70279bfb681aea16',
|
||||||
|
'duration': 165.768,
|
||||||
|
'timestamp': 1441391203,
|
||||||
|
'upload_date': '20150904',
|
||||||
|
'uploader_id': '929656772001',
|
||||||
|
'formats': 'mincount:22',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
# with rtmp streams
|
||||||
|
'url': 'http://players.brightcove.net/4036320279001/5d112ed9-283f-485f-a7f9-33f42e8bc042_default/index.html?videoId=4279049078001',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '4279049078001',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Titansgrave: Chapter 0',
|
||||||
|
'description': 'Titansgrave: Chapter 0',
|
||||||
|
'duration': 1242.058,
|
||||||
|
'timestamp': 1433556729,
|
||||||
|
'upload_date': '20150606',
|
||||||
|
'uploader_id': '4036320279001',
|
||||||
|
'formats': 'mincount:41',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True,
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _extract_urls(webpage):
|
||||||
|
# Reference:
|
||||||
|
# 1. http://docs.brightcove.com/en/video-cloud/brightcove-player/guides/publish-video.html#setvideoiniframe
|
||||||
|
# 2. http://docs.brightcove.com/en/video-cloud/brightcove-player/guides/publish-video.html#setvideousingjavascript)
|
||||||
|
# 3. http://docs.brightcove.com/en/video-cloud/brightcove-player/guides/embed-in-page.html
|
||||||
|
|
||||||
|
entries = []
|
||||||
|
|
||||||
|
# Look for iframe embeds [1]
|
||||||
|
for _, url in re.findall(
|
||||||
|
r'<iframe[^>]+src=(["\'])((?:https?:)//players\.brightcove\.net/\d+/[^/]+/index\.html.+?)\1', webpage):
|
||||||
|
entries.append(url)
|
||||||
|
|
||||||
|
# Look for embed_in_page embeds [2]
|
||||||
|
for video_id, account_id, player_id, embed in re.findall(
|
||||||
|
# According to examples from [3] it's unclear whether video id
|
||||||
|
# may be optional and what to do when it is
|
||||||
|
r'''(?sx)
|
||||||
|
<video[^>]+
|
||||||
|
data-video-id=["\'](\d+)["\'][^>]*>.*?
|
||||||
|
</video>.*?
|
||||||
|
<script[^>]+
|
||||||
|
src=["\'](?:https?:)?//players\.brightcove\.net/
|
||||||
|
(\d+)/([\da-f-]+)_([^/]+)/index\.min\.js
|
||||||
|
''', webpage):
|
||||||
|
entries.append(
|
||||||
|
'http://players.brightcove.net/%s/%s_%s/index.html?videoId=%s'
|
||||||
|
% (account_id, player_id, embed, video_id))
|
||||||
|
|
||||||
|
return entries
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
account_id, player_id, embed, video_id = re.match(self._VALID_URL, url).groups()
|
||||||
|
|
||||||
|
webpage = self._download_webpage(
|
||||||
|
'http://players.brightcove.net/%s/%s_%s/index.min.js'
|
||||||
|
% (account_id, player_id, embed), video_id)
|
||||||
|
|
||||||
|
policy_key = None
|
||||||
|
|
||||||
|
catalog = self._search_regex(
|
||||||
|
r'catalog\(({.+?})\);', webpage, 'catalog', default=None)
|
||||||
|
if catalog:
|
||||||
|
catalog = self._parse_json(
|
||||||
|
js_to_json(catalog), video_id, fatal=False)
|
||||||
|
if catalog:
|
||||||
|
policy_key = catalog.get('policyKey')
|
||||||
|
|
||||||
|
if not policy_key:
|
||||||
|
policy_key = self._search_regex(
|
||||||
|
r'policyKey\s*:\s*(["\'])(?P<pk>.+?)\1',
|
||||||
|
webpage, 'policy key', group='pk')
|
||||||
|
|
||||||
|
req = sanitized_Request(
|
||||||
|
'https://edge.api.brightcove.com/playback/v1/accounts/%s/videos/%s'
|
||||||
|
% (account_id, video_id),
|
||||||
|
headers={'Accept': 'application/json;pk=%s' % policy_key})
|
||||||
|
json_data = self._download_json(req, video_id)
|
||||||
|
|
||||||
|
title = json_data['name']
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
for source in json_data.get('sources', []):
|
||||||
|
source_type = source.get('type')
|
||||||
|
src = source.get('src')
|
||||||
|
if source_type == 'application/x-mpegURL':
|
||||||
|
if not src:
|
||||||
|
continue
|
||||||
|
m3u8_formats = self._extract_m3u8_formats(
|
||||||
|
src, video_id, 'mp4', entry_protocol='m3u8_native',
|
||||||
|
m3u8_id='hls', fatal=False)
|
||||||
|
if m3u8_formats:
|
||||||
|
formats.extend(m3u8_formats)
|
||||||
|
else:
|
||||||
|
streaming_src = source.get('streaming_src')
|
||||||
|
stream_name, app_name = source.get('stream_name'), source.get('app_name')
|
||||||
|
if not src and not streaming_src and (not stream_name or not app_name):
|
||||||
|
continue
|
||||||
|
tbr = float_or_none(source.get('avg_bitrate'), 1000)
|
||||||
|
height = int_or_none(source.get('height'))
|
||||||
|
f = {
|
||||||
|
'tbr': tbr,
|
||||||
|
'width': int_or_none(source.get('width')),
|
||||||
|
'height': height,
|
||||||
|
'filesize': int_or_none(source.get('size')),
|
||||||
|
'container': source.get('container'),
|
||||||
|
'vcodec': source.get('codec'),
|
||||||
|
'ext': source.get('container').lower(),
|
||||||
|
}
|
||||||
|
|
||||||
|
def build_format_id(kind):
|
||||||
|
format_id = kind
|
||||||
|
if tbr:
|
||||||
|
format_id += '-%dk' % int(tbr)
|
||||||
|
if height:
|
||||||
|
format_id += '-%dp' % height
|
||||||
|
return format_id
|
||||||
|
|
||||||
|
if src or streaming_src:
|
||||||
|
f.update({
|
||||||
|
'url': src or streaming_src,
|
||||||
|
'format_id': build_format_id('http' if src else 'http-streaming'),
|
||||||
|
'preference': 2 if src else 1,
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
f.update({
|
||||||
|
'url': app_name,
|
||||||
|
'play_path': stream_name,
|
||||||
|
'format_id': build_format_id('rtmp'),
|
||||||
|
})
|
||||||
|
formats.append(f)
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
description = json_data.get('description')
|
||||||
|
thumbnail = json_data.get('thumbnail')
|
||||||
|
timestamp = parse_iso8601(json_data.get('published_at'))
|
||||||
|
duration = float_or_none(json_data.get('duration'), 1000)
|
||||||
|
tags = json_data.get('tags', [])
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'duration': duration,
|
||||||
|
'timestamp': timestamp,
|
||||||
|
'uploader_id': account_id,
|
||||||
|
'formats': formats,
|
||||||
|
'tags': tags,
|
||||||
|
}
|
||||||
|
@@ -14,9 +14,10 @@ class BYUtvIE(InfoExtractor):
|
|||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'studio-c-season-5-episode-5',
|
'id': 'studio-c-season-5-episode-5',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'description': 'md5:5438d33774b6bdc662f9485a340401cc',
|
'description': 'md5:e07269172baff037f8e8bf9956bc9747',
|
||||||
'title': 'Season 5 Episode 5',
|
'title': 'Season 5 Episode 5',
|
||||||
'thumbnail': 're:^https?://.*\.jpg$'
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
'duration': 1486.486,
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
|
@@ -1,6 +1,10 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
sanitized_Request,
|
||||||
|
smuggle_url,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CBSIE(InfoExtractor):
|
class CBSIE(InfoExtractor):
|
||||||
@@ -46,13 +50,19 @@ class CBSIE(InfoExtractor):
|
|||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
display_id = self._match_id(url)
|
display_id = self._match_id(url)
|
||||||
webpage = self._download_webpage(url, display_id)
|
request = sanitized_Request(url)
|
||||||
|
# Android UA is served with higher quality (720p) streams (see
|
||||||
|
# https://github.com/rg3/youtube-dl/issues/7490)
|
||||||
|
request.add_header('User-Agent', 'Mozilla/5.0 (Linux; Android 4.4; Nexus 5)')
|
||||||
|
webpage = self._download_webpage(request, display_id)
|
||||||
real_id = self._search_regex(
|
real_id = self._search_regex(
|
||||||
[r"video\.settings\.pid\s*=\s*'([^']+)';", r"cbsplayer\.pid\s*=\s*'([^']+)';"],
|
[r"video\.settings\.pid\s*=\s*'([^']+)';", r"cbsplayer\.pid\s*=\s*'([^']+)';"],
|
||||||
webpage, 'real video ID')
|
webpage, 'real video ID')
|
||||||
return {
|
return {
|
||||||
'_type': 'url_transparent',
|
'_type': 'url_transparent',
|
||||||
'ie_key': 'ThePlatform',
|
'ie_key': 'ThePlatform',
|
||||||
'url': 'theplatform:%s' % real_id,
|
'url': smuggle_url(
|
||||||
|
'http://link.theplatform.com/s/dJ5BDC/%s?mbr=true&manifest=m3u' % real_id,
|
||||||
|
{'force_smil_url': True}),
|
||||||
'display_id': display_id,
|
'display_id': display_id,
|
||||||
}
|
}
|
||||||
|
@@ -67,9 +67,12 @@ class CBSNewsIE(InfoExtractor):
|
|||||||
'format_id': format_id,
|
'format_id': format_id,
|
||||||
}
|
}
|
||||||
if uri.startswith('rtmp'):
|
if uri.startswith('rtmp'):
|
||||||
|
play_path = re.sub(
|
||||||
|
r'{slistFilePath}', '',
|
||||||
|
uri.split('<break>')[-1].split('{break}')[-1])
|
||||||
fmt.update({
|
fmt.update({
|
||||||
'app': 'ondemand?auth=cbs',
|
'app': 'ondemand?auth=cbs',
|
||||||
'play_path': 'mp4:' + uri.split('<break>')[-1],
|
'play_path': 'mp4:' + play_path,
|
||||||
'player_url': 'http://www.cbsnews.com/[[IMPORT]]/vidtech.cbsinteractive.com/player/3_3_0/CBSI_PLAYER_HD.swf',
|
'player_url': 'http://www.cbsnews.com/[[IMPORT]]/vidtech.cbsinteractive.com/player/3_3_0/CBSI_PLAYER_HD.swf',
|
||||||
'page_url': 'http://www.cbsnews.com',
|
'page_url': 'http://www.cbsnews.com',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
|
@@ -5,7 +5,6 @@ import re
|
|||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import (
|
||||||
compat_urllib_request,
|
|
||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
compat_urllib_parse_unquote,
|
compat_urllib_parse_unquote,
|
||||||
compat_urllib_parse_urlparse,
|
compat_urllib_parse_urlparse,
|
||||||
@@ -13,6 +12,7 @@ from ..compat import (
|
|||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
float_or_none,
|
float_or_none,
|
||||||
|
sanitized_Request,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ class CeskaTelevizeIE(InfoExtractor):
|
|||||||
'requestSource': 'iVysilani',
|
'requestSource': 'iVysilani',
|
||||||
}
|
}
|
||||||
|
|
||||||
req = compat_urllib_request.Request(
|
req = sanitized_Request(
|
||||||
'http://www.ceskatelevize.cz/ivysilani/ajax/get-client-playlist',
|
'http://www.ceskatelevize.cz/ivysilani/ajax/get-client-playlist',
|
||||||
data=compat_urllib_parse.urlencode(data))
|
data=compat_urllib_parse.urlencode(data))
|
||||||
|
|
||||||
@@ -115,7 +115,7 @@ class CeskaTelevizeIE(InfoExtractor):
|
|||||||
if playlist_url == 'error_region':
|
if playlist_url == 'error_region':
|
||||||
raise ExtractorError(NOT_AVAILABLE_STRING, expected=True)
|
raise ExtractorError(NOT_AVAILABLE_STRING, expected=True)
|
||||||
|
|
||||||
req = compat_urllib_request.Request(compat_urllib_parse_unquote(playlist_url))
|
req = sanitized_Request(compat_urllib_parse_unquote(playlist_url))
|
||||||
req.add_header('Referer', url)
|
req.add_header('Referer', url)
|
||||||
|
|
||||||
playlist_title = self._og_search_title(webpage)
|
playlist_title = self._og_search_title(webpage)
|
||||||
|
@@ -1,14 +1,9 @@
|
|||||||
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 (
|
||||||
determine_ext,
|
|
||||||
int_or_none,
|
int_or_none,
|
||||||
js_to_json,
|
unified_strdate,
|
||||||
parse_iso8601,
|
|
||||||
remove_end,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -21,48 +16,47 @@ class ClipfishIE(InfoExtractor):
|
|||||||
'id': '3966754',
|
'id': '3966754',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'FIFA 14 - E3 2013 Trailer',
|
'title': 'FIFA 14 - E3 2013 Trailer',
|
||||||
'timestamp': 1370938118,
|
'description': 'Video zu FIFA 14: E3 2013 Trailer',
|
||||||
'upload_date': '20130611',
|
'upload_date': '20130611',
|
||||||
'duration': 82,
|
'duration': 82,
|
||||||
|
'view_count': int,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
video_info = self._download_json(
|
||||||
|
'http://www.clipfish.de/devapi/id/%s?format=json&apikey=hbbtv' % video_id,
|
||||||
video_info = self._parse_json(
|
video_id)['items'][0]
|
||||||
js_to_json(self._html_search_regex(
|
|
||||||
'(?s)videoObject\s*=\s*({.+?});', webpage, 'video object')),
|
|
||||||
video_id)
|
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
for video_url in re.findall(r'var\s+videourl\s*=\s*"([^"]+)"', webpage):
|
|
||||||
ext = determine_ext(video_url)
|
|
||||||
if ext == 'm3u8':
|
|
||||||
formats.append({
|
|
||||||
'url': video_url.replace('de.hls.fra.clipfish.de', 'hls.fra.clipfish.de'),
|
|
||||||
'ext': 'mp4',
|
|
||||||
'format_id': 'hls',
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
formats.append({
|
|
||||||
'url': video_url,
|
|
||||||
'format_id': ext,
|
|
||||||
})
|
|
||||||
self._sort_formats(formats)
|
|
||||||
|
|
||||||
title = remove_end(self._og_search_title(webpage), ' - Video')
|
m3u8_url = video_info.get('media_videourl_hls')
|
||||||
thumbnail = self._og_search_thumbnail(webpage)
|
if m3u8_url:
|
||||||
duration = int_or_none(video_info.get('length'))
|
formats.append({
|
||||||
timestamp = parse_iso8601(self._html_search_meta('uploadDate', webpage, 'upload date'))
|
'url': m3u8_url.replace('de.hls.fra.clipfish.de', 'hls.fra.clipfish.de'),
|
||||||
|
'ext': 'mp4',
|
||||||
|
'format_id': 'hls',
|
||||||
|
})
|
||||||
|
|
||||||
|
mp4_url = video_info.get('media_videourl')
|
||||||
|
if mp4_url:
|
||||||
|
formats.append({
|
||||||
|
'url': mp4_url,
|
||||||
|
'format_id': 'mp4',
|
||||||
|
'width': int_or_none(video_info.get('width')),
|
||||||
|
'height': int_or_none(video_info.get('height')),
|
||||||
|
'tbr': int_or_none(video_info.get('bitrate')),
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': title,
|
'title': video_info['title'],
|
||||||
|
'description': video_info.get('descr'),
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
'thumbnail': thumbnail,
|
'thumbnail': video_info.get('media_content_thumbnail_large') or video_info.get('media_thumbnail'),
|
||||||
'duration': duration,
|
'duration': int_or_none(video_info.get('media_length')),
|
||||||
'timestamp': timestamp,
|
'upload_date': unified_strdate(video_info.get('pubDate')),
|
||||||
|
'view_count': int_or_none(video_info.get('media_views'))
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import determine_ext
|
from ..utils import int_or_none
|
||||||
|
|
||||||
|
|
||||||
_translation_table = {
|
_translation_table = {
|
||||||
@@ -42,31 +42,26 @@ class CliphunterIE(InfoExtractor):
|
|||||||
video_title = self._search_regex(
|
video_title = self._search_regex(
|
||||||
r'mediaTitle = "([^"]+)"', webpage, 'title')
|
r'mediaTitle = "([^"]+)"', webpage, 'title')
|
||||||
|
|
||||||
fmts = {}
|
gexo_files = self._parse_json(
|
||||||
for fmt in ('mp4', 'flv'):
|
self._search_regex(
|
||||||
fmt_list = self._parse_json(self._search_regex(
|
r'var\s+gexoFiles\s*=\s*({.+?});', webpage, 'gexo files'),
|
||||||
r'var %sjson\s*=\s*(\[.*?\]);' % fmt, webpage, '%s formats' % fmt), video_id)
|
video_id)
|
||||||
for f in fmt_list:
|
|
||||||
fmts[f['fname']] = _decode(f['sUrl'])
|
|
||||||
|
|
||||||
qualities = self._parse_json(self._search_regex(
|
|
||||||
r'var player_btns\s*=\s*(.*?);\n', webpage, 'quality info'), video_id)
|
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
for fname, url in fmts.items():
|
for format_id, f in gexo_files.items():
|
||||||
f = {
|
video_url = f.get('url')
|
||||||
'url': url,
|
if not video_url:
|
||||||
}
|
continue
|
||||||
if fname in qualities:
|
fmt = f.get('fmt')
|
||||||
qual = qualities[fname]
|
height = f.get('h')
|
||||||
f.update({
|
format_id = '%s_%sp' % (fmt, height) if fmt and height else format_id
|
||||||
'format_id': '%s_%sp' % (determine_ext(url), qual['h']),
|
formats.append({
|
||||||
'width': qual['w'],
|
'url': _decode(video_url),
|
||||||
'height': qual['h'],
|
'format_id': format_id,
|
||||||
'tbr': qual['br'],
|
'width': int_or_none(f.get('w')),
|
||||||
})
|
'height': int_or_none(height),
|
||||||
formats.append(f)
|
'tbr': int_or_none(f.get('br')),
|
||||||
|
})
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
thumbnail = self._search_regex(
|
thumbnail = self._search_regex(
|
||||||
|
57
youtube_dl/extractor/clyp.py
Normal file
57
youtube_dl/extractor/clyp.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
float_or_none,
|
||||||
|
parse_iso8601,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ClypIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?clyp\.it/(?P<id>[a-z0-9]+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'https://clyp.it/ojz2wfah',
|
||||||
|
'md5': '1d4961036c41247ecfdcc439c0cddcbb',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'ojz2wfah',
|
||||||
|
'ext': 'mp3',
|
||||||
|
'title': 'Krisson80 - bits wip wip',
|
||||||
|
'description': '#Krisson80BitsWipWip #chiptune\n#wip',
|
||||||
|
'duration': 263.21,
|
||||||
|
'timestamp': 1443515251,
|
||||||
|
'upload_date': '20150929',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
audio_id = self._match_id(url)
|
||||||
|
|
||||||
|
metadata = self._download_json(
|
||||||
|
'https://api.clyp.it/%s' % audio_id, audio_id)
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
for secure in ('', 'Secure'):
|
||||||
|
for ext in ('Ogg', 'Mp3'):
|
||||||
|
format_id = '%s%s' % (secure, ext)
|
||||||
|
format_url = metadata.get('%sUrl' % format_id)
|
||||||
|
if format_url:
|
||||||
|
formats.append({
|
||||||
|
'url': format_url,
|
||||||
|
'format_id': format_id,
|
||||||
|
'vcodec': 'none',
|
||||||
|
})
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
title = metadata['Title']
|
||||||
|
description = metadata.get('Description')
|
||||||
|
duration = float_or_none(metadata.get('Duration'))
|
||||||
|
timestamp = parse_iso8601(metadata.get('DateCreated'))
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': audio_id,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'duration': duration,
|
||||||
|
'timestamp': timestamp,
|
||||||
|
'formats': formats,
|
||||||
|
}
|
@@ -4,7 +4,7 @@ from .mtv import MTVIE
|
|||||||
|
|
||||||
class CMTIE(MTVIE):
|
class CMTIE(MTVIE):
|
||||||
IE_NAME = 'cmt.com'
|
IE_NAME = 'cmt.com'
|
||||||
_VALID_URL = r'https?://www\.cmt\.com/videos/.+?/(?P<videoid>[^/]+)\.jhtml'
|
_VALID_URL = r'https?://www\.cmt\.com/(?:videos|shows)/(?:[^/]+/)*(?P<videoid>\d+)'
|
||||||
_FEED_URL = 'http://www.cmt.com/sitewide/apps/player/embed/rss/'
|
_FEED_URL = 'http://www.cmt.com/sitewide/apps/player/embed/rss/'
|
||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
@@ -16,4 +16,7 @@ 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',
|
||||||
},
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.cmt.com/shows/party-down-south/party-down-south-ep-407-gone-girl/1738172/playlist/#id=1738172',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
@@ -3,10 +3,10 @@ from __future__ import unicode_literals
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import compat_urllib_request
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
float_or_none,
|
float_or_none,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
|
sanitized_Request,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ class CollegeRamaIE(InfoExtractor):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
request = compat_urllib_request.Request(
|
request = sanitized_Request(
|
||||||
'http://collegerama.tudelft.nl/Mediasite/PlayerService/PlayerService.svc/json/GetPlayerOptions',
|
'http://collegerama.tudelft.nl/Mediasite/PlayerService/PlayerService.svc/json/GetPlayerOptions',
|
||||||
json.dumps(player_options_request))
|
json.dumps(player_options_request))
|
||||||
request.add_header('Content-Type', 'application/json')
|
request.add_header('Content-Type', 'application/json')
|
||||||
|
@@ -10,20 +10,18 @@ import re
|
|||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import xml.etree.ElementTree
|
|
||||||
|
|
||||||
from ..compat import (
|
from ..compat import (
|
||||||
compat_cookiejar,
|
compat_cookiejar,
|
||||||
compat_cookies,
|
compat_cookies,
|
||||||
compat_getpass,
|
compat_getpass,
|
||||||
compat_HTTPError,
|
|
||||||
compat_http_client,
|
compat_http_client,
|
||||||
compat_urllib_error,
|
compat_urllib_error,
|
||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
compat_urllib_parse_urlparse,
|
compat_urllib_parse_urlparse,
|
||||||
compat_urllib_request,
|
|
||||||
compat_urlparse,
|
compat_urlparse,
|
||||||
compat_str,
|
compat_str,
|
||||||
|
compat_etree_fromstring,
|
||||||
)
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
NO_DEFAULT,
|
NO_DEFAULT,
|
||||||
@@ -38,6 +36,7 @@ from ..utils import (
|
|||||||
int_or_none,
|
int_or_none,
|
||||||
RegexNotFoundError,
|
RegexNotFoundError,
|
||||||
sanitize_filename,
|
sanitize_filename,
|
||||||
|
sanitized_Request,
|
||||||
unescapeHTML,
|
unescapeHTML,
|
||||||
unified_strdate,
|
unified_strdate,
|
||||||
url_basename,
|
url_basename,
|
||||||
@@ -168,7 +167,7 @@ class InfoExtractor(object):
|
|||||||
"ext" will be calculated from URL if missing
|
"ext" will be calculated from URL if missing
|
||||||
automatic_captions: Like 'subtitles', used by the YoutubeIE for
|
automatic_captions: Like 'subtitles', used by the YoutubeIE for
|
||||||
automatically generated captions
|
automatically generated captions
|
||||||
duration: Length of the video in seconds, as an integer.
|
duration: Length of the video in seconds, as an integer or float.
|
||||||
view_count: How many users have watched the video on the platform.
|
view_count: How many users have watched the video on the platform.
|
||||||
like_count: Number of positive ratings of the video
|
like_count: Number of positive ratings of the video
|
||||||
dislike_count: Number of negative ratings of the video
|
dislike_count: Number of negative ratings of the video
|
||||||
@@ -311,11 +310,11 @@ class InfoExtractor(object):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def ie_key(cls):
|
def ie_key(cls):
|
||||||
"""A string for getting the InfoExtractor with get_info_extractor"""
|
"""A string for getting the InfoExtractor with get_info_extractor"""
|
||||||
return cls.__name__[:-2]
|
return compat_str(cls.__name__[:-2])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def IE_NAME(self):
|
def IE_NAME(self):
|
||||||
return type(self).__name__[:-2]
|
return compat_str(type(self).__name__[:-2])
|
||||||
|
|
||||||
def _request_webpage(self, url_or_request, video_id, note=None, errnote=None, fatal=True):
|
def _request_webpage(self, url_or_request, video_id, note=None, errnote=None, fatal=True):
|
||||||
""" Returns the response handle """
|
""" Returns the response handle """
|
||||||
@@ -462,7 +461,7 @@ class InfoExtractor(object):
|
|||||||
return xml_string
|
return xml_string
|
||||||
if transform_source:
|
if transform_source:
|
||||||
xml_string = transform_source(xml_string)
|
xml_string = transform_source(xml_string)
|
||||||
return xml.etree.ElementTree.fromstring(xml_string.encode('utf-8'))
|
return compat_etree_fromstring(xml_string.encode('utf-8'))
|
||||||
|
|
||||||
def _download_json(self, url_or_request, video_id,
|
def _download_json(self, url_or_request, video_id,
|
||||||
note='Downloading JSON metadata',
|
note='Downloading JSON metadata',
|
||||||
@@ -842,7 +841,7 @@ class InfoExtractor(object):
|
|||||||
self._request_webpage(url, video_id, 'Checking %s URL' % item)
|
self._request_webpage(url, video_id, 'Checking %s URL' % item)
|
||||||
return True
|
return True
|
||||||
except ExtractorError as e:
|
except ExtractorError as e:
|
||||||
if isinstance(e.cause, compat_HTTPError):
|
if isinstance(e.cause, compat_urllib_error.URLError):
|
||||||
self.to_screen(
|
self.to_screen(
|
||||||
'%s: %s URL is invalid, skipping' % (video_id, item))
|
'%s: %s URL is invalid, skipping' % (video_id, item))
|
||||||
return False
|
return False
|
||||||
@@ -892,6 +891,11 @@ class InfoExtractor(object):
|
|||||||
if not media_nodes:
|
if not media_nodes:
|
||||||
manifest_version = '2.0'
|
manifest_version = '2.0'
|
||||||
media_nodes = manifest.findall('{http://ns.adobe.com/f4m/2.0}media')
|
media_nodes = manifest.findall('{http://ns.adobe.com/f4m/2.0}media')
|
||||||
|
base_url = xpath_text(
|
||||||
|
manifest, ['{http://ns.adobe.com/f4m/1.0}baseURL', '{http://ns.adobe.com/f4m/2.0}baseURL'],
|
||||||
|
'base URL', default=None)
|
||||||
|
if base_url:
|
||||||
|
base_url = base_url.strip()
|
||||||
for i, media_el in enumerate(media_nodes):
|
for i, media_el in enumerate(media_nodes):
|
||||||
if manifest_version == '2.0':
|
if manifest_version == '2.0':
|
||||||
media_url = media_el.attrib.get('href') or media_el.attrib.get('url')
|
media_url = media_el.attrib.get('href') or media_el.attrib.get('url')
|
||||||
@@ -899,7 +903,7 @@ class InfoExtractor(object):
|
|||||||
continue
|
continue
|
||||||
manifest_url = (
|
manifest_url = (
|
||||||
media_url if media_url.startswith('http://') or media_url.startswith('https://')
|
media_url if media_url.startswith('http://') or media_url.startswith('https://')
|
||||||
else ('/'.join(manifest_url.split('/')[:-1]) + '/' + media_url))
|
else ((base_url or '/'.join(manifest_url.split('/')[:-1])) + '/' + media_url))
|
||||||
# If media_url is itself a f4m manifest do the recursive extraction
|
# If media_url is itself a f4m manifest do the recursive extraction
|
||||||
# since bitrates in parent manifest (this one) and media_url manifest
|
# since bitrates in parent manifest (this one) and media_url manifest
|
||||||
# may differ leading to inability to resolve the format by requested
|
# may differ leading to inability to resolve the format by requested
|
||||||
@@ -944,13 +948,15 @@ class InfoExtractor(object):
|
|||||||
if re.match(r'^https?://', u)
|
if re.match(r'^https?://', u)
|
||||||
else compat_urlparse.urljoin(m3u8_url, u))
|
else compat_urlparse.urljoin(m3u8_url, u))
|
||||||
|
|
||||||
m3u8_doc = self._download_webpage(
|
res = self._download_webpage_handle(
|
||||||
m3u8_url, video_id,
|
m3u8_url, video_id,
|
||||||
note=note or 'Downloading m3u8 information',
|
note=note or 'Downloading m3u8 information',
|
||||||
errnote=errnote or 'Failed to download m3u8 information',
|
errnote=errnote or 'Failed to download m3u8 information',
|
||||||
fatal=fatal)
|
fatal=fatal)
|
||||||
if m3u8_doc is False:
|
if res is False:
|
||||||
return m3u8_doc
|
return res
|
||||||
|
m3u8_doc, urlh = res
|
||||||
|
m3u8_url = urlh.geturl()
|
||||||
last_info = None
|
last_info = None
|
||||||
last_media = None
|
last_media = None
|
||||||
kv_rex = re.compile(
|
kv_rex = re.compile(
|
||||||
@@ -1279,7 +1285,7 @@ class InfoExtractor(object):
|
|||||||
|
|
||||||
def _get_cookies(self, url):
|
def _get_cookies(self, url):
|
||||||
""" Return a compat_cookies.SimpleCookie with the cookies for the url """
|
""" Return a compat_cookies.SimpleCookie with the cookies for the url """
|
||||||
req = compat_urllib_request.Request(url)
|
req = sanitized_Request(url)
|
||||||
self._downloader.cookiejar.add_cookie_header(req)
|
self._downloader.cookiejar.add_cookie_header(req)
|
||||||
return compat_cookies.SimpleCookie(req.get_header('Cookie'))
|
return compat_cookies.SimpleCookie(req.get_header('Cookie'))
|
||||||
|
|
||||||
|
@@ -5,12 +5,12 @@ import re
|
|||||||
import json
|
import json
|
||||||
import base64
|
import base64
|
||||||
import zlib
|
import zlib
|
||||||
import xml.etree.ElementTree
|
|
||||||
|
|
||||||
from hashlib import sha1
|
from hashlib import sha1
|
||||||
from math import pow, sqrt, floor
|
from math import pow, sqrt, floor
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import (
|
||||||
|
compat_etree_fromstring,
|
||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
compat_urllib_parse_unquote,
|
compat_urllib_parse_unquote,
|
||||||
compat_urllib_request,
|
compat_urllib_request,
|
||||||
@@ -21,7 +21,9 @@ from ..utils import (
|
|||||||
bytes_to_intlist,
|
bytes_to_intlist,
|
||||||
intlist_to_bytes,
|
intlist_to_bytes,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
|
lowercase_escape,
|
||||||
remove_end,
|
remove_end,
|
||||||
|
sanitized_Request,
|
||||||
unified_strdate,
|
unified_strdate,
|
||||||
urlencode_postdata,
|
urlencode_postdata,
|
||||||
xpath_text,
|
xpath_text,
|
||||||
@@ -45,7 +47,7 @@ class CrunchyrollBaseIE(InfoExtractor):
|
|||||||
'name': username,
|
'name': username,
|
||||||
'password': password,
|
'password': password,
|
||||||
})
|
})
|
||||||
login_request = compat_urllib_request.Request(login_url, data)
|
login_request = sanitized_Request(login_url, data)
|
||||||
login_request.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
login_request.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
||||||
self._download_webpage(login_request, None, False, 'Wrong login info')
|
self._download_webpage(login_request, None, False, 'Wrong login info')
|
||||||
|
|
||||||
@@ -54,7 +56,7 @@ class CrunchyrollBaseIE(InfoExtractor):
|
|||||||
|
|
||||||
def _download_webpage(self, url_or_request, video_id, note=None, errnote=None, fatal=True, tries=1, timeout=5, encoding=None):
|
def _download_webpage(self, url_or_request, video_id, note=None, errnote=None, fatal=True, tries=1, timeout=5, encoding=None):
|
||||||
request = (url_or_request if isinstance(url_or_request, compat_urllib_request.Request)
|
request = (url_or_request if isinstance(url_or_request, compat_urllib_request.Request)
|
||||||
else compat_urllib_request.Request(url_or_request))
|
else sanitized_Request(url_or_request))
|
||||||
# Accept-Language must be set explicitly to accept any language to avoid issues
|
# Accept-Language must be set explicitly to accept any language to avoid issues
|
||||||
# similar to https://github.com/rg3/youtube-dl/issues/6797.
|
# similar to https://github.com/rg3/youtube-dl/issues/6797.
|
||||||
# Along with IP address Crunchyroll uses Accept-Language to guess whether georestriction
|
# Along with IP address Crunchyroll uses Accept-Language to guess whether georestriction
|
||||||
@@ -104,7 +106,7 @@ class CrunchyrollIE(CrunchyrollBaseIE):
|
|||||||
'id': '589804',
|
'id': '589804',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'title': 'Culture Japan Episode 1 – Rebuilding Japan after the 3.11',
|
'title': 'Culture Japan Episode 1 – Rebuilding Japan after the 3.11',
|
||||||
'description': 'md5:fe2743efedb49d279552926d0bd0cd9e',
|
'description': 'md5:2fbc01f90b87e8e9137296f37b461c12',
|
||||||
'thumbnail': 're:^https?://.*\.jpg$',
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
'uploader': 'Danny Choo Network',
|
'uploader': 'Danny Choo Network',
|
||||||
'upload_date': '20120213',
|
'upload_date': '20120213',
|
||||||
@@ -234,7 +236,7 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
|||||||
return output
|
return output
|
||||||
|
|
||||||
def _extract_subtitles(self, subtitle):
|
def _extract_subtitles(self, subtitle):
|
||||||
sub_root = xml.etree.ElementTree.fromstring(subtitle)
|
sub_root = compat_etree_fromstring(subtitle)
|
||||||
return [{
|
return [{
|
||||||
'ext': 'srt',
|
'ext': 'srt',
|
||||||
'data': self._convert_subtitles_to_srt(sub_root),
|
'data': self._convert_subtitles_to_srt(sub_root),
|
||||||
@@ -287,11 +289,15 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
|||||||
if 'To view this, please log in to verify you are 18 or older.' in webpage:
|
if 'To view this, please log in to verify you are 18 or older.' in webpage:
|
||||||
self.raise_login_required()
|
self.raise_login_required()
|
||||||
|
|
||||||
video_title = self._html_search_regex(r'<h1[^>]*>(.+?)</h1>', webpage, 'video_title', flags=re.DOTALL)
|
video_title = self._html_search_regex(
|
||||||
|
r'(?s)<h1[^>]*>((?:(?!<h1).)*?<span[^>]+itemprop=["\']title["\'][^>]*>(?:(?!<h1).)+?)</h1>',
|
||||||
|
webpage, 'video_title')
|
||||||
video_title = re.sub(r' {2,}', ' ', video_title)
|
video_title = re.sub(r' {2,}', ' ', video_title)
|
||||||
video_description = self._html_search_regex(r'"description":"([^"]+)', webpage, 'video_description', default='')
|
video_description = self._html_search_regex(
|
||||||
if not video_description:
|
r'<script[^>]*>\s*.+?\[media_id=%s\].+?"description"\s*:\s*"([^"]+)' % video_id,
|
||||||
video_description = None
|
webpage, 'description', default=None)
|
||||||
|
if video_description:
|
||||||
|
video_description = lowercase_escape(video_description.replace(r'\r\n', '\n'))
|
||||||
video_upload_date = self._html_search_regex(
|
video_upload_date = self._html_search_regex(
|
||||||
[r'<div>Availability for free users:(.+?)</div>', r'<div>[^<>]+<span>\s*(.+?\d{4})\s*</span></div>'],
|
[r'<div>Availability for free users:(.+?)</div>', r'<div>[^<>]+<span>\s*(.+?\d{4})\s*</span></div>'],
|
||||||
webpage, 'video_upload_date', fatal=False, flags=re.DOTALL)
|
webpage, 'video_upload_date', fatal=False, flags=re.DOTALL)
|
||||||
@@ -302,7 +308,7 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
|||||||
'video_uploader', fatal=False)
|
'video_uploader', fatal=False)
|
||||||
|
|
||||||
playerdata_url = compat_urllib_parse_unquote(self._html_search_regex(r'"config_url":"([^"]+)', webpage, 'playerdata_url'))
|
playerdata_url = compat_urllib_parse_unquote(self._html_search_regex(r'"config_url":"([^"]+)', webpage, 'playerdata_url'))
|
||||||
playerdata_req = compat_urllib_request.Request(playerdata_url)
|
playerdata_req = sanitized_Request(playerdata_url)
|
||||||
playerdata_req.data = compat_urllib_parse.urlencode({'current_page': webpage_url})
|
playerdata_req.data = compat_urllib_parse.urlencode({'current_page': webpage_url})
|
||||||
playerdata_req.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
playerdata_req.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
||||||
playerdata = self._download_webpage(playerdata_req, video_id, note='Downloading media info')
|
playerdata = self._download_webpage(playerdata_req, video_id, note='Downloading media info')
|
||||||
@@ -314,7 +320,7 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
|||||||
for fmt in re.findall(r'showmedia\.([0-9]{3,4})p', webpage):
|
for fmt in re.findall(r'showmedia\.([0-9]{3,4})p', webpage):
|
||||||
stream_quality, stream_format = self._FORMAT_IDS[fmt]
|
stream_quality, stream_format = self._FORMAT_IDS[fmt]
|
||||||
video_format = fmt + 'p'
|
video_format = fmt + 'p'
|
||||||
streamdata_req = compat_urllib_request.Request(
|
streamdata_req = sanitized_Request(
|
||||||
'http://www.crunchyroll.com/xml/?req=RpcApiVideoPlayer_GetStandardConfig&media_id=%s&video_format=%s&video_quality=%s'
|
'http://www.crunchyroll.com/xml/?req=RpcApiVideoPlayer_GetStandardConfig&media_id=%s&video_format=%s&video_quality=%s'
|
||||||
% (stream_id, stream_format, stream_quality),
|
% (stream_id, stream_format, stream_quality),
|
||||||
compat_urllib_parse.urlencode({'current_page': url}).encode('utf-8'))
|
compat_urllib_parse.urlencode({'current_page': url}).encode('utf-8'))
|
||||||
|
@@ -9,6 +9,7 @@ from ..utils import (
|
|||||||
find_xpath_attr,
|
find_xpath_attr,
|
||||||
smuggle_url,
|
smuggle_url,
|
||||||
determine_ext,
|
determine_ext,
|
||||||
|
ExtractorError,
|
||||||
)
|
)
|
||||||
from .senateisvp import SenateISVPIE
|
from .senateisvp import SenateISVPIE
|
||||||
|
|
||||||
@@ -18,33 +19,32 @@ class CSpanIE(InfoExtractor):
|
|||||||
IE_DESC = 'C-SPAN'
|
IE_DESC = 'C-SPAN'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.c-span.org/video/?313572-1/HolderonV',
|
'url': 'http://www.c-span.org/video/?313572-1/HolderonV',
|
||||||
'md5': '8e44ce11f0f725527daccc453f553eb0',
|
'md5': '94b29a4f131ff03d23471dd6f60b6a1d',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '315139',
|
'id': '315139',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Attorney General Eric Holder on Voting Rights Act Decision',
|
'title': 'Attorney General Eric Holder on Voting Rights Act Decision',
|
||||||
'description': 'Attorney General Eric Holder spoke to reporters following the Supreme Court decision in Shelby County v. Holder in which the court ruled that the preclearance provisions of the Voting Rights Act could not be enforced until Congress established new guidelines for review.',
|
'description': 'Attorney General Eric Holder speaks to reporters following the Supreme Court decision in [Shelby County v. Holder], in which the court ruled that the preclearance provisions of the Voting Rights Act could not be enforced.',
|
||||||
},
|
},
|
||||||
'skip': 'Regularly fails on travis, for unknown reasons',
|
'skip': 'Regularly fails on travis, for unknown reasons',
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.c-span.org/video/?c4486943/cspan-international-health-care-models',
|
'url': 'http://www.c-span.org/video/?c4486943/cspan-international-health-care-models',
|
||||||
# For whatever reason, the served video alternates between
|
'md5': '8e5fbfabe6ad0f89f3012a7943c1287b',
|
||||||
# two different ones
|
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '340723',
|
'id': 'c4486943',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'International Health Care Models',
|
'title': 'CSPAN - International Health Care Models',
|
||||||
'description': 'md5:7a985a2d595dba00af3d9c9f0783c967',
|
'description': 'md5:7a985a2d595dba00af3d9c9f0783c967',
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.c-span.org/video/?318608-1/gm-ignition-switch-recall',
|
'url': 'http://www.c-span.org/video/?318608-1/gm-ignition-switch-recall',
|
||||||
'md5': '446562a736c6bf97118e389433ed88d4',
|
'md5': '2ae5051559169baadba13fc35345ae74',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '342759',
|
'id': '342759',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'General Motors Ignition Switch Recall',
|
'title': 'General Motors Ignition Switch Recall',
|
||||||
'duration': 14848,
|
'duration': 14848,
|
||||||
'description': 'md5:70c7c3b8fa63fa60d42772440596034c'
|
'description': 'md5:118081aedd24bf1d3b68b3803344e7f3'
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
# Video from senate.gov
|
# Video from senate.gov
|
||||||
@@ -57,67 +57,77 @@ class CSpanIE(InfoExtractor):
|
|||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
page_id = mobj.group('id')
|
webpage = self._download_webpage(url, video_id)
|
||||||
webpage = self._download_webpage(url, page_id)
|
matches = re.search(r'data-(prog|clip)id=\'([0-9]+)\'', webpage)
|
||||||
video_id = self._search_regex(r'progid=\'?([0-9]+)\'?>', webpage, 'video id')
|
if matches:
|
||||||
|
video_type, video_id = matches.groups()
|
||||||
|
if video_type == 'prog':
|
||||||
|
video_type = 'program'
|
||||||
|
else:
|
||||||
|
senate_isvp_url = SenateISVPIE._search_iframe_url(webpage)
|
||||||
|
if senate_isvp_url:
|
||||||
|
title = self._og_search_title(webpage)
|
||||||
|
surl = smuggle_url(senate_isvp_url, {'force_title': title})
|
||||||
|
return self.url_result(surl, 'SenateISVP', video_id, title)
|
||||||
|
|
||||||
description = self._html_search_regex(
|
def get_text_attr(d, attr):
|
||||||
[
|
return d.get(attr, {}).get('#text')
|
||||||
# The full description
|
|
||||||
r'<div class=\'expandable\'>(.*?)<a href=\'#\'',
|
|
||||||
# If the description is small enough the other div is not
|
|
||||||
# present, otherwise this is a stripped version
|
|
||||||
r'<p class=\'initial\'>(.*?)</p>'
|
|
||||||
],
|
|
||||||
webpage, 'description', flags=re.DOTALL, default=None)
|
|
||||||
|
|
||||||
info_url = 'http://c-spanvideo.org/videoLibrary/assets/player/ajax-player.php?os=android&html5=program&id=' + video_id
|
data = self._download_json(
|
||||||
data = self._download_json(info_url, video_id)
|
'http://www.c-span.org/assets/player/ajax-player.php?os=android&html5=%s&id=%s' % (video_type, video_id),
|
||||||
|
video_id)['video']
|
||||||
|
if data['@status'] != 'Success':
|
||||||
|
raise ExtractorError('%s said: %s' % (self.IE_NAME, get_text_attr(data, 'error')), expected=True)
|
||||||
|
|
||||||
doc = self._download_xml(
|
doc = self._download_xml(
|
||||||
'http://www.c-span.org/common/services/flashXml.php?programid=' + video_id,
|
'http://www.c-span.org/common/services/flashXml.php?%sid=%s' % (video_type, video_id),
|
||||||
video_id)
|
video_id)
|
||||||
|
|
||||||
|
description = self._html_search_meta('description', webpage)
|
||||||
|
|
||||||
title = find_xpath_attr(doc, './/string', 'name', 'title').text
|
title = find_xpath_attr(doc, './/string', 'name', 'title').text
|
||||||
thumbnail = find_xpath_attr(doc, './/string', 'name', 'poster').text
|
thumbnail = find_xpath_attr(doc, './/string', 'name', 'poster').text
|
||||||
|
|
||||||
senate_isvp_url = SenateISVPIE._search_iframe_url(webpage)
|
files = data['files']
|
||||||
if senate_isvp_url:
|
capfile = get_text_attr(data, 'capfile')
|
||||||
surl = smuggle_url(senate_isvp_url, {'force_title': title})
|
|
||||||
return self.url_result(surl, 'SenateISVP', video_id, title)
|
|
||||||
|
|
||||||
files = data['video']['files']
|
entries = []
|
||||||
try:
|
for partnum, f in enumerate(files):
|
||||||
capfile = data['video']['capfile']['#text']
|
formats = []
|
||||||
except KeyError:
|
for quality in f['qualities']:
|
||||||
capfile = None
|
formats.append({
|
||||||
|
'format_id': '%s-%sp' % (get_text_attr(quality, 'bitrate'), get_text_attr(quality, 'height')),
|
||||||
entries = [{
|
'url': unescapeHTML(get_text_attr(quality, 'file')),
|
||||||
'id': '%s_%d' % (video_id, partnum + 1),
|
'height': int_or_none(get_text_attr(quality, 'height')),
|
||||||
'title': (
|
'tbr': int_or_none(get_text_attr(quality, 'bitrate')),
|
||||||
title if len(files) == 1 else
|
})
|
||||||
'%s part %d' % (title, partnum + 1)),
|
self._sort_formats(formats)
|
||||||
'url': unescapeHTML(f['path']['#text']),
|
entries.append({
|
||||||
'description': description,
|
'id': '%s_%d' % (video_id, partnum + 1),
|
||||||
'thumbnail': thumbnail,
|
'title': (
|
||||||
'duration': int_or_none(f.get('length', {}).get('#text')),
|
title if len(files) == 1 else
|
||||||
'subtitles': {
|
'%s part %d' % (title, partnum + 1)),
|
||||||
'en': [{
|
'formats': formats,
|
||||||
'url': capfile,
|
'description': description,
|
||||||
'ext': determine_ext(capfile, 'dfxp')
|
'thumbnail': thumbnail,
|
||||||
}],
|
'duration': int_or_none(get_text_attr(f, 'length')),
|
||||||
} if capfile else None,
|
'subtitles': {
|
||||||
} for partnum, f in enumerate(files)]
|
'en': [{
|
||||||
|
'url': capfile,
|
||||||
|
'ext': determine_ext(capfile, 'dfxp')
|
||||||
|
}],
|
||||||
|
} if capfile else None,
|
||||||
|
})
|
||||||
|
|
||||||
if len(entries) == 1:
|
if len(entries) == 1:
|
||||||
entry = dict(entries[0])
|
entry = dict(entries[0])
|
||||||
entry['id'] = video_id
|
entry['id'] = 'c' + video_id if video_type == 'clip' else video_id
|
||||||
return entry
|
return entry
|
||||||
else:
|
else:
|
||||||
return {
|
return {
|
||||||
'_type': 'playlist',
|
'_type': 'playlist',
|
||||||
'entries': entries,
|
'entries': entries,
|
||||||
'title': title,
|
'title': title,
|
||||||
'id': video_id,
|
'id': 'c' + video_id if video_type == 'clip' else video_id,
|
||||||
}
|
}
|
||||||
|
@@ -7,15 +7,13 @@ import itertools
|
|||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
|
||||||
from ..compat import (
|
from ..compat import compat_str
|
||||||
compat_str,
|
|
||||||
compat_urllib_request,
|
|
||||||
)
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
determine_ext,
|
determine_ext,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
parse_iso8601,
|
parse_iso8601,
|
||||||
|
sanitized_Request,
|
||||||
str_to_int,
|
str_to_int,
|
||||||
unescapeHTML,
|
unescapeHTML,
|
||||||
)
|
)
|
||||||
@@ -25,7 +23,7 @@ class DailymotionBaseInfoExtractor(InfoExtractor):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def _build_request(url):
|
def _build_request(url):
|
||||||
"""Build a request with the family filter disabled"""
|
"""Build a request with the family filter disabled"""
|
||||||
request = compat_urllib_request.Request(url)
|
request = sanitized_Request(url)
|
||||||
request.add_header('Cookie', 'family_filter=off; ff=off')
|
request.add_header('Cookie', 'family_filter=off; ff=off')
|
||||||
return request
|
return request
|
||||||
|
|
||||||
@@ -101,6 +99,11 @@ class DailymotionIE(DailymotionBaseInfoExtractor):
|
|||||||
{
|
{
|
||||||
'url': 'http://www.dailymotion.com/video/xhza0o',
|
'url': 'http://www.dailymotion.com/video/xhza0o',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
},
|
||||||
|
# with subtitles
|
||||||
|
{
|
||||||
|
'url': 'http://www.dailymotion.com/video/x20su5f_the-power-of-nightmares-1-the-rise-of-the-politics-of-fear-bbc-2004_news',
|
||||||
|
'only_matching': True,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -124,7 +127,9 @@ class DailymotionIE(DailymotionBaseInfoExtractor):
|
|||||||
webpage, 'comment count', fatal=False))
|
webpage, 'comment count', fatal=False))
|
||||||
|
|
||||||
player_v5 = self._search_regex(
|
player_v5 = self._search_regex(
|
||||||
[r'buildPlayer\(({.+?})\);', r'playerV5\s*=\s*dmp\.create\([^,]+?,\s*({.+?})\);'],
|
[r'buildPlayer\(({.+?})\);\n', # See https://github.com/rg3/youtube-dl/issues/7826
|
||||||
|
r'playerV5\s*=\s*dmp\.create\([^,]+?,\s*({.+?})\);',
|
||||||
|
r'buildPlayer\(({.+?})\);'],
|
||||||
webpage, 'player v5', default=None)
|
webpage, 'player v5', default=None)
|
||||||
if player_v5:
|
if player_v5:
|
||||||
player = self._parse_json(player_v5, video_id)
|
player = self._parse_json(player_v5, video_id)
|
||||||
@@ -141,9 +146,17 @@ 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
|
||||||
if type_ == 'application/x-mpegURL' or determine_ext(media_url) == 'm3u8':
|
ext = determine_ext(media_url)
|
||||||
formats.extend(self._extract_m3u8_formats(
|
if type_ == 'application/x-mpegURL' or ext == 'm3u8':
|
||||||
media_url, video_id, 'mp4', m3u8_id='hls'))
|
m3u8_formats = self._extract_m3u8_formats(
|
||||||
|
media_url, video_id, 'mp4', m3u8_id='hls', fatal=False)
|
||||||
|
if m3u8_formats:
|
||||||
|
formats.extend(m3u8_formats)
|
||||||
|
elif type_ == 'application/f4m' or ext == 'f4m':
|
||||||
|
f4m_formats = self._extract_f4m_formats(
|
||||||
|
media_url, video_id, preference=-1, f4m_id='hds', fatal=False)
|
||||||
|
if f4m_formats:
|
||||||
|
formats.extend(f4m_formats)
|
||||||
else:
|
else:
|
||||||
f = {
|
f = {
|
||||||
'url': media_url,
|
'url': media_url,
|
||||||
@@ -166,11 +179,13 @@ class DailymotionIE(DailymotionBaseInfoExtractor):
|
|||||||
uploader_id = metadata.get('owner', {}).get('id')
|
uploader_id = metadata.get('owner', {}).get('id')
|
||||||
|
|
||||||
subtitles = {}
|
subtitles = {}
|
||||||
for subtitle_lang, subtitle in metadata.get('subtitles', {}).get('data', {}).items():
|
subtitles_data = metadata.get('subtitles', {}).get('data', {})
|
||||||
subtitles[subtitle_lang] = [{
|
if subtitles_data and isinstance(subtitles_data, dict):
|
||||||
'ext': determine_ext(subtitle_url),
|
for subtitle_lang, subtitle in subtitles_data.items():
|
||||||
'url': subtitle_url,
|
subtitles[subtitle_lang] = [{
|
||||||
} for subtitle_url in subtitle.get('urls', [])]
|
'ext': determine_ext(subtitle_url),
|
||||||
|
'url': subtitle_url,
|
||||||
|
} for subtitle_url in subtitle.get('urls', [])]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
|
@@ -13,8 +13,8 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class DBTVIE(InfoExtractor):
|
class DBTVIE(InfoExtractor):
|
||||||
_VALID_URL = r'http://dbtv\.no/(?P<id>[0-9]+)#(?P<display_id>.+)'
|
_VALID_URL = r'https?://(?:www\.)?dbtv\.no/(?:(?:lazyplayer|player)/)?(?P<id>[0-9]+)(?:#(?P<display_id>.+))?'
|
||||||
_TEST = {
|
_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': 'b89953ed25dacb6edb3ef6c6f430f8bc',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@@ -30,12 +30,18 @@ class DBTVIE(InfoExtractor):
|
|||||||
'view_count': int,
|
'view_count': int,
|
||||||
'categories': list,
|
'categories': list,
|
||||||
}
|
}
|
||||||
}
|
}, {
|
||||||
|
'url': 'http://dbtv.no/3649835190001',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.dbtv.no/lazyplayer/4631135248001',
|
||||||
|
'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
|
||||||
|
|
||||||
data = self._download_json(
|
data = self._download_json(
|
||||||
'http://api.dbtv.no/discovery/%s' % video_id, display_id)
|
'http://api.dbtv.no/discovery/%s' % video_id, display_id)
|
||||||
|
@@ -2,13 +2,11 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import compat_urllib_parse
|
||||||
compat_urllib_parse,
|
|
||||||
compat_urllib_request,
|
|
||||||
)
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
int_or_none,
|
int_or_none,
|
||||||
parse_iso8601,
|
parse_iso8601,
|
||||||
|
sanitized_Request,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -36,7 +34,7 @@ class DCNIE(InfoExtractor):
|
|||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
request = compat_urllib_request.Request(
|
request = sanitized_Request(
|
||||||
'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'})
|
headers={'Origin': 'http://www.dcndigital.ae'})
|
||||||
|
|
||||||
|
88
youtube_dl/extractor/democracynow.py
Normal file
88
youtube_dl/extractor/democracynow.py
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..compat import compat_urlparse
|
||||||
|
from ..utils import (
|
||||||
|
url_basename,
|
||||||
|
remove_start,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DemocracynowIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?democracynow.org/(?P<id>[^\?]*)'
|
||||||
|
IE_NAME = 'democracynow'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://www.democracynow.org/shows/2015/7/3',
|
||||||
|
'md5': 'fbb8fe3d7a56a5e12431ce2f9b2fab0d',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '2015-0703-001',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'July 03, 2015 - Democracy Now!',
|
||||||
|
'description': 'A daily independent global news hour with Amy Goodman & Juan González "What to the Slave is 4th of July?": James Earl Jones Reads Frederick Douglass\u2019 Historic Speech : "This Flag Comes Down Today": Bree Newsome Scales SC Capitol Flagpole, Takes Down Confederate Flag : "We Shall Overcome": Remembering Folk Icon, Activist Pete Seeger in His Own Words & Songs',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.democracynow.org/2015/7/3/this_flag_comes_down_today_bree',
|
||||||
|
'md5': 'fbb8fe3d7a56a5e12431ce2f9b2fab0d',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '2015-0703-001',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': '"This Flag Comes Down Today": Bree Newsome Scales SC Capitol Flagpole, Takes Down Confederate Flag',
|
||||||
|
'description': 'md5:4d2bc4f0d29f5553c2210a4bc7761a21',
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
display_id = self._match_id(url)
|
||||||
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
description = self._og_search_description(webpage)
|
||||||
|
|
||||||
|
json_data = self._parse_json(self._search_regex(
|
||||||
|
r'<script[^>]+type="text/json"[^>]*>\s*({[^>]+})', webpage, 'json'),
|
||||||
|
display_id)
|
||||||
|
video_id = None
|
||||||
|
formats = []
|
||||||
|
|
||||||
|
default_lang = 'en'
|
||||||
|
|
||||||
|
subtitles = {}
|
||||||
|
|
||||||
|
def add_subtitle_item(lang, info_dict):
|
||||||
|
if lang not in subtitles:
|
||||||
|
subtitles[lang] = []
|
||||||
|
subtitles[lang].append(info_dict)
|
||||||
|
|
||||||
|
# chapter_file are not subtitles
|
||||||
|
if 'caption_file' in json_data:
|
||||||
|
add_subtitle_item(default_lang, {
|
||||||
|
'url': compat_urlparse.urljoin(url, json_data['caption_file']),
|
||||||
|
})
|
||||||
|
|
||||||
|
for subtitle_item in json_data.get('captions', []):
|
||||||
|
lang = subtitle_item.get('language', '').lower() or default_lang
|
||||||
|
add_subtitle_item(lang, {
|
||||||
|
'url': compat_urlparse.urljoin(url, subtitle_item['url']),
|
||||||
|
})
|
||||||
|
|
||||||
|
for key in ('file', 'audio', 'video'):
|
||||||
|
media_url = json_data.get(key, '')
|
||||||
|
if not media_url:
|
||||||
|
continue
|
||||||
|
media_url = re.sub(r'\?.*', '', compat_urlparse.urljoin(url, media_url))
|
||||||
|
video_id = video_id or remove_start(os.path.splitext(url_basename(media_url))[0], 'dn')
|
||||||
|
formats.append({
|
||||||
|
'url': media_url,
|
||||||
|
})
|
||||||
|
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id or display_id,
|
||||||
|
'title': json_data['title'],
|
||||||
|
'description': description,
|
||||||
|
'subtitles': subtitles,
|
||||||
|
'formats': formats,
|
||||||
|
}
|
51
youtube_dl/extractor/dplay.py
Normal file
51
youtube_dl/extractor/dplay.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# encoding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import int_or_none
|
||||||
|
|
||||||
|
|
||||||
|
class DPlayIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'http://www\.dplay\.se/[^/]+/(?P<id>[^/?#]+)'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.dplay.se/nugammalt-77-handelser-som-format-sverige/season-1-svensken-lar-sig-njuta-av-livet/',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '3172',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'display_id': 'season-1-svensken-lar-sig-njuta-av-livet',
|
||||||
|
'title': 'Svensken lär sig njuta av livet',
|
||||||
|
'duration': 2650,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
display_id = self._match_id(url)
|
||||||
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
video_id = self._search_regex(
|
||||||
|
r'data-video-id="(\d+)"', webpage, 'video id')
|
||||||
|
|
||||||
|
info = self._download_json(
|
||||||
|
'http://www.dplay.se/api/v2/ajax/videos?video_id=' + video_id,
|
||||||
|
video_id)['data'][0]
|
||||||
|
|
||||||
|
self._set_cookie(
|
||||||
|
'secure.dplay.se', 'dsc-geo',
|
||||||
|
'{"countryCode":"NL","expiry":%d}' % ((time.time() + 20 * 60) * 1000))
|
||||||
|
# TODO: consider adding support for 'stream_type=hds', it seems to
|
||||||
|
# require setting some cookies
|
||||||
|
manifest_url = self._download_json(
|
||||||
|
'https://secure.dplay.se/secure/api/v2/user/authorization/stream/%s?stream_type=hls' % video_id,
|
||||||
|
video_id, 'Getting manifest url for hls stream')['hls']
|
||||||
|
formats = self._extract_m3u8_formats(
|
||||||
|
manifest_url, video_id, ext='mp4', entry_protocol='m3u8_native')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'display_id': display_id,
|
||||||
|
'title': info['title'],
|
||||||
|
'formats': formats,
|
||||||
|
'duration': int_or_none(info.get('video_metadata_length'), scale=1000),
|
||||||
|
}
|
@@ -7,7 +7,6 @@ from .common import InfoExtractor
|
|||||||
from ..compat import (
|
from ..compat import (
|
||||||
compat_HTTPError,
|
compat_HTTPError,
|
||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
compat_urllib_request,
|
|
||||||
compat_urlparse,
|
compat_urlparse,
|
||||||
)
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
@@ -16,6 +15,7 @@ from ..utils import (
|
|||||||
determine_ext,
|
determine_ext,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
parse_iso8601,
|
parse_iso8601,
|
||||||
|
sanitized_Request,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ class DramaFeverBaseIE(InfoExtractor):
|
|||||||
'password': password,
|
'password': password,
|
||||||
}
|
}
|
||||||
|
|
||||||
request = compat_urllib_request.Request(
|
request = sanitized_Request(
|
||||||
self._LOGIN_URL, compat_urllib_parse.urlencode(login_form).encode('utf-8'))
|
self._LOGIN_URL, compat_urllib_parse.urlencode(login_form).encode('utf-8'))
|
||||||
response = self._download_webpage(
|
response = self._download_webpage(
|
||||||
request, None, 'Logging in as %s' % username)
|
request, None, 'Logging in as %s' % username)
|
||||||
|
@@ -2,14 +2,17 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import compat_urllib_request
|
from ..utils import (
|
||||||
from ..utils import qualities
|
qualities,
|
||||||
|
sanitized_Request,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class DumpertIE(InfoExtractor):
|
class DumpertIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?dumpert\.nl/(?:mediabase|embed)/(?P<id>[0-9]+/[0-9a-zA-Z]+)'
|
_VALID_URL = r'(?P<protocol>https?)://(?:www\.)?dumpert\.nl/(?:mediabase|embed)/(?P<id>[0-9]+/[0-9a-zA-Z]+)'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.dumpert.nl/mediabase/6646981/951bc60f/',
|
'url': 'http://www.dumpert.nl/mediabase/6646981/951bc60f/',
|
||||||
'md5': '1b9318d7d5054e7dcb9dc7654f21d643',
|
'md5': '1b9318d7d5054e7dcb9dc7654f21d643',
|
||||||
@@ -26,10 +29,12 @@ class DumpertIE(InfoExtractor):
|
|||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
protocol = mobj.group('protocol')
|
||||||
|
|
||||||
url = 'https://www.dumpert.nl/mediabase/' + video_id
|
url = '%s://www.dumpert.nl/mediabase/%s' % (protocol, video_id)
|
||||||
req = compat_urllib_request.Request(url)
|
req = sanitized_Request(url)
|
||||||
req.add_header('Cookie', 'nsfw=1; cpc=10')
|
req.add_header('Cookie', 'nsfw=1; cpc=10')
|
||||||
webpage = self._download_webpage(req, video_id)
|
webpage = self._download_webpage(req, video_id)
|
||||||
|
|
||||||
|
@@ -1,39 +1,92 @@
|
|||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from .brightcove import BrightcoveIE
|
from ..utils import (
|
||||||
from ..utils import ExtractorError
|
float_or_none,
|
||||||
|
int_or_none,
|
||||||
|
parse_iso8601,
|
||||||
|
sanitized_Request,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class EitbIE(InfoExtractor):
|
class EitbIE(InfoExtractor):
|
||||||
IE_NAME = 'eitb.tv'
|
IE_NAME = 'eitb.tv'
|
||||||
_VALID_URL = r'https?://www\.eitb\.tv/(eu/bideoa|es/video)/[^/]+/(?P<playlist_id>\d+)/(?P<chapter_id>\d+)'
|
_VALID_URL = r'https?://(?:www\.)?eitb\.tv/(?:eu/bideoa|es/video)/[^/]+/\d+/(?P<id>\d+)'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'add_ie': ['Brightcove'],
|
'url': 'http://www.eitb.tv/es/video/60-minutos-60-minutos-2013-2014/4104995148001/4090227752001/lasa-y-zabala-30-anos/',
|
||||||
'url': 'http://www.eitb.tv/es/video/60-minutos-60-minutos-2013-2014/2677100210001/2743577154001/lasa-y-zabala-30-anos/',
|
|
||||||
'md5': 'edf4436247185adee3ea18ce64c47998',
|
'md5': 'edf4436247185adee3ea18ce64c47998',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '2743577154001',
|
'id': '4090227752001',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': '60 minutos (Lasa y Zabala, 30 años)',
|
'title': '60 minutos (Lasa y Zabala, 30 años)',
|
||||||
# All videos from eitb has this description in the brightcove info
|
'description': 'Programa de reportajes de actualidad.',
|
||||||
'description': '.',
|
'duration': 3996.76,
|
||||||
'uploader': 'Euskal Telebista',
|
'timestamp': 1381789200,
|
||||||
|
'upload_date': '20131014',
|
||||||
|
'tags': list,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
chapter_id = mobj.group('chapter_id')
|
|
||||||
webpage = self._download_webpage(url, chapter_id)
|
video = self._download_json(
|
||||||
bc_url = BrightcoveIE._extract_brightcove_url(webpage)
|
'http://mam.eitb.eus/mam/REST/ServiceMultiweb/Video/MULTIWEBTV/%s/' % video_id,
|
||||||
if bc_url is None:
|
video_id, 'Downloading video JSON')
|
||||||
raise ExtractorError('Could not extract the Brightcove url')
|
|
||||||
# The BrightcoveExperience object doesn't contain the video id, we set
|
media = video['web_media'][0]
|
||||||
# it manually
|
|
||||||
bc_url += '&%40videoPlayer={0}'.format(chapter_id)
|
formats = []
|
||||||
return self.url_result(bc_url, BrightcoveIE.ie_key())
|
for rendition in media['RENDITIONS']:
|
||||||
|
video_url = rendition.get('PMD_URL')
|
||||||
|
if not video_url:
|
||||||
|
continue
|
||||||
|
tbr = float_or_none(rendition.get('ENCODING_RATE'), 1000)
|
||||||
|
format_id = 'http'
|
||||||
|
if tbr:
|
||||||
|
format_id += '-%d' % int(tbr)
|
||||||
|
formats.append({
|
||||||
|
'url': rendition['PMD_URL'],
|
||||||
|
'format_id': format_id,
|
||||||
|
'width': int_or_none(rendition.get('FRAME_WIDTH')),
|
||||||
|
'height': int_or_none(rendition.get('FRAME_HEIGHT')),
|
||||||
|
'tbr': tbr,
|
||||||
|
})
|
||||||
|
|
||||||
|
hls_url = media.get('HLS_SURL')
|
||||||
|
if hls_url:
|
||||||
|
request = sanitized_Request(
|
||||||
|
'http://mam.eitb.eus/mam/REST/ServiceMultiweb/DomainRestrictedSecurity/TokenAuth/',
|
||||||
|
headers={'Referer': url})
|
||||||
|
token_data = self._download_json(
|
||||||
|
request, video_id, 'Downloading auth token', fatal=False)
|
||||||
|
if token_data:
|
||||||
|
token = token_data.get('token')
|
||||||
|
if token:
|
||||||
|
m3u8_formats = self._extract_m3u8_formats(
|
||||||
|
'%s?hdnts=%s' % (hls_url, token), video_id, m3u8_id='hls', fatal=False)
|
||||||
|
if m3u8_formats:
|
||||||
|
formats.extend(m3u8_formats)
|
||||||
|
|
||||||
|
hds_url = media.get('HDS_SURL')
|
||||||
|
if hds_url:
|
||||||
|
f4m_formats = self._extract_f4m_formats(
|
||||||
|
'%s?hdcore=3.7.0' % hds_url.replace('euskalsvod', 'euskalvod'),
|
||||||
|
video_id, f4m_id='hds', fatal=False)
|
||||||
|
if f4m_formats:
|
||||||
|
formats.extend(f4m_formats)
|
||||||
|
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': media.get('NAME_ES') or media.get('name') or media['NAME_EU'],
|
||||||
|
'description': media.get('SHORT_DESC_ES') or video.get('desc_group') or media.get('SHORT_DESC_EU'),
|
||||||
|
'thumbnail': media.get('STILL_URL') or media.get('THUMBNAIL_URL'),
|
||||||
|
'duration': float_or_none(media.get('LENGTH'), 1000),
|
||||||
|
'timestamp': parse_iso8601(media.get('BROADCST_DATE'), ' '),
|
||||||
|
'tags': media.get('TAGS'),
|
||||||
|
'formats': formats,
|
||||||
|
}
|
||||||
|
@@ -3,13 +3,12 @@ from __future__ import unicode_literals
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import compat_urllib_request
|
|
||||||
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
determine_ext,
|
determine_ext,
|
||||||
clean_html,
|
clean_html,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
float_or_none,
|
float_or_none,
|
||||||
|
sanitized_Request,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -75,7 +74,7 @@ class EscapistIE(InfoExtractor):
|
|||||||
video_id = ims_video['videoID']
|
video_id = ims_video['videoID']
|
||||||
key = ims_video['hash']
|
key = ims_video['hash']
|
||||||
|
|
||||||
config_req = compat_urllib_request.Request(
|
config_req = sanitized_Request(
|
||||||
'http://www.escapistmagazine.com/videos/'
|
'http://www.escapistmagazine.com/videos/'
|
||||||
'vidconfig.php?videoID=%s&hash=%s' % (video_id, key))
|
'vidconfig.php?videoID=%s&hash=%s' % (video_id, key))
|
||||||
config_req.add_header('Referer', url)
|
config_req.add_header('Referer', url)
|
||||||
|
@@ -3,11 +3,9 @@ from __future__ import unicode_literals
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
|
||||||
compat_urllib_request,
|
|
||||||
)
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
|
sanitized_Request,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -42,7 +40,7 @@ class EveryonesMixtapeIE(InfoExtractor):
|
|||||||
playlist_id = mobj.group('id')
|
playlist_id = mobj.group('id')
|
||||||
|
|
||||||
pllist_url = 'http://everyonesmixtape.com/mixtape.php?a=getMixes&u=-1&linked=%s&explore=' % playlist_id
|
pllist_url = 'http://everyonesmixtape.com/mixtape.php?a=getMixes&u=-1&linked=%s&explore=' % playlist_id
|
||||||
pllist_req = compat_urllib_request.Request(pllist_url)
|
pllist_req = sanitized_Request(pllist_url)
|
||||||
pllist_req.add_header('X-Requested-With', 'XMLHttpRequest')
|
pllist_req.add_header('X-Requested-With', 'XMLHttpRequest')
|
||||||
|
|
||||||
playlist_list = self._download_json(
|
playlist_list = self._download_json(
|
||||||
@@ -55,7 +53,7 @@ class EveryonesMixtapeIE(InfoExtractor):
|
|||||||
raise ExtractorError('Playlist id not found')
|
raise ExtractorError('Playlist id not found')
|
||||||
|
|
||||||
pl_url = 'http://everyonesmixtape.com/mixtape.php?a=getMix&id=%s&userId=null&code=' % playlist_no
|
pl_url = 'http://everyonesmixtape.com/mixtape.php?a=getMix&id=%s&userId=null&code=' % playlist_no
|
||||||
pl_req = compat_urllib_request.Request(pl_url)
|
pl_req = sanitized_Request(pl_url)
|
||||||
pl_req.add_header('X-Requested-With', 'XMLHttpRequest')
|
pl_req.add_header('X-Requested-With', 'XMLHttpRequest')
|
||||||
playlist = self._download_json(
|
playlist = self._download_json(
|
||||||
pl_req, playlist_id, note='Downloading playlist info')
|
pl_req, playlist_id, note='Downloading playlist info')
|
||||||
|
@@ -3,23 +3,20 @@ from __future__ import unicode_literals
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
|
||||||
compat_parse_qs,
|
|
||||||
compat_urllib_request,
|
|
||||||
)
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
qualities,
|
int_or_none,
|
||||||
|
sanitized_Request,
|
||||||
str_to_int,
|
str_to_int,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ExtremeTubeIE(InfoExtractor):
|
class ExtremeTubeIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?(?P<url>extremetube\.com/.*?video/.+?(?P<id>[0-9]+))(?:[/?&]|$)'
|
_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': '344d0c6d50e2f16b06e49ca011d8ac69',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '652431',
|
'id': 'music-video-14-british-euro-brit-european-cumshots-swallow-652431',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Music Video 14 british euro brit european cumshots swallow',
|
'title': 'Music Video 14 british euro brit european cumshots swallow',
|
||||||
'uploader': 'unknown',
|
'uploader': 'unknown',
|
||||||
@@ -29,14 +26,18 @@ class ExtremeTubeIE(InfoExtractor):
|
|||||||
}, {
|
}, {
|
||||||
'url': 'http://www.extremetube.com/gay/video/abcde-1234',
|
'url': 'http://www.extremetube.com/gay/video/abcde-1234',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.extremetube.com/video/latina-slut-fucked-by-fat-black-dick',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.extremetube.com/video/652431',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = mobj.group('id')
|
|
||||||
url = 'http://www.' + mobj.group('url')
|
|
||||||
|
|
||||||
req = compat_urllib_request.Request(url)
|
req = sanitized_Request(url)
|
||||||
req.add_header('Cookie', 'age_verified=1')
|
req.add_header('Cookie', 'age_verified=1')
|
||||||
webpage = self._download_webpage(req, video_id)
|
webpage = self._download_webpage(req, video_id)
|
||||||
|
|
||||||
@@ -49,20 +50,36 @@ class ExtremeTubeIE(InfoExtractor):
|
|||||||
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 = compat_parse_qs(self._search_regex(
|
flash_vars = self._parse_json(
|
||||||
r'<param[^>]+?name="flashvars"[^>]+?value="([^"]+)"', webpage, 'flash vars'))
|
self._search_regex(
|
||||||
|
r'var\s+flashvars\s*=\s*({.+?});', webpage, 'flash vars'),
|
||||||
|
video_id)
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
quality = qualities(['180p', '240p', '360p', '480p', '720p', '1080p'])
|
for quality_key, video_url in flash_vars.items():
|
||||||
for k, vals in flash_vars.items():
|
height = int_or_none(self._search_regex(
|
||||||
m = re.match(r'quality_(?P<quality>[0-9]+p)$', k)
|
r'quality_(\d+)[pP]$', quality_key, 'height', default=None))
|
||||||
if m is not None:
|
if not height:
|
||||||
formats.append({
|
continue
|
||||||
'format_id': m.group('quality'),
|
f = {
|
||||||
'quality': quality(m.group('quality')),
|
'url': video_url,
|
||||||
'url': vals[0],
|
}
|
||||||
|
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)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@@ -10,11 +10,11 @@ from ..compat import (
|
|||||||
compat_str,
|
compat_str,
|
||||||
compat_urllib_error,
|
compat_urllib_error,
|
||||||
compat_urllib_parse_unquote,
|
compat_urllib_parse_unquote,
|
||||||
compat_urllib_request,
|
|
||||||
)
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
limit_length,
|
limit_length,
|
||||||
|
sanitized_Request,
|
||||||
urlencode_postdata,
|
urlencode_postdata,
|
||||||
get_element_by_id,
|
get_element_by_id,
|
||||||
clean_html,
|
clean_html,
|
||||||
@@ -73,7 +73,7 @@ class FacebookIE(InfoExtractor):
|
|||||||
if useremail is None:
|
if useremail is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
login_page_req = compat_urllib_request.Request(self._LOGIN_URL)
|
login_page_req = sanitized_Request(self._LOGIN_URL)
|
||||||
login_page_req.add_header('Cookie', 'locale=en_US')
|
login_page_req.add_header('Cookie', 'locale=en_US')
|
||||||
login_page = self._download_webpage(login_page_req, None,
|
login_page = self._download_webpage(login_page_req, None,
|
||||||
note='Downloading login page',
|
note='Downloading login page',
|
||||||
@@ -94,7 +94,7 @@ class FacebookIE(InfoExtractor):
|
|||||||
'timezone': '-60',
|
'timezone': '-60',
|
||||||
'trynum': '1',
|
'trynum': '1',
|
||||||
}
|
}
|
||||||
request = compat_urllib_request.Request(self._LOGIN_URL, urlencode_postdata(login_form))
|
request = sanitized_Request(self._LOGIN_URL, urlencode_postdata(login_form))
|
||||||
request.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
request.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
||||||
try:
|
try:
|
||||||
login_results = self._download_webpage(request, None,
|
login_results = self._download_webpage(request, None,
|
||||||
@@ -109,7 +109,7 @@ class FacebookIE(InfoExtractor):
|
|||||||
r'name="h"\s+(?:\w+="[^"]+"\s+)*?value="([^"]+)"', login_results, 'h'),
|
r'name="h"\s+(?:\w+="[^"]+"\s+)*?value="([^"]+)"', login_results, 'h'),
|
||||||
'name_action_selected': 'dont_save',
|
'name_action_selected': 'dont_save',
|
||||||
}
|
}
|
||||||
check_req = compat_urllib_request.Request(self._CHECKPOINT_URL, urlencode_postdata(check_form))
|
check_req = sanitized_Request(self._CHECKPOINT_URL, urlencode_postdata(check_form))
|
||||||
check_req.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
check_req.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
||||||
check_response = self._download_webpage(check_req, None,
|
check_response = self._download_webpage(check_req, None,
|
||||||
note='Confirming login')
|
note='Confirming login')
|
||||||
@@ -164,7 +164,7 @@ class FacebookIE(InfoExtractor):
|
|||||||
if not video_title:
|
if not video_title:
|
||||||
video_title = self._html_search_regex(
|
video_title = self._html_search_regex(
|
||||||
r'(?s)<span class="fbPhotosPhotoCaption".*?id="fbPhotoPageCaption"><span class="hasCaption">(.*?)</span>',
|
r'(?s)<span class="fbPhotosPhotoCaption".*?id="fbPhotoPageCaption"><span class="hasCaption">(.*?)</span>',
|
||||||
webpage, 'alternative title', fatal=False)
|
webpage, 'alternative title', default=None)
|
||||||
video_title = limit_length(video_title, 80)
|
video_title = limit_length(video_title, 80)
|
||||||
if not video_title:
|
if not video_title:
|
||||||
video_title = 'Facebook video #%s' % video_id
|
video_title = 'Facebook video #%s' % video_id
|
||||||
|
@@ -12,6 +12,7 @@ from ..compat import (
|
|||||||
from ..utils import (
|
from ..utils import (
|
||||||
encode_dict,
|
encode_dict,
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
|
sanitized_Request,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -36,8 +37,8 @@ class FC2IE(InfoExtractor):
|
|||||||
'params': {
|
'params': {
|
||||||
'username': 'ytdl@yt-dl.org',
|
'username': 'ytdl@yt-dl.org',
|
||||||
'password': '(snip)',
|
'password': '(snip)',
|
||||||
'skip': 'requires actual password'
|
},
|
||||||
}
|
'skip': 'requires actual password',
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://video.fc2.com/en/a/content/20130926eZpARwsF',
|
'url': 'http://video.fc2.com/en/a/content/20130926eZpARwsF',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
@@ -57,7 +58,7 @@ class FC2IE(InfoExtractor):
|
|||||||
}
|
}
|
||||||
|
|
||||||
login_data = compat_urllib_parse.urlencode(encode_dict(login_form_strs)).encode('utf-8')
|
login_data = compat_urllib_parse.urlencode(encode_dict(login_form_strs)).encode('utf-8')
|
||||||
request = compat_urllib_request.Request(
|
request = sanitized_Request(
|
||||||
'https://secure.id.fc2.com/index.php?mode=login&switch_language=en', login_data)
|
'https://secure.id.fc2.com/index.php?mode=login&switch_language=en', login_data)
|
||||||
|
|
||||||
login_results = self._download_webpage(request, None, note='Logging in', errnote='Unable to log in')
|
login_results = self._download_webpage(request, None, note='Logging in', errnote='Unable to log in')
|
||||||
@@ -66,7 +67,7 @@ class FC2IE(InfoExtractor):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# this is also needed
|
# this is also needed
|
||||||
login_redir = compat_urllib_request.Request('http://id.fc2.com/?mode=redirect&login=done')
|
login_redir = sanitized_Request('http://id.fc2.com/?mode=redirect&login=done')
|
||||||
self._download_webpage(
|
self._download_webpage(
|
||||||
login_redir, None, note='Login redirect', errnote='Login redirect failed')
|
login_redir, None, note='Login redirect', errnote='Login redirect failed')
|
||||||
|
|
||||||
|
@@ -3,10 +3,10 @@ from __future__ import unicode_literals
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import compat_urllib_request
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
find_xpath_attr,
|
find_xpath_attr,
|
||||||
|
sanitized_Request,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ class FlickrIE(InfoExtractor):
|
|||||||
video_id = mobj.group('id')
|
video_id = mobj.group('id')
|
||||||
video_uploader_id = mobj.group('uploader_id')
|
video_uploader_id = mobj.group('uploader_id')
|
||||||
webpage_url = 'http://www.flickr.com/photos/' + video_uploader_id + '/' + video_id
|
webpage_url = 'http://www.flickr.com/photos/' + video_uploader_id + '/' + video_id
|
||||||
req = compat_urllib_request.Request(webpage_url)
|
req = sanitized_Request(webpage_url)
|
||||||
req.add_header(
|
req.add_header(
|
||||||
'User-Agent',
|
'User-Agent',
|
||||||
# it needs a more recent version
|
# it needs a more recent version
|
||||||
|
@@ -3,12 +3,10 @@ from __future__ import unicode_literals
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
|
||||||
compat_urllib_request,
|
|
||||||
)
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
parse_duration,
|
parse_duration,
|
||||||
parse_iso8601,
|
parse_iso8601,
|
||||||
|
sanitized_Request,
|
||||||
str_to_int,
|
str_to_int,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -93,7 +91,7 @@ class FourTubeIE(InfoExtractor):
|
|||||||
b'Content-Type': b'application/x-www-form-urlencoded',
|
b'Content-Type': b'application/x-www-form-urlencoded',
|
||||||
b'Origin': b'http://www.4tube.com',
|
b'Origin': b'http://www.4tube.com',
|
||||||
}
|
}
|
||||||
token_req = compat_urllib_request.Request(token_url, b'{}', headers)
|
token_req = sanitized_Request(token_url, b'{}', headers)
|
||||||
tokens = self._download_json(token_req, video_id)
|
tokens = self._download_json(token_req, video_id)
|
||||||
formats = [{
|
formats = [{
|
||||||
'url': tokens[format]['token'],
|
'url': tokens[format]['token'],
|
||||||
|
@@ -83,6 +83,14 @@ class FranceTVBaseInfoExtractor(InfoExtractor):
|
|||||||
if subtitle:
|
if subtitle:
|
||||||
title += ' - %s' % subtitle
|
title += ' - %s' % subtitle
|
||||||
|
|
||||||
|
subtitles = {}
|
||||||
|
subtitles_list = [{
|
||||||
|
'url': subformat['url'],
|
||||||
|
'ext': subformat.get('format'),
|
||||||
|
} for subformat in info.get('subtitles', []) if subformat.get('url')]
|
||||||
|
if subtitles_list:
|
||||||
|
subtitles['fr'] = subtitles_list
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': title,
|
'title': title,
|
||||||
@@ -91,20 +99,27 @@ class FranceTVBaseInfoExtractor(InfoExtractor):
|
|||||||
'duration': int_or_none(info.get('real_duration')) or parse_duration(info['duree']),
|
'duration': int_or_none(info.get('real_duration')) or parse_duration(info['duree']),
|
||||||
'timestamp': int_or_none(info['diffusion']['timestamp']),
|
'timestamp': int_or_none(info['diffusion']['timestamp']),
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
|
'subtitles': subtitles,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PluzzIE(FranceTVBaseInfoExtractor):
|
class PluzzIE(FranceTVBaseInfoExtractor):
|
||||||
IE_NAME = 'pluzz.francetv.fr'
|
IE_NAME = 'pluzz.francetv.fr'
|
||||||
_VALID_URL = r'https?://pluzz\.francetv\.fr/videos/(.*?)\.html'
|
_VALID_URL = r'https?://(?:m\.)?pluzz\.francetv\.fr/videos/(?P<id>.+?)\.html'
|
||||||
|
|
||||||
# Can't use tests, videos expire in 7 days
|
# Can't use tests, videos expire in 7 days
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
title = re.match(self._VALID_URL, url).group(1)
|
display_id = self._match_id(url)
|
||||||
webpage = self._download_webpage(url, title)
|
|
||||||
video_id = self._search_regex(
|
webpage = self._download_webpage(url, display_id)
|
||||||
r'data-diffusion="(\d+)"', webpage, 'ID')
|
|
||||||
|
video_id = self._html_search_meta(
|
||||||
|
'id_video', webpage, 'video id', default=None)
|
||||||
|
if not video_id:
|
||||||
|
video_id = self._search_regex(
|
||||||
|
r'data-diffusion=["\'](\d+)', webpage, 'video id')
|
||||||
|
|
||||||
return self._extract_video(video_id, 'Pluzz')
|
return self._extract_video(video_id, 'Pluzz')
|
||||||
|
|
||||||
|
|
||||||
@@ -120,6 +135,9 @@ class FranceTvInfoIE(FranceTVBaseInfoExtractor):
|
|||||||
'title': 'Soir 3',
|
'title': 'Soir 3',
|
||||||
'upload_date': '20130826',
|
'upload_date': '20130826',
|
||||||
'timestamp': 1377548400,
|
'timestamp': 1377548400,
|
||||||
|
'subtitles': {
|
||||||
|
'fr': 'mincount:2',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.francetvinfo.fr/elections/europeennes/direct-europeennes-regardez-le-debat-entre-les-candidats-a-la-presidence-de-la-commission_600639.html',
|
'url': 'http://www.francetvinfo.fr/elections/europeennes/direct-europeennes-regardez-le-debat-entre-les-candidats-a-la-presidence-de-la-commission_600639.html',
|
||||||
|
193
youtube_dl/extractor/funimation.py
Normal file
193
youtube_dl/extractor/funimation.py
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
clean_html,
|
||||||
|
determine_ext,
|
||||||
|
encode_dict,
|
||||||
|
int_or_none,
|
||||||
|
sanitized_Request,
|
||||||
|
ExtractorError,
|
||||||
|
urlencode_postdata
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class FunimationIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?funimation\.com/shows/[^/]+/videos/(?:official|promotional)/(?P<id>[^/?#&]+)'
|
||||||
|
|
||||||
|
_NETRC_MACHINE = 'funimation'
|
||||||
|
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://www.funimation.com/shows/air/videos/official/breeze',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '658',
|
||||||
|
'display_id': 'breeze',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Air - 1 - Breeze',
|
||||||
|
'description': 'md5:1769f43cd5fc130ace8fd87232207892',
|
||||||
|
'thumbnail': 're:https?://.*\.jpg',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.funimation.com/shows/hacksign/videos/official/role-play',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '31128',
|
||||||
|
'display_id': 'role-play',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': '.hack//SIGN - 1 - Role Play',
|
||||||
|
'description': 'md5:b602bdc15eef4c9bbb201bb6e6a4a2dd',
|
||||||
|
'thumbnail': 're:https?://.*\.jpg',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.funimation.com/shows/attack-on-titan-junior-high/videos/promotional/broadcast-dub-preview',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '9635',
|
||||||
|
'display_id': 'broadcast-dub-preview',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Attack on Titan: Junior High - Broadcast Dub Preview',
|
||||||
|
'description': 'md5:f8ec49c0aff702a7832cd81b8a44f803',
|
||||||
|
'thumbnail': 're:https?://.*\.(?:jpg|png)',
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _login(self):
|
||||||
|
(username, password) = self._get_login_info()
|
||||||
|
if username is None:
|
||||||
|
return
|
||||||
|
data = urlencode_postdata(encode_dict({
|
||||||
|
'email_field': username,
|
||||||
|
'password_field': password,
|
||||||
|
}))
|
||||||
|
login_request = sanitized_Request('http://www.funimation.com/login', data, headers={
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Windows NT 5.2; WOW64; rv:42.0) Gecko/20100101 Firefox/42.0',
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
|
})
|
||||||
|
login_page = self._download_webpage(
|
||||||
|
login_request, None, 'Logging in as %s' % username)
|
||||||
|
if any(p in login_page for p in ('funimation.com/logout', '>Log Out<')):
|
||||||
|
return
|
||||||
|
error = self._html_search_regex(
|
||||||
|
r'(?s)<div[^>]+id=["\']errorMessages["\'][^>]*>(.+?)</div>',
|
||||||
|
login_page, 'error messages', default=None)
|
||||||
|
if error:
|
||||||
|
raise ExtractorError('Unable to login: %s' % error, expected=True)
|
||||||
|
raise ExtractorError('Unable to log in')
|
||||||
|
|
||||||
|
def _real_initialize(self):
|
||||||
|
self._login()
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
display_id = self._match_id(url)
|
||||||
|
|
||||||
|
errors = []
|
||||||
|
formats = []
|
||||||
|
|
||||||
|
ERRORS_MAP = {
|
||||||
|
'ERROR_MATURE_CONTENT_LOGGED_IN': 'matureContentLoggedIn',
|
||||||
|
'ERROR_MATURE_CONTENT_LOGGED_OUT': 'matureContentLoggedOut',
|
||||||
|
'ERROR_SUBSCRIPTION_LOGGED_OUT': 'subscriptionLoggedOut',
|
||||||
|
'ERROR_VIDEO_EXPIRED': 'videoExpired',
|
||||||
|
'ERROR_TERRITORY_UNAVAILABLE': 'territoryUnavailable',
|
||||||
|
'SVODBASIC_SUBSCRIPTION_IN_PLAYER': 'basicSubscription',
|
||||||
|
'SVODNON_SUBSCRIPTION_IN_PLAYER': 'nonSubscription',
|
||||||
|
'ERROR_PLAYER_NOT_RESPONDING': 'playerNotResponding',
|
||||||
|
'ERROR_UNABLE_TO_CONNECT_TO_CDN': 'unableToConnectToCDN',
|
||||||
|
'ERROR_STREAM_NOT_FOUND': 'streamNotFound',
|
||||||
|
}
|
||||||
|
|
||||||
|
USER_AGENTS = (
|
||||||
|
# PC UA is served with m3u8 that provides some bonus lower quality formats
|
||||||
|
('pc', 'Mozilla/5.0 (Windows NT 5.2; WOW64; rv:42.0) Gecko/20100101 Firefox/42.0'),
|
||||||
|
# Mobile UA allows to extract direct links and also does not fail when
|
||||||
|
# PC UA fails with hulu error (e.g.
|
||||||
|
# http://www.funimation.com/shows/hacksign/videos/official/role-play)
|
||||||
|
('mobile', 'Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.114 Mobile Safari/537.36'),
|
||||||
|
)
|
||||||
|
|
||||||
|
for kind, user_agent in USER_AGENTS:
|
||||||
|
request = sanitized_Request(url)
|
||||||
|
request.add_header('User-Agent', user_agent)
|
||||||
|
webpage = self._download_webpage(
|
||||||
|
request, display_id, 'Downloading %s webpage' % kind)
|
||||||
|
|
||||||
|
playlist = self._parse_json(
|
||||||
|
self._search_regex(
|
||||||
|
r'var\s+playersData\s*=\s*(\[.+?\]);\n',
|
||||||
|
webpage, 'players data'),
|
||||||
|
display_id)[0]['playlist']
|
||||||
|
|
||||||
|
items = next(item['items'] for item in playlist if item.get('items'))
|
||||||
|
item = next(item for item in items if item.get('itemAK') == display_id)
|
||||||
|
|
||||||
|
error_messages = {}
|
||||||
|
video_error_messages = self._search_regex(
|
||||||
|
r'var\s+videoErrorMessages\s*=\s*({.+?});\n',
|
||||||
|
webpage, 'error messages', default=None)
|
||||||
|
if video_error_messages:
|
||||||
|
error_messages_json = self._parse_json(video_error_messages, display_id, fatal=False)
|
||||||
|
if error_messages_json:
|
||||||
|
for _, error in error_messages_json.items():
|
||||||
|
type_ = error.get('type')
|
||||||
|
description = error.get('description')
|
||||||
|
content = error.get('content')
|
||||||
|
if type_ == 'text' and description and content:
|
||||||
|
error_message = ERRORS_MAP.get(description)
|
||||||
|
if error_message:
|
||||||
|
error_messages[error_message] = content
|
||||||
|
|
||||||
|
for video in item.get('videoSet', []):
|
||||||
|
auth_token = video.get('authToken')
|
||||||
|
if not auth_token:
|
||||||
|
continue
|
||||||
|
funimation_id = video.get('FUNImationID') or video.get('videoId')
|
||||||
|
preference = 1 if video.get('languageMode') == 'dub' else 0
|
||||||
|
if not auth_token.startswith('?'):
|
||||||
|
auth_token = '?%s' % auth_token
|
||||||
|
for quality, height in (('sd', 480), ('hd', 720), ('hd1080', 1080)):
|
||||||
|
format_url = video.get('%sUrl' % quality)
|
||||||
|
if not format_url:
|
||||||
|
continue
|
||||||
|
if not format_url.startswith(('http', '//')):
|
||||||
|
errors.append(format_url)
|
||||||
|
continue
|
||||||
|
if determine_ext(format_url) == 'm3u8':
|
||||||
|
m3u8_formats = self._extract_m3u8_formats(
|
||||||
|
format_url + auth_token, display_id, 'mp4', entry_protocol='m3u8_native',
|
||||||
|
preference=preference, m3u8_id='%s-hls' % funimation_id, fatal=False)
|
||||||
|
if m3u8_formats:
|
||||||
|
formats.extend(m3u8_formats)
|
||||||
|
else:
|
||||||
|
tbr = int_or_none(self._search_regex(
|
||||||
|
r'-(\d+)[Kk]', format_url, 'tbr', default=None))
|
||||||
|
formats.append({
|
||||||
|
'url': format_url + auth_token,
|
||||||
|
'format_id': '%s-http-%dp' % (funimation_id, height),
|
||||||
|
'height': height,
|
||||||
|
'tbr': tbr,
|
||||||
|
'preference': preference,
|
||||||
|
})
|
||||||
|
|
||||||
|
if not formats and errors:
|
||||||
|
raise ExtractorError(
|
||||||
|
'%s returned error: %s'
|
||||||
|
% (self.IE_NAME, clean_html(error_messages.get(errors[0], errors[0]))),
|
||||||
|
expected=True)
|
||||||
|
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
title = item['title']
|
||||||
|
artist = item.get('artist')
|
||||||
|
if artist:
|
||||||
|
title = '%s - %s' % (artist, title)
|
||||||
|
description = self._og_search_description(webpage) or item.get('description')
|
||||||
|
thumbnail = self._og_search_thumbnail(webpage) or item.get('posterUrl')
|
||||||
|
video_id = item.get('itemId') or display_id
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'display_id': display_id,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'formats': formats,
|
||||||
|
}
|
@@ -45,11 +45,20 @@ class FunnyOrDieIE(InfoExtractor):
|
|||||||
|
|
||||||
links.sort(key=lambda link: 1 if link[1] == 'mp4' else 0)
|
links.sort(key=lambda link: 1 if link[1] == 'mp4' else 0)
|
||||||
|
|
||||||
bitrates = self._html_search_regex(r'<source src="[^"]+/v,((?:\d+,)+)\.mp4\.csmil', webpage, 'video bitrates')
|
m3u8_url = self._search_regex(
|
||||||
bitrates = [int(b) for b in bitrates.rstrip(',').split(',')]
|
r'<source[^>]+src=(["\'])(?P<url>.+?/master\.m3u8)\1',
|
||||||
bitrates.sort()
|
webpage, 'm3u8 url', default=None, group='url')
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
|
|
||||||
|
m3u8_formats = self._extract_m3u8_formats(
|
||||||
|
m3u8_url, video_id, 'mp4', 'm3u8_native', m3u8_id='hls', fatal=False)
|
||||||
|
if m3u8_formats:
|
||||||
|
formats.extend(m3u8_formats)
|
||||||
|
|
||||||
|
bitrates = [int(bitrate) for bitrate in re.findall(r'[,/]v(\d+)[,/]', m3u8_url)]
|
||||||
|
bitrates.sort()
|
||||||
|
|
||||||
for bitrate in bitrates:
|
for bitrate in bitrates:
|
||||||
for link in links:
|
for link in links:
|
||||||
formats.append({
|
formats.append({
|
||||||
|
43
youtube_dl/extractor/gameinformer.py
Normal file
43
youtube_dl/extractor/gameinformer.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..compat import compat_str
|
||||||
|
from ..utils import int_or_none
|
||||||
|
|
||||||
|
|
||||||
|
class GameInformerIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?gameinformer\.com/(?:[^/]+/)*(?P<id>.+)\.aspx'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.gameinformer.com/b/features/archive/2015/09/26/replay-animal-crossing.aspx',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '4515472681001',
|
||||||
|
'ext': 'm3u8',
|
||||||
|
'title': 'Replay - Animal Crossing',
|
||||||
|
'description': 'md5:2e211891b215c85d061adc7a4dd2d930',
|
||||||
|
'timestamp': 1443457610706,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# m3u8 download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
display_id = self._match_id(url)
|
||||||
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
|
||||||
|
bc_api_url = self._search_regex(r"getVideo\('([^']+)'", webpage, 'brightcove api url')
|
||||||
|
json_data = self._download_json(
|
||||||
|
bc_api_url + '&video_fields=id,name,shortDescription,publishedDate,videoStillURL,length,IOSRenditions',
|
||||||
|
display_id)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': compat_str(json_data['id']),
|
||||||
|
'display_id': display_id,
|
||||||
|
'url': json_data['IOSRenditions'][0]['url'],
|
||||||
|
'title': json_data['name'],
|
||||||
|
'description': json_data.get('shortDescription'),
|
||||||
|
'timestamp': int_or_none(json_data.get('publishedDate')),
|
||||||
|
'duration': int_or_none(json_data.get('length')),
|
||||||
|
}
|
@@ -1,19 +1,62 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .mtv import MTVServicesInfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
int_or_none,
|
||||||
|
parse_age_limit,
|
||||||
|
url_basename,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class GametrailersIE(MTVServicesInfoExtractor):
|
class GametrailersIE(InfoExtractor):
|
||||||
_VALID_URL = r'http://www\.gametrailers\.com/(?P<type>videos|reviews|full-episodes)/(?P<id>.*?)/(?P<title>.*)'
|
_VALID_URL = r'http://www\.gametrailers\.com/videos/view/[^/]+/(?P<id>.+)'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.gametrailers.com/videos/zbvr8i/mirror-s-edge-2-e3-2013--debut-trailer',
|
'url': 'http://www.gametrailers.com/videos/view/gametrailers-com/116437-Just-Cause-3-Review',
|
||||||
'md5': '4c8e67681a0ea7ec241e8c09b3ea8cf7',
|
'md5': 'f28c4efa0bdfaf9b760f6507955b6a6a',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '70e9a5d7-cf25-4a10-9104-6f3e7342ae0d',
|
'id': '2983958',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'E3 2013: Debut Trailer',
|
'display_id': '116437-Just-Cause-3-Review',
|
||||||
'description': 'Faith is back! Check out the World Premiere trailer for Mirror\'s Edge 2 straight from the EA Press Conference at E3 2013!',
|
'title': 'Just Cause 3 - Review',
|
||||||
|
'description': 'It\'s a lot of fun to shoot at things and then watch them explode in Just Cause 3, but should there be more to the experience than that?',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
_FEED_URL = 'http://www.gametrailers.com/feeds/mrss'
|
def _real_extract(self, url):
|
||||||
|
display_id = self._match_id(url)
|
||||||
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
title = self._html_search_regex(
|
||||||
|
r'<title>(.+?)\|', webpage, 'title').strip()
|
||||||
|
embed_url = self._proto_relative_url(
|
||||||
|
self._search_regex(
|
||||||
|
r'src=\'(//embed.gametrailers.com/embed/[^\']+)\'', webpage,
|
||||||
|
'embed url'),
|
||||||
|
scheme='http:')
|
||||||
|
video_id = url_basename(embed_url)
|
||||||
|
embed_page = self._download_webpage(embed_url, video_id)
|
||||||
|
embed_vars_json = self._search_regex(
|
||||||
|
r'(?s)var embedVars = (\{.*?\})\s*</script>', embed_page,
|
||||||
|
'embed vars')
|
||||||
|
info = self._parse_json(embed_vars_json, video_id)
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
for media in info['media']:
|
||||||
|
if media['mediaPurpose'] == 'play':
|
||||||
|
formats.append({
|
||||||
|
'url': media['uri'],
|
||||||
|
'height': media['height'],
|
||||||
|
'width:': media['width'],
|
||||||
|
})
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'display_id': display_id,
|
||||||
|
'title': title,
|
||||||
|
'formats': formats,
|
||||||
|
'thumbnail': info.get('thumbUri'),
|
||||||
|
'description': self._og_search_description(webpage),
|
||||||
|
'duration': int_or_none(info.get('videoLengthInSeconds')),
|
||||||
|
'age_limit': parse_age_limit(info.get('audienceRating')),
|
||||||
|
}
|
||||||
|
@@ -3,13 +3,11 @@ from __future__ import unicode_literals
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import compat_urllib_parse
|
||||||
compat_urllib_parse,
|
|
||||||
compat_urllib_request,
|
|
||||||
)
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
remove_end,
|
remove_end,
|
||||||
HEADRequest,
|
HEADRequest,
|
||||||
|
sanitized_Request,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -125,7 +123,7 @@ class GDCVaultIE(InfoExtractor):
|
|||||||
'password': password,
|
'password': password,
|
||||||
}
|
}
|
||||||
|
|
||||||
request = compat_urllib_request.Request(login_url, compat_urllib_parse.urlencode(login_form))
|
request = sanitized_Request(login_url, compat_urllib_parse.urlencode(login_form))
|
||||||
request.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
request.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
||||||
self._download_webpage(request, display_id, 'Logging in')
|
self._download_webpage(request, display_id, 'Logging in')
|
||||||
start_page = self._download_webpage(webpage_url, display_id, 'Getting authenticated video page')
|
start_page = self._download_webpage(webpage_url, display_id, 'Getting authenticated video page')
|
||||||
|
@@ -9,8 +9,8 @@ import sys
|
|||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from .youtube import YoutubeIE
|
from .youtube import YoutubeIE
|
||||||
from ..compat import (
|
from ..compat import (
|
||||||
|
compat_etree_fromstring,
|
||||||
compat_urllib_parse_unquote,
|
compat_urllib_parse_unquote,
|
||||||
compat_urllib_request,
|
|
||||||
compat_urlparse,
|
compat_urlparse,
|
||||||
compat_xml_parse_error,
|
compat_xml_parse_error,
|
||||||
)
|
)
|
||||||
@@ -21,7 +21,7 @@ from ..utils import (
|
|||||||
HEADRequest,
|
HEADRequest,
|
||||||
is_html,
|
is_html,
|
||||||
orderedSet,
|
orderedSet,
|
||||||
parse_xml,
|
sanitized_Request,
|
||||||
smuggle_url,
|
smuggle_url,
|
||||||
unescapeHTML,
|
unescapeHTML,
|
||||||
unified_strdate,
|
unified_strdate,
|
||||||
@@ -30,7 +30,10 @@ from ..utils import (
|
|||||||
url_basename,
|
url_basename,
|
||||||
xpath_text,
|
xpath_text,
|
||||||
)
|
)
|
||||||
from .brightcove import BrightcoveIE
|
from .brightcove import (
|
||||||
|
BrightcoveLegacyIE,
|
||||||
|
BrightcoveNewIE,
|
||||||
|
)
|
||||||
from .nbc import NBCSportsVPlayerIE
|
from .nbc import NBCSportsVPlayerIE
|
||||||
from .ooyala import OoyalaIE
|
from .ooyala import OoyalaIE
|
||||||
from .rutv import RUTVIE
|
from .rutv import RUTVIE
|
||||||
@@ -51,6 +54,7 @@ from .onionstudios import OnionStudiosIE
|
|||||||
from .snagfilms import SnagFilmsEmbedIE
|
from .snagfilms import SnagFilmsEmbedIE
|
||||||
from .screenwavemedia import ScreenwaveMediaIE
|
from .screenwavemedia import ScreenwaveMediaIE
|
||||||
from .mtv import MTVServicesEmbeddedIE
|
from .mtv import MTVServicesEmbeddedIE
|
||||||
|
from .pladform import PladformIE
|
||||||
|
|
||||||
|
|
||||||
class GenericIE(InfoExtractor):
|
class GenericIE(InfoExtractor):
|
||||||
@@ -141,6 +145,7 @@ class GenericIE(InfoExtractor):
|
|||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Automatics, robotics and biocybernetics',
|
'title': 'Automatics, robotics and biocybernetics',
|
||||||
'description': 'md5:815fc1deb6b3a2bff99de2d5325be482',
|
'description': 'md5:815fc1deb6b3a2bff99de2d5325be482',
|
||||||
|
'upload_date': '20130627',
|
||||||
'formats': 'mincount:16',
|
'formats': 'mincount:16',
|
||||||
'subtitles': 'mincount:1',
|
'subtitles': 'mincount:1',
|
||||||
},
|
},
|
||||||
@@ -274,7 +279,7 @@ class GenericIE(InfoExtractor):
|
|||||||
# it also tests brightcove videos that need to set the 'Referer' in the
|
# it also tests brightcove videos that need to set the 'Referer' in the
|
||||||
# http requests
|
# http requests
|
||||||
{
|
{
|
||||||
'add_ie': ['Brightcove'],
|
'add_ie': ['BrightcoveLegacy'],
|
||||||
'url': 'http://www.bfmtv.com/video/bfmbusiness/cours-bourse/cours-bourse-l-analyse-technique-154522/',
|
'url': 'http://www.bfmtv.com/video/bfmbusiness/cours-bourse/cours-bourse-l-analyse-technique-154522/',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '2765128793001',
|
'id': '2765128793001',
|
||||||
@@ -298,7 +303,7 @@ class GenericIE(InfoExtractor):
|
|||||||
'uploader': 'thestar.com',
|
'uploader': 'thestar.com',
|
||||||
'description': 'Mississauga resident David Farmer is still out of power as a result of the ice storm a month ago. To keep the house warm, Farmer cuts wood from his property for a wood burning stove downstairs.',
|
'description': 'Mississauga resident David Farmer is still out of power as a result of the ice storm a month ago. To keep the house warm, Farmer cuts wood from his property for a wood burning stove downstairs.',
|
||||||
},
|
},
|
||||||
'add_ie': ['Brightcove'],
|
'add_ie': ['BrightcoveLegacy'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'url': 'http://www.championat.com/video/football/v/87/87499.html',
|
'url': 'http://www.championat.com/video/football/v/87/87499.html',
|
||||||
@@ -313,7 +318,7 @@ class GenericIE(InfoExtractor):
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
# https://github.com/rg3/youtube-dl/issues/3541
|
# https://github.com/rg3/youtube-dl/issues/3541
|
||||||
'add_ie': ['Brightcove'],
|
'add_ie': ['BrightcoveLegacy'],
|
||||||
'url': 'http://www.kijk.nl/sbs6/leermijvrouwenkennen/videos/jqMiXKAYan2S/aflevering-1',
|
'url': 'http://www.kijk.nl/sbs6/leermijvrouwenkennen/videos/jqMiXKAYan2S/aflevering-1',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '3866516442001',
|
'id': '3866516442001',
|
||||||
@@ -335,6 +340,7 @@ class GenericIE(InfoExtractor):
|
|||||||
'id': 'BwY2RxaTrTkslxOfcan0UCf0YqyvWysJ',
|
'id': 'BwY2RxaTrTkslxOfcan0UCf0YqyvWysJ',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': '2cc213299525360.mov', # that's what we get
|
'title': '2cc213299525360.mov', # that's what we get
|
||||||
|
'duration': 238.231,
|
||||||
},
|
},
|
||||||
'add_ie': ['Ooyala'],
|
'add_ie': ['Ooyala'],
|
||||||
},
|
},
|
||||||
@@ -346,6 +352,7 @@ class GenericIE(InfoExtractor):
|
|||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': '"Steve Jobs: Man in the Machine" trailer',
|
'title': '"Steve Jobs: Man in the Machine" trailer',
|
||||||
'description': 'The first trailer for the Alex Gibney documentary "Steve Jobs: Man in the Machine."',
|
'description': 'The first trailer for the Alex Gibney documentary "Steve Jobs: Man in the Machine."',
|
||||||
|
'duration': 135.427,
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
@@ -819,6 +826,19 @@ class GenericIE(InfoExtractor):
|
|||||||
'title': 'Os Guinness // Is It Fools Talk? // Unbelievable? Conference 2014',
|
'title': 'Os Guinness // Is It Fools Talk? // Unbelievable? Conference 2014',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
# Kaltura embed protected with referrer
|
||||||
|
{
|
||||||
|
'url': 'http://www.disney.nl/disney-channel/filmpjes/achter-de-schermen#/videoId/violetta-achter-de-schermen-ruggero',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '1_g4fbemnq',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Violetta - Achter De Schermen - Ruggero',
|
||||||
|
'description': 'Achter de schermen met Ruggero',
|
||||||
|
'timestamp': 1435133761,
|
||||||
|
'upload_date': '20150624',
|
||||||
|
'uploader_id': 'echojecka',
|
||||||
|
},
|
||||||
|
},
|
||||||
# Eagle.Platform embed (generic URL)
|
# Eagle.Platform embed (generic URL)
|
||||||
{
|
{
|
||||||
'url': 'http://lenta.ru/news/2015/03/06/navalny/',
|
'url': 'http://lenta.ru/news/2015/03/06/navalny/',
|
||||||
@@ -943,8 +963,9 @@ class GenericIE(InfoExtractor):
|
|||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '50YnY4czr4ms1vJ7yz3xzq0excz_pUMs',
|
'id': '50YnY4czr4ms1vJ7yz3xzq0excz_pUMs',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'description': 'VIDEO: Index/Match versus VLOOKUP.',
|
'description': 'VIDEO: INDEX/MATCH versus VLOOKUP.',
|
||||||
'title': 'This is what separates the Excel masters from the wannabes',
|
'title': 'This is what separates the Excel masters from the wannabes',
|
||||||
|
'duration': 191.933,
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
# m3u8 downloads
|
# m3u8 downloads
|
||||||
@@ -1030,6 +1051,31 @@ class GenericIE(InfoExtractor):
|
|||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'cinemasnob',
|
'title': 'cinemasnob',
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
# BrightcoveInPageEmbed embed
|
||||||
|
{
|
||||||
|
'url': 'http://www.geekandsundry.com/tabletop-bonus-wils-final-thoughts-on-dread/',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '4238694884001',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Tabletop: Dread, Last Thoughts',
|
||||||
|
'description': 'Tabletop: Dread, Last Thoughts',
|
||||||
|
'duration': 51690,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
# JWPlayer with M3U8
|
||||||
|
{
|
||||||
|
'url': 'http://ren.tv/novosti/2015-09-25/sluchaynyy-prohozhiy-poymal-avtougonshchika-v-murmanske-video',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'playlist',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Случайный прохожий поймал автоугонщика в Мурманске. ВИДЕО | РЕН ТВ',
|
||||||
|
'uploader': 'ren.tv',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# m3u8 downloads
|
||||||
|
'skip_download': True,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1173,7 +1219,7 @@ class GenericIE(InfoExtractor):
|
|||||||
|
|
||||||
full_response = None
|
full_response = None
|
||||||
if head_response is False:
|
if head_response is False:
|
||||||
request = compat_urllib_request.Request(url)
|
request = sanitized_Request(url)
|
||||||
request.add_header('Accept-Encoding', '*')
|
request.add_header('Accept-Encoding', '*')
|
||||||
full_response = self._request_webpage(request, video_id)
|
full_response = self._request_webpage(request, video_id)
|
||||||
head_response = full_response
|
head_response = full_response
|
||||||
@@ -1202,7 +1248,7 @@ class GenericIE(InfoExtractor):
|
|||||||
'%s on generic information extractor.' % ('Forcing' if force else 'Falling back'))
|
'%s on generic information extractor.' % ('Forcing' if force else 'Falling back'))
|
||||||
|
|
||||||
if not full_response:
|
if not full_response:
|
||||||
request = compat_urllib_request.Request(url)
|
request = sanitized_Request(url)
|
||||||
# Some webservers may serve compressed content of rather big size (e.g. gzipped flac)
|
# Some webservers may serve compressed content of rather big size (e.g. gzipped flac)
|
||||||
# making it impossible to download only chunk of the file (yet we need only 512kB to
|
# making it impossible to download only chunk of the file (yet we need only 512kB to
|
||||||
# test whether it's HTML or not). According to youtube-dl default Accept-Encoding
|
# test whether it's HTML or not). According to youtube-dl default Accept-Encoding
|
||||||
@@ -1237,7 +1283,7 @@ class GenericIE(InfoExtractor):
|
|||||||
|
|
||||||
# Is it an RSS feed, a SMIL file or a XSPF playlist?
|
# Is it an RSS feed, a SMIL file or a XSPF playlist?
|
||||||
try:
|
try:
|
||||||
doc = parse_xml(webpage)
|
doc = compat_etree_fromstring(webpage.encode('utf-8'))
|
||||||
if doc.tag == 'rss':
|
if doc.tag == 'rss':
|
||||||
return self._extract_rss(url, video_id, doc)
|
return self._extract_rss(url, video_id, doc)
|
||||||
elif re.match(r'^(?:{[^}]+})?smil$', doc.tag):
|
elif re.match(r'^(?:{[^}]+})?smil$', doc.tag):
|
||||||
@@ -1289,14 +1335,14 @@ class GenericIE(InfoExtractor):
|
|||||||
return self.playlist_result(
|
return self.playlist_result(
|
||||||
urlrs, playlist_id=video_id, playlist_title=video_title)
|
urlrs, playlist_id=video_id, playlist_title=video_title)
|
||||||
|
|
||||||
# Look for BrightCove:
|
# Look for Brightcove Legacy Studio embeds
|
||||||
bc_urls = BrightcoveIE._extract_brightcove_urls(webpage)
|
bc_urls = BrightcoveLegacyIE._extract_brightcove_urls(webpage)
|
||||||
if bc_urls:
|
if bc_urls:
|
||||||
self.to_screen('Brightcove video detected.')
|
self.to_screen('Brightcove video detected.')
|
||||||
entries = [{
|
entries = [{
|
||||||
'_type': 'url',
|
'_type': 'url',
|
||||||
'url': smuggle_url(bc_url, {'Referer': url}),
|
'url': smuggle_url(bc_url, {'Referer': url}),
|
||||||
'ie_key': 'Brightcove'
|
'ie_key': 'BrightcoveLegacy'
|
||||||
} for bc_url in bc_urls]
|
} for bc_url in bc_urls]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -1306,6 +1352,11 @@ class GenericIE(InfoExtractor):
|
|||||||
'entries': entries,
|
'entries': entries,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Look for Brightcove New Studio embeds
|
||||||
|
bc_urls = BrightcoveNewIE._extract_urls(webpage)
|
||||||
|
if bc_urls:
|
||||||
|
return _playlist_from_matches(bc_urls, ie='BrightcoveNew')
|
||||||
|
|
||||||
# Look for embedded rtl.nl player
|
# Look for embedded rtl.nl player
|
||||||
matches = re.findall(
|
matches = re.findall(
|
||||||
r'<iframe[^>]+?src="((?:https?:)?//(?:www\.)?rtl\.nl/system/videoplayer/[^"]+(?:video_)?embed[^"]+)"',
|
r'<iframe[^>]+?src="((?:https?:)?//(?:www\.)?rtl\.nl/system/videoplayer/[^"]+(?:video_)?embed[^"]+)"',
|
||||||
@@ -1454,7 +1505,7 @@ class GenericIE(InfoExtractor):
|
|||||||
re.search(r'SBN\.VideoLinkset\.ooyala\([\'"](?P<ec>.{32})[\'"]\)', webpage) or
|
re.search(r'SBN\.VideoLinkset\.ooyala\([\'"](?P<ec>.{32})[\'"]\)', webpage) or
|
||||||
re.search(r'data-ooyala-video-id\s*=\s*[\'"](?P<ec>.{32})[\'"]', webpage))
|
re.search(r'data-ooyala-video-id\s*=\s*[\'"](?P<ec>.{32})[\'"]', webpage))
|
||||||
if mobj is not None:
|
if mobj is not None:
|
||||||
return OoyalaIE._build_url_result(mobj.group('ec'))
|
return OoyalaIE._build_url_result(smuggle_url(mobj.group('ec'), {'domain': url}))
|
||||||
|
|
||||||
# Look for multiple Ooyala embeds on SBN network websites
|
# Look for multiple Ooyala embeds on SBN network websites
|
||||||
mobj = re.search(r'SBN\.VideoLinkset\.entryGroup\((\[.*?\])', webpage)
|
mobj = re.search(r'SBN\.VideoLinkset\.entryGroup\((\[.*?\])', webpage)
|
||||||
@@ -1462,7 +1513,7 @@ class GenericIE(InfoExtractor):
|
|||||||
embeds = self._parse_json(mobj.group(1), video_id, fatal=False)
|
embeds = self._parse_json(mobj.group(1), video_id, fatal=False)
|
||||||
if embeds:
|
if embeds:
|
||||||
return _playlist_from_matches(
|
return _playlist_from_matches(
|
||||||
embeds, getter=lambda v: OoyalaIE._url_for_embed_code(v['provider_video_id']), ie='Ooyala')
|
embeds, getter=lambda v: OoyalaIE._url_for_embed_code(smuggle_url(v['provider_video_id'], {'domain': url})), ie='Ooyala')
|
||||||
|
|
||||||
# Look for Aparat videos
|
# Look for Aparat videos
|
||||||
mobj = re.search(r'<iframe .*?src="(http://www\.aparat\.com/video/[^"]+)"', webpage)
|
mobj = re.search(r'<iframe .*?src="(http://www\.aparat\.com/video/[^"]+)"', webpage)
|
||||||
@@ -1671,10 +1722,12 @@ class GenericIE(InfoExtractor):
|
|||||||
return self.url_result(mobj.group('url'), 'Zapiks')
|
return self.url_result(mobj.group('url'), 'Zapiks')
|
||||||
|
|
||||||
# Look for Kaltura embeds
|
# Look for Kaltura embeds
|
||||||
mobj = (re.search(r"(?s)kWidget\.(?:thumb)?[Ee]mbed\(\{.*?'wid'\s*:\s*'_?(?P<partner_id>[^']+)',.*?'entry_id'\s*:\s*'(?P<id>[^']+)',", webpage) or
|
mobj = (re.search(r"(?s)kWidget\.(?:thumb)?[Ee]mbed\(\{.*?'wid'\s*:\s*'_?(?P<partner_id>[^']+)',.*?'entry_?[Ii]d'\s*:\s*'(?P<id>[^']+)',", webpage) or
|
||||||
re.search(r'(?s)(["\'])(?:https?:)?//cdnapisec\.kaltura\.com/.*?(?:p|partner_id)/(?P<partner_id>\d+).*?\1.*?entry_id\s*:\s*(["\'])(?P<id>[^\2]+?)\2', webpage))
|
re.search(r'(?s)(?P<q1>["\'])(?:https?:)?//cdnapi(?:sec)?\.kaltura\.com/.*?(?:p|partner_id)/(?P<partner_id>\d+).*?(?P=q1).*?entry_?[Ii]d\s*:\s*(?P<q2>["\'])(?P<id>.+?)(?P=q2)', webpage))
|
||||||
if mobj is not None:
|
if mobj is not None:
|
||||||
return self.url_result('kaltura:%(partner_id)s:%(id)s' % mobj.groupdict(), 'Kaltura')
|
return self.url_result(smuggle_url(
|
||||||
|
'kaltura:%(partner_id)s:%(id)s' % mobj.groupdict(),
|
||||||
|
{'source_url': url}), 'Kaltura')
|
||||||
|
|
||||||
# Look for Eagle.Platform embeds
|
# Look for Eagle.Platform embeds
|
||||||
mobj = re.search(
|
mobj = re.search(
|
||||||
@@ -1689,10 +1742,9 @@ class GenericIE(InfoExtractor):
|
|||||||
return self.url_result('eagleplatform:%(host)s:%(id)s' % mobj.groupdict(), 'EaglePlatform')
|
return self.url_result('eagleplatform:%(host)s:%(id)s' % mobj.groupdict(), 'EaglePlatform')
|
||||||
|
|
||||||
# Look for Pladform embeds
|
# Look for Pladform embeds
|
||||||
mobj = re.search(
|
pladform_url = PladformIE._extract_url(webpage)
|
||||||
r'<iframe[^>]+src="(?P<url>https?://out\.pladform\.ru/player\?.+?)"', webpage)
|
if pladform_url:
|
||||||
if mobj is not None:
|
return self.url_result(pladform_url)
|
||||||
return self.url_result(mobj.group('url'), 'Pladform')
|
|
||||||
|
|
||||||
# Look for Playwire embeds
|
# Look for Playwire embeds
|
||||||
mobj = re.search(
|
mobj = re.search(
|
||||||
@@ -1719,7 +1771,7 @@ class GenericIE(InfoExtractor):
|
|||||||
|
|
||||||
# Look for UDN embeds
|
# Look for UDN embeds
|
||||||
mobj = re.search(
|
mobj = re.search(
|
||||||
r'<iframe[^>]+src="(?P<url>%s)"' % UDNEmbedIE._VALID_URL, webpage)
|
r'<iframe[^>]+src="(?P<url>%s)"' % UDNEmbedIE._PROTOCOL_RELATIVE_VALID_URL, webpage)
|
||||||
if mobj is not None:
|
if mobj is not None:
|
||||||
return self.url_result(
|
return self.url_result(
|
||||||
compat_urlparse.urljoin(url, mobj.group('url')), 'UDNEmbed')
|
compat_urlparse.urljoin(url, mobj.group('url')), 'UDNEmbed')
|
||||||
@@ -1839,6 +1891,7 @@ class GenericIE(InfoExtractor):
|
|||||||
|
|
||||||
entries = []
|
entries = []
|
||||||
for video_url in found:
|
for video_url in found:
|
||||||
|
video_url = video_url.replace('\\/', '/')
|
||||||
video_url = compat_urlparse.urljoin(url, video_url)
|
video_url = compat_urlparse.urljoin(url, video_url)
|
||||||
video_id = compat_urllib_parse_unquote(os.path.basename(video_url))
|
video_id = compat_urllib_parse_unquote(os.path.basename(video_url))
|
||||||
|
|
||||||
@@ -1850,25 +1903,24 @@ class GenericIE(InfoExtractor):
|
|||||||
# here's a fun little line of code for you:
|
# here's a fun little line of code for you:
|
||||||
video_id = os.path.splitext(video_id)[0]
|
video_id = os.path.splitext(video_id)[0]
|
||||||
|
|
||||||
|
entry_info_dict = {
|
||||||
|
'id': video_id,
|
||||||
|
'uploader': video_uploader,
|
||||||
|
'title': video_title,
|
||||||
|
'age_limit': age_limit,
|
||||||
|
}
|
||||||
|
|
||||||
ext = determine_ext(video_url)
|
ext = determine_ext(video_url)
|
||||||
if ext == 'smil':
|
if ext == 'smil':
|
||||||
entries.append({
|
entry_info_dict['formats'] = self._extract_smil_formats(video_url, video_id)
|
||||||
'id': video_id,
|
|
||||||
'formats': self._extract_smil_formats(video_url, video_id),
|
|
||||||
'uploader': video_uploader,
|
|
||||||
'title': video_title,
|
|
||||||
'age_limit': age_limit,
|
|
||||||
})
|
|
||||||
elif ext == 'xspf':
|
elif ext == 'xspf':
|
||||||
return self.playlist_result(self._extract_xspf_playlist(video_url, video_id), video_id)
|
return self.playlist_result(self._extract_xspf_playlist(video_url, video_id), video_id)
|
||||||
|
elif ext == 'm3u8':
|
||||||
|
entry_info_dict['formats'] = self._extract_m3u8_formats(video_url, video_id, ext='mp4')
|
||||||
else:
|
else:
|
||||||
entries.append({
|
entry_info_dict['url'] = video_url
|
||||||
'id': video_id,
|
|
||||||
'url': video_url,
|
entries.append(entry_info_dict)
|
||||||
'uploader': video_uploader,
|
|
||||||
'title': video_title,
|
|
||||||
'age_limit': age_limit,
|
|
||||||
})
|
|
||||||
|
|
||||||
if len(entries) == 1:
|
if len(entries) == 1:
|
||||||
return entries[0]
|
return entries[0]
|
||||||
|
@@ -14,79 +14,58 @@ from ..utils import (
|
|||||||
ExtractorError,
|
ExtractorError,
|
||||||
float_or_none,
|
float_or_none,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
|
str_or_none,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class GloboIE(InfoExtractor):
|
class GloboIE(InfoExtractor):
|
||||||
_VALID_URL = 'https?://.+?\.globo\.com/(?P<id>.+)'
|
_VALID_URL = '(?:globo:|https?://.+?\.globo\.com/(?:[^/]+/)*(?:v/(?:[^/]+/)?|videos/))(?P<id>\d{7,})'
|
||||||
|
|
||||||
_API_URL_TEMPLATE = 'http://api.globovideos.com/videos/%s/playlist'
|
_API_URL_TEMPLATE = 'http://api.globovideos.com/videos/%s/playlist'
|
||||||
_SECURITY_URL_TEMPLATE = 'http://security.video.globo.com/videos/%s/hash?player=flash&version=17.0.0.132&resource_id=%s'
|
_SECURITY_URL_TEMPLATE = 'http://security.video.globo.com/videos/%s/hash?player=flash&version=17.0.0.132&resource_id=%s'
|
||||||
|
|
||||||
_VIDEOID_REGEXES = [
|
|
||||||
r'\bdata-video-id="(\d+)"',
|
|
||||||
r'\bdata-player-videosids="(\d+)"',
|
|
||||||
r'<div[^>]+\bid="(\d+)"',
|
|
||||||
]
|
|
||||||
|
|
||||||
_RESIGN_EXPIRATION = 86400
|
_RESIGN_EXPIRATION = 86400
|
||||||
|
|
||||||
_TESTS = [
|
_TESTS = [{
|
||||||
{
|
'url': 'http://g1.globo.com/carros/autoesporte/videos/t/exclusivos-do-g1/v/mercedes-benz-gla-passa-por-teste-de-colisao-na-europa/3607726/',
|
||||||
'url': 'http://globotv.globo.com/sportv/futebol-nacional/v/os-gols-de-atletico-mg-3-x-2-santos-pela-24a-rodada-do-brasileirao/3654973/',
|
'md5': 'b3ccc801f75cd04a914d51dadb83a78d',
|
||||||
'md5': '03ebf41cb7ade43581608b7d9b71fab0',
|
'info_dict': {
|
||||||
'info_dict': {
|
'id': '3607726',
|
||||||
'id': '3654973',
|
'ext': 'mp4',
|
||||||
'ext': 'mp4',
|
'title': 'Mercedes-Benz GLA passa por teste de colisão na Europa',
|
||||||
'title': 'Os gols de Atlético-MG 3 x 2 Santos pela 24ª rodada do Brasileirão',
|
'duration': 103.204,
|
||||||
'duration': 251.585,
|
'uploader': 'Globo.com',
|
||||||
'uploader': 'SporTV',
|
'uploader_id': '265',
|
||||||
'uploader_id': 698,
|
|
||||||
'like_count': int,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
}, {
|
||||||
'url': 'http://g1.globo.com/carros/autoesporte/videos/t/exclusivos-do-g1/v/mercedes-benz-gla-passa-por-teste-de-colisao-na-europa/3607726/',
|
'url': 'http://globoplay.globo.com/v/4581987/',
|
||||||
'md5': 'b3ccc801f75cd04a914d51dadb83a78d',
|
'md5': 'f36a1ecd6a50da1577eee6dd17f67eff',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '3607726',
|
'id': '4581987',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Mercedes-Benz GLA passa por teste de colisão na Europa',
|
'title': 'Acidentes de trânsito estão entre as maiores causas de queda de energia em SP',
|
||||||
'duration': 103.204,
|
'duration': 137.973,
|
||||||
'uploader': 'Globo.com',
|
'uploader': 'Rede Globo',
|
||||||
'uploader_id': 265,
|
'uploader_id': '196',
|
||||||
'like_count': int,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
}, {
|
||||||
'url': 'http://g1.globo.com/jornal-nacional/noticia/2014/09/novidade-na-fiscalizacao-de-bagagem-pela-receita-provoca-discussoes.html',
|
'url': 'http://canalbrasil.globo.com/programas/sangue-latino/videos/3928201.html',
|
||||||
'md5': '307fdeae4390ccfe6ba1aa198cf6e72b',
|
'only_matching': True,
|
||||||
'info_dict': {
|
}, {
|
||||||
'id': '3652183',
|
'url': 'http://globosatplay.globo.com/globonews/v/4472924/',
|
||||||
'ext': 'mp4',
|
'only_matching': True,
|
||||||
'title': 'Receita Federal explica como vai fiscalizar bagagens de quem retorna ao Brasil de avião',
|
}, {
|
||||||
'duration': 110.711,
|
'url': 'http://globotv.globo.com/t/programa/v/clipe-sexo-e-as-negas-adeus/3836166/',
|
||||||
'uploader': 'Rede Globo',
|
'only_matching': True,
|
||||||
'uploader_id': 196,
|
}, {
|
||||||
'like_count': int,
|
'url': 'http://globotv.globo.com/canal-brasil/sangue-latino/t/todos-os-videos/v/ator-e-diretor-argentino-ricado-darin-fala-sobre-utopias-e-suas-perdas/3928201/',
|
||||||
}
|
'only_matching': True,
|
||||||
},
|
}, {
|
||||||
{
|
'url': 'http://canaloff.globo.com/programas/desejar-profundo/videos/4518560.html',
|
||||||
'url': 'http://globotv.globo.com/canal-brasil/sangue-latino/t/todos-os-videos/v/ator-e-diretor-argentino-ricado-darin-fala-sobre-utopias-e-suas-perdas/3928201/',
|
'only_matching': True,
|
||||||
'md5': 'c1defca721ce25b2354e927d3e4b3dec',
|
}]
|
||||||
'info_dict': {
|
|
||||||
'id': '3928201',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': 'Ator e diretor argentino, Ricado Darín fala sobre utopias e suas perdas',
|
|
||||||
'duration': 1472.906,
|
|
||||||
'uploader': 'Canal Brasil',
|
|
||||||
'uploader_id': 705,
|
|
||||||
'like_count': int,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
class MD5():
|
class MD5:
|
||||||
HEX_FORMAT_LOWERCASE = 0
|
HEX_FORMAT_LOWERCASE = 0
|
||||||
HEX_FORMAT_UPPERCASE = 1
|
HEX_FORMAT_UPPERCASE = 1
|
||||||
BASE64_PAD_CHARACTER_DEFAULT_COMPLIANCE = ''
|
BASE64_PAD_CHARACTER_DEFAULT_COMPLIANCE = ''
|
||||||
@@ -353,9 +332,6 @@ class GloboIE(InfoExtractor):
|
|||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
|
||||||
video_id = self._search_regex(self._VIDEOID_REGEXES, webpage, 'video id')
|
|
||||||
|
|
||||||
video = self._download_json(
|
video = self._download_json(
|
||||||
self._API_URL_TEMPLATE % video_id, video_id)['videos'][0]
|
self._API_URL_TEMPLATE % video_id, video_id)['videos'][0]
|
||||||
|
|
||||||
@@ -364,7 +340,7 @@ class GloboIE(InfoExtractor):
|
|||||||
formats = []
|
formats = []
|
||||||
for resource in video['resources']:
|
for resource in video['resources']:
|
||||||
resource_id = resource.get('_id')
|
resource_id = resource.get('_id')
|
||||||
if not resource_id:
|
if not resource_id or resource_id.endswith('manifest'):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
security = self._download_json(
|
security = self._download_json(
|
||||||
@@ -393,20 +369,23 @@ class GloboIE(InfoExtractor):
|
|||||||
resource_url = resource['url']
|
resource_url = resource['url']
|
||||||
signed_url = '%s?h=%s&k=%s' % (resource_url, signed_hash, 'flash')
|
signed_url = '%s?h=%s&k=%s' % (resource_url, signed_hash, 'flash')
|
||||||
if resource_id.endswith('m3u8') or resource_url.endswith('.m3u8'):
|
if resource_id.endswith('m3u8') or resource_url.endswith('.m3u8'):
|
||||||
formats.extend(self._extract_m3u8_formats(signed_url, resource_id, 'mp4'))
|
m3u8_formats = self._extract_m3u8_formats(
|
||||||
|
signed_url, resource_id, 'mp4', entry_protocol='m3u8_native',
|
||||||
|
m3u8_id='hls', fatal=False)
|
||||||
|
if m3u8_formats:
|
||||||
|
formats.extend(m3u8_formats)
|
||||||
else:
|
else:
|
||||||
formats.append({
|
formats.append({
|
||||||
'url': signed_url,
|
'url': signed_url,
|
||||||
'format_id': resource_id,
|
'format_id': 'http-%s' % resource_id,
|
||||||
'height': resource.get('height'),
|
'height': int_or_none(resource.get('height')),
|
||||||
})
|
})
|
||||||
|
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
duration = float_or_none(video.get('duration'), 1000)
|
duration = float_or_none(video.get('duration'), 1000)
|
||||||
like_count = int_or_none(video.get('likes'))
|
|
||||||
uploader = video.get('channel')
|
uploader = video.get('channel')
|
||||||
uploader_id = video.get('channel_id')
|
uploader_id = str_or_none(video.get('channel_id'))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
@@ -414,6 +393,46 @@ class GloboIE(InfoExtractor):
|
|||||||
'duration': duration,
|
'duration': duration,
|
||||||
'uploader': uploader,
|
'uploader': uploader,
|
||||||
'uploader_id': uploader_id,
|
'uploader_id': uploader_id,
|
||||||
'like_count': like_count,
|
|
||||||
'formats': formats
|
'formats': formats
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class GloboArticleIE(InfoExtractor):
|
||||||
|
_VALID_URL = 'https?://.+?\.globo\.com/(?:[^/]+/)*(?P<id>[^/]+)\.html'
|
||||||
|
|
||||||
|
_VIDEOID_REGEXES = [
|
||||||
|
r'\bdata-video-id=["\'](\d{7,})',
|
||||||
|
r'\bdata-player-videosids=["\'](\d{7,})',
|
||||||
|
r'\bvideosIDs\s*:\s*["\'](\d{7,})',
|
||||||
|
r'\bdata-id=["\'](\d{7,})',
|
||||||
|
r'<div[^>]+\bid=["\'](\d{7,})',
|
||||||
|
]
|
||||||
|
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://g1.globo.com/jornal-nacional/noticia/2014/09/novidade-na-fiscalizacao-de-bagagem-pela-receita-provoca-discussoes.html',
|
||||||
|
'md5': '307fdeae4390ccfe6ba1aa198cf6e72b',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '3652183',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Receita Federal explica como vai fiscalizar bagagens de quem retorna ao Brasil de avião',
|
||||||
|
'duration': 110.711,
|
||||||
|
'uploader': 'Rede Globo',
|
||||||
|
'uploader_id': '196',
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'url': 'http://gq.globo.com/Prazeres/Poder/noticia/2015/10/all-o-desafio-assista-ao-segundo-capitulo-da-serie.html',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://gshow.globo.com/programas/tv-xuxa/O-Programa/noticia/2014/01/xuxa-e-junno-namoram-muuuito-em-luau-de-zeze-di-camargo-e-luciano.html',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def suitable(cls, url):
|
||||||
|
return False if GloboIE.suitable(url) else super(GloboArticleIE, cls).suitable(url)
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
display_id = self._match_id(url)
|
||||||
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
video_id = self._search_regex(self._VIDEOID_REGEXES, webpage, 'video id')
|
||||||
|
return self.url_result('globo:%s' % video_id, 'Globo')
|
||||||
|
@@ -61,7 +61,7 @@ class GooglePlusIE(InfoExtractor):
|
|||||||
'width': int(width),
|
'width': int(width),
|
||||||
'height': int(height),
|
'height': int(height),
|
||||||
} for width, height, video_url in re.findall(
|
} for width, height, video_url in re.findall(
|
||||||
r'\d+,(\d+),(\d+),"(https?://redirector\.googlevideo\.com.*?)"', webpage)]
|
r'\d+,(\d+),(\d+),"(https?://[^.]+\.googleusercontent.com.*?)"', webpage)]
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@@ -18,6 +18,8 @@ class GrouponIE(InfoExtractor):
|
|||||||
'id': 'tubGNycTo_9Uxg82uESj4i61EYX8nyuf',
|
'id': 'tubGNycTo_9Uxg82uESj4i61EYX8nyuf',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Bikram Yoga Huntington Beach | Orange County',
|
'title': 'Bikram Yoga Huntington Beach | Orange County',
|
||||||
|
'description': 'md5:d41d8cd98f00b204e9800998ecf8427e',
|
||||||
|
'duration': 44.961,
|
||||||
},
|
},
|
||||||
}],
|
}],
|
||||||
'params': {
|
'params': {
|
||||||
|
@@ -4,12 +4,10 @@ from __future__ import unicode_literals
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import compat_urlparse
|
||||||
compat_urllib_request,
|
|
||||||
compat_urlparse,
|
|
||||||
)
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
HEADRequest,
|
HEADRequest,
|
||||||
|
sanitized_Request,
|
||||||
str_to_int,
|
str_to_int,
|
||||||
urlencode_postdata,
|
urlencode_postdata,
|
||||||
urlhandle_detect_ext,
|
urlhandle_detect_ext,
|
||||||
@@ -47,7 +45,7 @@ class HearThisAtIE(InfoExtractor):
|
|||||||
r'intTrackId\s*=\s*(\d+)', webpage, 'track ID')
|
r'intTrackId\s*=\s*(\d+)', webpage, 'track ID')
|
||||||
|
|
||||||
payload = urlencode_postdata({'tracks[]': track_id})
|
payload = urlencode_postdata({'tracks[]': track_id})
|
||||||
req = compat_urllib_request.Request(self._PLAYLIST_URL, payload)
|
req = sanitized_Request(self._PLAYLIST_URL, payload)
|
||||||
req.add_header('Content-type', 'application/x-www-form-urlencoded')
|
req.add_header('Content-type', 'application/x-www-form-urlencoded')
|
||||||
|
|
||||||
track = self._download_json(req, track_id, 'Downloading playlist')[0]
|
track = self._download_json(req, track_id, 'Downloading playlist')[0]
|
||||||
|
@@ -3,13 +3,11 @@ from __future__ import unicode_literals
|
|||||||
import base64
|
import base64
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import compat_urllib_parse
|
||||||
compat_urllib_parse,
|
|
||||||
compat_urllib_request,
|
|
||||||
)
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
HEADRequest,
|
HEADRequest,
|
||||||
|
sanitized_Request,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -41,7 +39,7 @@ class HotNewHipHopIE(InfoExtractor):
|
|||||||
('mediaType', 's'),
|
('mediaType', 's'),
|
||||||
('mediaId', video_id),
|
('mediaId', video_id),
|
||||||
])
|
])
|
||||||
r = compat_urllib_request.Request(
|
r = sanitized_Request(
|
||||||
'http://www.hotnewhiphop.com/ajax/media/getActions/', data=reqdata)
|
'http://www.hotnewhiphop.com/ajax/media/getActions/', data=reqdata)
|
||||||
r.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
r.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
||||||
mkd = self._download_json(
|
mkd = self._download_json(
|
||||||
|
@@ -16,6 +16,7 @@ class HowcastIE(InfoExtractor):
|
|||||||
'description': 'md5:dbe792e5f6f1489027027bf2eba188a3',
|
'description': 'md5:dbe792e5f6f1489027027bf2eba188a3',
|
||||||
'timestamp': 1276081287,
|
'timestamp': 1276081287,
|
||||||
'upload_date': '20100609',
|
'upload_date': '20100609',
|
||||||
|
'duration': 56.823,
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
# m3u8 download
|
# m3u8 download
|
||||||
|
@@ -4,12 +4,10 @@ import json
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import compat_urllib_parse
|
||||||
compat_urllib_parse,
|
|
||||||
compat_urllib_request,
|
|
||||||
)
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
|
sanitized_Request,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -30,15 +28,12 @@ class HypemIE(InfoExtractor):
|
|||||||
track_id = self._match_id(url)
|
track_id = self._match_id(url)
|
||||||
|
|
||||||
data = {'ax': 1, 'ts': time.time()}
|
data = {'ax': 1, 'ts': time.time()}
|
||||||
data_encoded = compat_urllib_parse.urlencode(data)
|
request = sanitized_Request(url + '?' + compat_urllib_parse.urlencode(data))
|
||||||
complete_url = url + "?" + data_encoded
|
|
||||||
request = compat_urllib_request.Request(complete_url)
|
|
||||||
response, urlh = self._download_webpage_handle(
|
response, urlh = self._download_webpage_handle(
|
||||||
request, track_id, 'Downloading webpage with the url')
|
request, track_id, 'Downloading webpage with the url')
|
||||||
cookie = urlh.headers.get('Set-Cookie', '')
|
|
||||||
|
|
||||||
html_tracks = self._html_search_regex(
|
html_tracks = self._html_search_regex(
|
||||||
r'(?ms)<script type="application/json" id="displayList-data">\s*(.*?)\s*</script>',
|
r'(?ms)<script type="application/json" id="displayList-data">(.+?)</script>',
|
||||||
response, 'tracks')
|
response, 'tracks')
|
||||||
try:
|
try:
|
||||||
track_list = json.loads(html_tracks)
|
track_list = json.loads(html_tracks)
|
||||||
@@ -48,15 +43,14 @@ class HypemIE(InfoExtractor):
|
|||||||
|
|
||||||
key = track['key']
|
key = track['key']
|
||||||
track_id = track['id']
|
track_id = track['id']
|
||||||
artist = track['artist']
|
|
||||||
title = track['song']
|
title = track['song']
|
||||||
|
|
||||||
serve_url = "http://hypem.com/serve/source/%s/%s" % (track_id, key)
|
request = sanitized_Request(
|
||||||
request = compat_urllib_request.Request(
|
'http://hypem.com/serve/source/%s/%s' % (track_id, key),
|
||||||
serve_url, '', {'Content-Type': 'application/json'})
|
'', {'Content-Type': 'application/json'})
|
||||||
request.add_header('cookie', cookie)
|
|
||||||
song_data = self._download_json(request, track_id, 'Downloading metadata')
|
song_data = self._download_json(request, track_id, 'Downloading metadata')
|
||||||
final_url = song_data["url"]
|
final_url = song_data['url']
|
||||||
|
artist = track.get('artist')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': track_id,
|
'id': track_id,
|
||||||
|
@@ -3,10 +3,7 @@ from __future__ import unicode_literals
|
|||||||
import base64
|
import base64
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import compat_urllib_parse_unquote
|
||||||
compat_urllib_parse_unquote,
|
|
||||||
compat_urlparse,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class InfoQIE(InfoExtractor):
|
class InfoQIE(InfoExtractor):
|
||||||
@@ -45,9 +42,11 @@ class InfoQIE(InfoExtractor):
|
|||||||
video_filename = playpath.split('/')[-1]
|
video_filename = playpath.split('/')[-1]
|
||||||
video_id, extension = video_filename.split('.')
|
video_id, extension = video_filename.split('.')
|
||||||
|
|
||||||
http_base = self._search_regex(
|
http_video_url = self._search_regex(r'P\.s\s*=\s*\'([^\']+)\'', webpage, 'video URL')
|
||||||
r'EXPRESSINSTALL_SWF\s*=\s*[^"]*"((?:https?:)?//[^/"]+/)', webpage,
|
|
||||||
'HTTP base URL')
|
policy = self._search_regex(r'InfoQConstants.scp\s*=\s*\'([^\']+)\'', webpage, 'policy')
|
||||||
|
signature = self._search_regex(r'InfoQConstants.scs\s*=\s*\'([^\']+)\'', webpage, 'signature')
|
||||||
|
key_pair_id = self._search_regex(r'InfoQConstants.sck\s*=\s*\'([^\']+)\'', webpage, 'key-pair-id')
|
||||||
|
|
||||||
formats = [{
|
formats = [{
|
||||||
'format_id': 'rtmp',
|
'format_id': 'rtmp',
|
||||||
@@ -56,7 +55,11 @@ class InfoQIE(InfoExtractor):
|
|||||||
'play_path': playpath,
|
'play_path': playpath,
|
||||||
}, {
|
}, {
|
||||||
'format_id': 'http',
|
'format_id': 'http',
|
||||||
'url': compat_urlparse.urljoin(url, http_base) + real_id,
|
'url': http_video_url,
|
||||||
|
'http_headers': {
|
||||||
|
'Cookie': 'CloudFront-Policy=%s; CloudFront-Signature=%s; CloudFront-Key-Pair-Id=%s' % (
|
||||||
|
policy, signature, key_pair_id),
|
||||||
|
},
|
||||||
}]
|
}]
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
@@ -10,8 +10,8 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class InstagramIE(InfoExtractor):
|
class InstagramIE(InfoExtractor):
|
||||||
_VALID_URL = r'https://instagram\.com/p/(?P<id>[\da-zA-Z]+)'
|
_VALID_URL = r'https?://(?:www\.)?instagram\.com/p/(?P<id>[^/?#&]+)'
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
'url': 'https://instagram.com/p/aye83DjauH/?foo=bar#abc',
|
'url': 'https://instagram.com/p/aye83DjauH/?foo=bar#abc',
|
||||||
'md5': '0d2da106a9d2631273e192b372806516',
|
'md5': '0d2da106a9d2631273e192b372806516',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@@ -21,7 +21,10 @@ class InstagramIE(InfoExtractor):
|
|||||||
'title': 'Video by naomipq',
|
'title': 'Video by naomipq',
|
||||||
'description': 'md5:1f17f0ab29bd6fe2bfad705f58de3cb8',
|
'description': 'md5:1f17f0ab29bd6fe2bfad705f58de3cb8',
|
||||||
}
|
}
|
||||||
}
|
}, {
|
||||||
|
'url': 'https://instagram.com/p/-Cmh1cukG2/',
|
||||||
|
'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)
|
||||||
|
@@ -6,12 +6,10 @@ from random import random
|
|||||||
from math import floor
|
from math import floor
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
|
||||||
compat_urllib_request,
|
|
||||||
)
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
remove_end,
|
remove_end,
|
||||||
|
sanitized_Request,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -61,7 +59,7 @@ class IPrimaIE(InfoExtractor):
|
|||||||
(floor(random() * 1073741824), floor(random() * 1073741824))
|
(floor(random() * 1073741824), floor(random() * 1073741824))
|
||||||
)
|
)
|
||||||
|
|
||||||
req = compat_urllib_request.Request(player_url)
|
req = sanitized_Request(player_url)
|
||||||
req.add_header('Referer', url)
|
req.add_header('Referer', url)
|
||||||
playerpage = self._download_webpage(req, video_id)
|
playerpage = self._download_webpage(req, video_id)
|
||||||
|
|
||||||
|
@@ -205,9 +205,8 @@ class IqiyiIE(InfoExtractor):
|
|||||||
|
|
||||||
def get_enc_key(self, swf_url, video_id):
|
def get_enc_key(self, swf_url, video_id):
|
||||||
# TODO: automatic key extraction
|
# TODO: automatic key extraction
|
||||||
# last update at 2015-10-22 for Zombie::bite
|
# last update at 2015-12-06 for Zombie::bite
|
||||||
# '7223c67061dbea1259d0ceb44f44b6d62288f4f80c972170de5201d2321060270e05'[2:66][0::2]
|
enc_key = '3719f6a1da83ee0aee3488d8802d7696'[::-1]
|
||||||
enc_key = '2c76de15dcb44bd28ff0927d50d31620'
|
|
||||||
return enc_key
|
return enc_key
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
|
@@ -5,11 +5,9 @@ import re
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
|
||||||
compat_urllib_request,
|
|
||||||
)
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
|
sanitized_Request,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -78,7 +76,7 @@ class IviIE(InfoExtractor):
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
request = compat_urllib_request.Request(api_url, json.dumps(data))
|
request = sanitized_Request(api_url, json.dumps(data))
|
||||||
|
|
||||||
video_json_page = self._download_webpage(
|
video_json_page = self._download_webpage(
|
||||||
request, video_id, 'Downloading video JSON')
|
request, video_id, 'Downloading video JSON')
|
||||||
|
@@ -2,12 +2,18 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import base64
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import compat_urllib_parse
|
from ..compat import (
|
||||||
|
compat_urllib_parse,
|
||||||
|
compat_urlparse,
|
||||||
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
|
clean_html,
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
|
unsmuggle_url,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -16,7 +22,7 @@ class KalturaIE(InfoExtractor):
|
|||||||
(?:
|
(?:
|
||||||
kaltura:(?P<partner_id_s>\d+):(?P<id_s>[0-9a-z_]+)|
|
kaltura:(?P<partner_id_s>\d+):(?P<id_s>[0-9a-z_]+)|
|
||||||
https?://
|
https?://
|
||||||
(:?(?:www|cdnapisec)\.)?kaltura\.com/
|
(:?(?:www|cdnapi(?:sec)?)\.)?kaltura\.com/
|
||||||
(?:
|
(?:
|
||||||
(?:
|
(?:
|
||||||
# flash player
|
# flash player
|
||||||
@@ -121,31 +127,47 @@ class KalturaIE(InfoExtractor):
|
|||||||
video_id, actions, note='Downloading video info JSON')
|
video_id, actions, note='Downloading video info JSON')
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
|
url, smuggled_data = unsmuggle_url(url, {})
|
||||||
|
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
partner_id = mobj.group('partner_id_s') or mobj.group('partner_id') or mobj.group('partner_id_html5')
|
partner_id = mobj.group('partner_id_s') or mobj.group('partner_id') or mobj.group('partner_id_html5')
|
||||||
entry_id = mobj.group('id_s') or mobj.group('id') or mobj.group('id_html5')
|
entry_id = mobj.group('id_s') or mobj.group('id') or mobj.group('id_html5')
|
||||||
|
|
||||||
info, source_data = self._get_video_info(entry_id, partner_id)
|
info, source_data = self._get_video_info(entry_id, partner_id)
|
||||||
|
|
||||||
formats = [{
|
source_url = smuggled_data.get('source_url')
|
||||||
'format_id': '%(fileExt)s-%(bitrate)s' % f,
|
if source_url:
|
||||||
'ext': f['fileExt'],
|
referrer = base64.b64encode(
|
||||||
'tbr': f['bitrate'],
|
'://'.join(compat_urlparse.urlparse(source_url)[:2])
|
||||||
'fps': f.get('frameRate'),
|
.encode('utf-8')).decode('utf-8')
|
||||||
'filesize_approx': int_or_none(f.get('size'), invscale=1024),
|
else:
|
||||||
'container': f.get('containerFormat'),
|
referrer = None
|
||||||
'vcodec': f.get('videoCodecId'),
|
|
||||||
'height': f.get('height'),
|
formats = []
|
||||||
'width': f.get('width'),
|
for f in source_data['flavorAssets']:
|
||||||
'url': '%s/flavorId/%s' % (info['dataUrl'], f['id']),
|
video_url = '%s/flavorId/%s' % (info['dataUrl'], f['id'])
|
||||||
} for f in source_data['flavorAssets']]
|
if referrer:
|
||||||
|
video_url += '?referrer=%s' % referrer
|
||||||
|
formats.append({
|
||||||
|
'format_id': '%(fileExt)s-%(bitrate)s' % f,
|
||||||
|
'ext': f.get('fileExt'),
|
||||||
|
'tbr': int_or_none(f['bitrate']),
|
||||||
|
'fps': int_or_none(f.get('frameRate')),
|
||||||
|
'filesize_approx': int_or_none(f.get('size'), invscale=1024),
|
||||||
|
'container': f.get('containerFormat'),
|
||||||
|
'vcodec': f.get('videoCodecId'),
|
||||||
|
'height': int_or_none(f.get('height')),
|
||||||
|
'width': int_or_none(f.get('width')),
|
||||||
|
'url': video_url,
|
||||||
|
})
|
||||||
|
self._check_formats(formats, entry_id)
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': entry_id,
|
'id': entry_id,
|
||||||
'title': info['name'],
|
'title': info['name'],
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
'description': info.get('description'),
|
'description': clean_html(info.get('description')),
|
||||||
'thumbnail': info.get('thumbnailUrl'),
|
'thumbnail': info.get('thumbnailUrl'),
|
||||||
'duration': info.get('duration'),
|
'duration': info.get('duration'),
|
||||||
'timestamp': info.get('createdAt'),
|
'timestamp': info.get('createdAt'),
|
||||||
|
@@ -1,12 +1,11 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import os
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..utils import (
|
||||||
compat_urllib_parse_urlparse,
|
sanitized_Request,
|
||||||
compat_urllib_request,
|
url_basename,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -14,19 +13,20 @@ class KeezMoviesIE(InfoExtractor):
|
|||||||
_VALID_URL = r'https?://(?:www\.)?keezmovies\.com/video/.+?(?P<id>[0-9]+)(?:[/?&]|$)'
|
_VALID_URL = r'https?://(?:www\.)?keezmovies\.com/video/.+?(?P<id>[0-9]+)(?:[/?&]|$)'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.keezmovies.com/video/petite-asian-lady-mai-playing-in-bathtub-1214711',
|
'url': 'http://www.keezmovies.com/video/petite-asian-lady-mai-playing-in-bathtub-1214711',
|
||||||
'md5': '6e297b7e789329923fcf83abb67c9289',
|
'md5': '1c1e75d22ffa53320f45eeb07bc4cdc0',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '1214711',
|
'id': '1214711',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Petite Asian Lady Mai Playing In Bathtub',
|
'title': 'Petite Asian Lady Mai Playing In Bathtub',
|
||||||
'age_limit': 18,
|
'age_limit': 18,
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
req = compat_urllib_request.Request(url)
|
req = sanitized_Request(url)
|
||||||
req.add_header('Cookie', 'age_verified=1')
|
req.add_header('Cookie', 'age_verified=1')
|
||||||
webpage = self._download_webpage(req, video_id)
|
webpage = self._download_webpage(req, video_id)
|
||||||
|
|
||||||
@@ -38,21 +38,29 @@ class KeezMoviesIE(InfoExtractor):
|
|||||||
|
|
||||||
video_title = self._html_search_regex(
|
video_title = self._html_search_regex(
|
||||||
r'<h1 [^>]*>([^<]+)', webpage, 'title')
|
r'<h1 [^>]*>([^<]+)', webpage, 'title')
|
||||||
video_url = self._html_search_regex(
|
flashvars = self._parse_json(self._search_regex(
|
||||||
r'(?s)html5VideoPlayer = .*?src="([^"]+)"', webpage, 'video URL')
|
r'var\s+flashvars\s*=\s*([^;]+);', webpage, 'flashvars'), video_id)
|
||||||
path = compat_urllib_parse_urlparse(video_url).path
|
|
||||||
extension = os.path.splitext(path)[1][1:]
|
formats = []
|
||||||
format = path.split('/')[4].split('_')[:2]
|
for height in (180, 240, 480):
|
||||||
format = "-".join(format)
|
if flashvars.get('quality_%dp' % height):
|
||||||
|
video_url = flashvars['quality_%dp' % height]
|
||||||
|
a_format = {
|
||||||
|
'url': video_url,
|
||||||
|
'height': height,
|
||||||
|
'format_id': '%dp' % height,
|
||||||
|
}
|
||||||
|
filename_parts = url_basename(video_url).split('_')
|
||||||
|
if len(filename_parts) >= 2 and re.match(r'\d+[Kk]', filename_parts[1]):
|
||||||
|
a_format['tbr'] = int(filename_parts[1][:-1])
|
||||||
|
formats.append(a_format)
|
||||||
|
|
||||||
age_limit = self._rta_search(webpage)
|
age_limit = self._rta_search(webpage)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': video_title,
|
'title': video_title,
|
||||||
'url': video_url,
|
'formats': formats,
|
||||||
'ext': extension,
|
|
||||||
'format': format,
|
|
||||||
'format_id': format,
|
|
||||||
'age_limit': age_limit,
|
'age_limit': age_limit,
|
||||||
|
'thumbnail': flashvars.get('image_url')
|
||||||
}
|
}
|
||||||
|
@@ -8,13 +8,13 @@ import time
|
|||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import (
|
||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
compat_urllib_request,
|
|
||||||
compat_ord,
|
compat_ord,
|
||||||
)
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
determine_ext,
|
determine_ext,
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
parse_iso8601,
|
parse_iso8601,
|
||||||
|
sanitized_Request,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
encode_data_uri,
|
encode_data_uri,
|
||||||
)
|
)
|
||||||
@@ -114,7 +114,7 @@ class LetvIE(InfoExtractor):
|
|||||||
'tkey': self.calc_time_key(int(time.time())),
|
'tkey': self.calc_time_key(int(time.time())),
|
||||||
'domain': 'www.letv.com'
|
'domain': 'www.letv.com'
|
||||||
}
|
}
|
||||||
play_json_req = compat_urllib_request.Request(
|
play_json_req = sanitized_Request(
|
||||||
'http://api.letv.com/mms/out/video/playJson?' + compat_urllib_parse.urlencode(params)
|
'http://api.letv.com/mms/out/video/playJson?' + compat_urllib_parse.urlencode(params)
|
||||||
)
|
)
|
||||||
cn_verification_proxy = self._downloader.params.get('cn_verification_proxy')
|
cn_verification_proxy = self._downloader.params.get('cn_verification_proxy')
|
||||||
|
@@ -7,12 +7,12 @@ from .common import InfoExtractor
|
|||||||
from ..compat import (
|
from ..compat import (
|
||||||
compat_str,
|
compat_str,
|
||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
compat_urllib_request,
|
|
||||||
)
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
clean_html,
|
clean_html,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
|
sanitized_Request,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ class LyndaBaseIE(InfoExtractor):
|
|||||||
self._login()
|
self._login()
|
||||||
|
|
||||||
def _login(self):
|
def _login(self):
|
||||||
(username, password) = self._get_login_info()
|
username, password = self._get_login_info()
|
||||||
if username is None:
|
if username is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ class LyndaBaseIE(InfoExtractor):
|
|||||||
'remember': 'false',
|
'remember': 'false',
|
||||||
'stayPut': 'false'
|
'stayPut': 'false'
|
||||||
}
|
}
|
||||||
request = compat_urllib_request.Request(
|
request = sanitized_Request(
|
||||||
self._LOGIN_URL, compat_urllib_parse.urlencode(login_form).encode('utf-8'))
|
self._LOGIN_URL, compat_urllib_parse.urlencode(login_form).encode('utf-8'))
|
||||||
login_page = self._download_webpage(
|
login_page = self._download_webpage(
|
||||||
request, None, 'Logging in as %s' % username)
|
request, None, 'Logging in as %s' % username)
|
||||||
@@ -64,7 +64,7 @@ class LyndaBaseIE(InfoExtractor):
|
|||||||
'remember': 'false',
|
'remember': 'false',
|
||||||
'stayPut': 'false',
|
'stayPut': 'false',
|
||||||
}
|
}
|
||||||
request = compat_urllib_request.Request(
|
request = sanitized_Request(
|
||||||
self._LOGIN_URL, compat_urllib_parse.urlencode(confirm_form).encode('utf-8'))
|
self._LOGIN_URL, compat_urllib_parse.urlencode(confirm_form).encode('utf-8'))
|
||||||
login_page = self._download_webpage(
|
login_page = self._download_webpage(
|
||||||
request, None,
|
request, None,
|
||||||
@@ -82,6 +82,15 @@ class LyndaBaseIE(InfoExtractor):
|
|||||||
expected=True)
|
expected=True)
|
||||||
raise ExtractorError('Unable to log in')
|
raise ExtractorError('Unable to log in')
|
||||||
|
|
||||||
|
def _logout(self):
|
||||||
|
username, _ = self._get_login_info()
|
||||||
|
if username is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._download_webpage(
|
||||||
|
'http://www.lynda.com/ajax/logout.aspx', None,
|
||||||
|
'Logging out', 'Unable to log out', fatal=False)
|
||||||
|
|
||||||
|
|
||||||
class LyndaIE(LyndaBaseIE):
|
class LyndaIE(LyndaBaseIE):
|
||||||
IE_NAME = 'lynda'
|
IE_NAME = 'lynda'
|
||||||
@@ -108,51 +117,47 @@ class LyndaIE(LyndaBaseIE):
|
|||||||
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(
|
video = self._download_json(
|
||||||
'http://www.lynda.com/ajax/player?videoId=%s&type=video' % video_id,
|
'http://www.lynda.com/ajax/player?videoId=%s&type=video' % video_id,
|
||||||
video_id, 'Downloading video JSON')
|
video_id, 'Downloading video JSON')
|
||||||
video_json = json.loads(page)
|
|
||||||
|
|
||||||
if 'Status' in video_json:
|
if 'Status' in video:
|
||||||
raise ExtractorError(
|
raise ExtractorError(
|
||||||
'lynda returned error: %s' % video_json['Message'], expected=True)
|
'lynda returned error: %s' % video['Message'], expected=True)
|
||||||
|
|
||||||
if video_json['HasAccess'] is False:
|
if video.get('HasAccess') is False:
|
||||||
self.raise_login_required('Video %s is only available for members' % video_id)
|
self.raise_login_required('Video %s is only available for members' % video_id)
|
||||||
|
|
||||||
video_id = compat_str(video_json['ID'])
|
video_id = compat_str(video.get('ID') or video_id)
|
||||||
duration = video_json['DurationInSeconds']
|
duration = int_or_none(video.get('DurationInSeconds'))
|
||||||
title = video_json['Title']
|
title = video['Title']
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
|
|
||||||
fmts = video_json.get('Formats')
|
fmts = video.get('Formats')
|
||||||
if fmts:
|
if fmts:
|
||||||
formats.extend([
|
formats.extend([{
|
||||||
{
|
'url': f['Url'],
|
||||||
'url': fmt['Url'],
|
'ext': f.get('Extension'),
|
||||||
'ext': fmt['Extension'],
|
'width': int_or_none(f.get('Width')),
|
||||||
'width': fmt['Width'],
|
'height': int_or_none(f.get('Height')),
|
||||||
'height': fmt['Height'],
|
'filesize': int_or_none(f.get('FileSize')),
|
||||||
'filesize': fmt['FileSize'],
|
'format_id': compat_str(f.get('Resolution')) if f.get('Resolution') else None,
|
||||||
'format_id': str(fmt['Resolution'])
|
} for f in fmts if f.get('Url')])
|
||||||
} for fmt in fmts])
|
|
||||||
|
|
||||||
prioritized_streams = video_json.get('PrioritizedStreams')
|
prioritized_streams = video.get('PrioritizedStreams')
|
||||||
if prioritized_streams:
|
if prioritized_streams:
|
||||||
for prioritized_stream_id, prioritized_stream in prioritized_streams.items():
|
for prioritized_stream_id, prioritized_stream in prioritized_streams.items():
|
||||||
formats.extend([
|
formats.extend([{
|
||||||
{
|
'url': video_url,
|
||||||
'url': video_url,
|
'width': int_or_none(format_id),
|
||||||
'width': int_or_none(format_id),
|
'format_id': '%s-%s' % (prioritized_stream_id, format_id),
|
||||||
'format_id': '%s-%s' % (prioritized_stream_id, format_id),
|
} for format_id, video_url in prioritized_stream.items()])
|
||||||
} for format_id, video_url in prioritized_stream.items()
|
|
||||||
])
|
|
||||||
|
|
||||||
self._check_formats(formats, video_id)
|
self._check_formats(formats, video_id)
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
subtitles = self.extract_subtitles(video_id, page)
|
subtitles = self.extract_subtitles(video_id)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
@@ -183,7 +188,7 @@ class LyndaIE(LyndaBaseIE):
|
|||||||
if srt:
|
if srt:
|
||||||
return srt
|
return srt
|
||||||
|
|
||||||
def _get_subtitles(self, video_id, webpage):
|
def _get_subtitles(self, video_id):
|
||||||
url = 'http://www.lynda.com/ajax/player?videoId=%s&type=transcript' % video_id
|
url = 'http://www.lynda.com/ajax/player?videoId=%s&type=transcript' % video_id
|
||||||
subs = self._download_json(url, None, False)
|
subs = self._download_json(url, None, False)
|
||||||
if subs:
|
if subs:
|
||||||
@@ -205,12 +210,13 @@ class LyndaCourseIE(LyndaBaseIE):
|
|||||||
course_path = mobj.group('coursepath')
|
course_path = mobj.group('coursepath')
|
||||||
course_id = mobj.group('courseid')
|
course_id = mobj.group('courseid')
|
||||||
|
|
||||||
page = self._download_webpage(
|
course = self._download_json(
|
||||||
'http://www.lynda.com/ajax/player?courseId=%s&type=course' % course_id,
|
'http://www.lynda.com/ajax/player?courseId=%s&type=course' % course_id,
|
||||||
course_id, 'Downloading course JSON')
|
course_id, 'Downloading course JSON')
|
||||||
course_json = json.loads(page)
|
|
||||||
|
|
||||||
if 'Status' in course_json and course_json['Status'] == 'NotFound':
|
self._logout()
|
||||||
|
|
||||||
|
if course.get('Status') == 'NotFound':
|
||||||
raise ExtractorError(
|
raise ExtractorError(
|
||||||
'Course %s does not exist' % course_id, expected=True)
|
'Course %s does not exist' % course_id, expected=True)
|
||||||
|
|
||||||
@@ -220,12 +226,13 @@ class LyndaCourseIE(LyndaBaseIE):
|
|||||||
# Might want to extract videos right here from video['Formats'] as it seems 'Formats' is not provided
|
# Might want to extract videos right here from video['Formats'] as it seems 'Formats' is not provided
|
||||||
# by single video API anymore
|
# by single video API anymore
|
||||||
|
|
||||||
for chapter in course_json['Chapters']:
|
for chapter in course['Chapters']:
|
||||||
for video in chapter['Videos']:
|
for video in chapter.get('Videos', []):
|
||||||
if video['HasAccess'] is False:
|
if video.get('HasAccess') is False:
|
||||||
unaccessible_videos += 1
|
unaccessible_videos += 1
|
||||||
continue
|
continue
|
||||||
videos.append(video['ID'])
|
if video.get('ID'):
|
||||||
|
videos.append(video['ID'])
|
||||||
|
|
||||||
if unaccessible_videos > 0:
|
if unaccessible_videos > 0:
|
||||||
self._downloader.report_warning(
|
self._downloader.report_warning(
|
||||||
@@ -238,6 +245,6 @@ class LyndaCourseIE(LyndaBaseIE):
|
|||||||
'Lynda')
|
'Lynda')
|
||||||
for video_id in videos]
|
for video_id in videos]
|
||||||
|
|
||||||
course_title = course_json['Title']
|
course_title = course.get('Title')
|
||||||
|
|
||||||
return self.playlist_result(entries, course_id, course_title)
|
return self.playlist_result(entries, course_id, course_title)
|
||||||
|
@@ -1,64 +1,169 @@
|
|||||||
|
# coding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from ..compat import compat_urlparse
|
||||||
|
from ..utils import (
|
||||||
|
determine_ext,
|
||||||
|
int_or_none,
|
||||||
|
parse_duration,
|
||||||
|
parse_iso8601,
|
||||||
|
xpath_text,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class MDRIE(InfoExtractor):
|
class MDRIE(InfoExtractor):
|
||||||
_VALID_URL = r'^(?P<domain>https?://(?:www\.)?mdr\.de)/(?:.*)/(?P<type>video|audio)(?P<video_id>[^/_]+)(?:_|\.html)'
|
IE_DESC = 'MDR.DE and KiKA'
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?(?:mdr|kika)\.de/(?:.*)/[a-z]+(?P<id>\d+)(?:_.+?)?\.html'
|
||||||
|
|
||||||
# No tests, MDR regularily deletes its videos
|
_TESTS = [{
|
||||||
_TEST = {
|
# MDR regularily deletes its videos
|
||||||
'url': 'http://www.mdr.de/fakt/video189002.html',
|
'url': 'http://www.mdr.de/fakt/video189002.html',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
}
|
}, {
|
||||||
|
# audio
|
||||||
|
'url': 'http://www.mdr.de/kultur/audio1312272_zc-15948bad_zs-86171fdd.html',
|
||||||
|
'md5': '64c4ee50f0a791deb9479cd7bbe9d2fa',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '1312272',
|
||||||
|
'ext': 'mp3',
|
||||||
|
'title': 'Feuilleton vom 30. Oktober 2015',
|
||||||
|
'duration': 250,
|
||||||
|
'uploader': 'MITTELDEUTSCHER RUNDFUNK',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.kika.de/baumhaus/videos/video19636.html',
|
||||||
|
'md5': '4930515e36b06c111213e80d1e4aad0e',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '19636',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Baumhaus vom 30. Oktober 2015',
|
||||||
|
'duration': 134,
|
||||||
|
'uploader': 'KIKA',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.kika.de/sendungen/einzelsendungen/weihnachtsprogramm/videos/video8182.html',
|
||||||
|
'md5': '5fe9c4dd7d71e3b238f04b8fdd588357',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '8182',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Beutolomäus und der geheime Weihnachtswunsch',
|
||||||
|
'description': 'md5:b69d32d7b2c55cbe86945ab309d39bbd',
|
||||||
|
'timestamp': 1419047100,
|
||||||
|
'upload_date': '20141220',
|
||||||
|
'duration': 4628,
|
||||||
|
'uploader': 'KIKA',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.kika.de/baumhaus/sendungen/video19636_zc-fea7f8a0_zs-4bf89c60.html',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.kika.de/sendungen/einzelsendungen/weihnachtsprogramm/einzelsendung2534.html',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
m = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = m.group('video_id')
|
|
||||||
domain = m.group('domain')
|
|
||||||
|
|
||||||
# determine title and media streams from webpage
|
webpage = self._download_webpage(url, video_id)
|
||||||
html = self._download_webpage(url, video_id)
|
|
||||||
|
|
||||||
title = self._html_search_regex(r'<h[12]>(.*?)</h[12]>', html, 'title')
|
data_url = self._search_regex(
|
||||||
xmlurl = self._search_regex(
|
r'dataURL\s*:\s*(["\'])(?P<url>/.+/(?:video|audio)[0-9]+-avCustom\.xml)\1',
|
||||||
r'dataURL:\'(/(?:.+)/(?:video|audio)[0-9]+-avCustom.xml)', html, 'XML URL')
|
webpage, 'data url', group='url')
|
||||||
|
|
||||||
|
doc = self._download_xml(
|
||||||
|
compat_urlparse.urljoin(url, data_url), video_id)
|
||||||
|
|
||||||
|
title = xpath_text(doc, ['./title', './broadcast/broadcastName'], 'title', fatal=True)
|
||||||
|
|
||||||
doc = self._download_xml(domain + xmlurl, video_id)
|
|
||||||
formats = []
|
formats = []
|
||||||
for a in doc.findall('./assets/asset'):
|
processed_urls = []
|
||||||
url_el = a.find('./progressiveDownloadUrl')
|
for asset in doc.findall('./assets/asset'):
|
||||||
if url_el is None:
|
for source in (
|
||||||
continue
|
'progressiveDownload',
|
||||||
abr = int(a.find('bitrateAudio').text) // 1000
|
'dynamicHttpStreamingRedirector',
|
||||||
media_type = a.find('mediaType').text
|
'adaptiveHttpStreamingRedirector'):
|
||||||
format = {
|
url_el = asset.find('./%sUrl' % source)
|
||||||
'abr': abr,
|
if url_el is None:
|
||||||
'filesize': int(a.find('fileSize').text),
|
continue
|
||||||
'url': url_el.text,
|
|
||||||
}
|
video_url = url_el.text
|
||||||
|
if video_url in processed_urls:
|
||||||
|
continue
|
||||||
|
|
||||||
|
processed_urls.append(video_url)
|
||||||
|
|
||||||
|
vbr = int_or_none(xpath_text(asset, './bitrateVideo', 'vbr'), 1000)
|
||||||
|
abr = int_or_none(xpath_text(asset, './bitrateAudio', 'abr'), 1000)
|
||||||
|
|
||||||
|
ext = determine_ext(url_el.text)
|
||||||
|
if ext == 'm3u8':
|
||||||
|
url_formats = self._extract_m3u8_formats(
|
||||||
|
video_url, video_id, 'mp4', entry_protocol='m3u8_native',
|
||||||
|
preference=0, m3u8_id='HLS', fatal=False)
|
||||||
|
elif ext == 'f4m':
|
||||||
|
url_formats = self._extract_f4m_formats(
|
||||||
|
video_url + '?hdcore=3.7.0&plugin=aasp-3.7.0.39.44', video_id,
|
||||||
|
preference=0, f4m_id='HDS', fatal=False)
|
||||||
|
else:
|
||||||
|
media_type = xpath_text(asset, './mediaType', 'media type', default='MP4')
|
||||||
|
vbr = int_or_none(xpath_text(asset, './bitrateVideo', 'vbr'), 1000)
|
||||||
|
abr = int_or_none(xpath_text(asset, './bitrateAudio', 'abr'), 1000)
|
||||||
|
filesize = int_or_none(xpath_text(asset, './fileSize', 'file size'))
|
||||||
|
|
||||||
|
f = {
|
||||||
|
'url': video_url,
|
||||||
|
'format_id': '%s-%d' % (media_type, vbr or abr),
|
||||||
|
'filesize': filesize,
|
||||||
|
'abr': abr,
|
||||||
|
'preference': 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
if vbr:
|
||||||
|
width = int_or_none(xpath_text(asset, './frameWidth', 'width'))
|
||||||
|
height = int_or_none(xpath_text(asset, './frameHeight', 'height'))
|
||||||
|
f.update({
|
||||||
|
'vbr': vbr,
|
||||||
|
'width': width,
|
||||||
|
'height': height,
|
||||||
|
})
|
||||||
|
|
||||||
|
url_formats = [f]
|
||||||
|
|
||||||
|
if not url_formats:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not vbr:
|
||||||
|
for f in url_formats:
|
||||||
|
abr = f.get('tbr') or abr
|
||||||
|
if 'tbr' in f:
|
||||||
|
del f['tbr']
|
||||||
|
f.update({
|
||||||
|
'abr': abr,
|
||||||
|
'vcodec': 'none',
|
||||||
|
})
|
||||||
|
|
||||||
|
formats.extend(url_formats)
|
||||||
|
|
||||||
vbr_el = a.find('bitrateVideo')
|
|
||||||
if vbr_el is None:
|
|
||||||
format.update({
|
|
||||||
'vcodec': 'none',
|
|
||||||
'format_id': '%s-%d' % (media_type, abr),
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
vbr = int(vbr_el.text) // 1000
|
|
||||||
format.update({
|
|
||||||
'vbr': vbr,
|
|
||||||
'width': int(a.find('frameWidth').text),
|
|
||||||
'height': int(a.find('frameHeight').text),
|
|
||||||
'format_id': '%s-%d' % (media_type, vbr),
|
|
||||||
})
|
|
||||||
formats.append(format)
|
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
description = xpath_text(doc, './broadcast/broadcastDescription', 'description')
|
||||||
|
timestamp = parse_iso8601(
|
||||||
|
xpath_text(
|
||||||
|
doc, [
|
||||||
|
'./broadcast/broadcastDate',
|
||||||
|
'./broadcast/broadcastStartDate',
|
||||||
|
'./broadcast/broadcastEndDate'],
|
||||||
|
'timestamp', default=None))
|
||||||
|
duration = parse_duration(xpath_text(doc, './duration', 'duration'))
|
||||||
|
uploader = xpath_text(doc, './rights', 'uploader')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': title,
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'timestamp': timestamp,
|
||||||
|
'duration': duration,
|
||||||
|
'uploader': uploader,
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
}
|
}
|
||||||
|
@@ -7,12 +7,12 @@ from ..compat import (
|
|||||||
compat_parse_qs,
|
compat_parse_qs,
|
||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
compat_urllib_parse_unquote,
|
compat_urllib_parse_unquote,
|
||||||
compat_urllib_request,
|
|
||||||
)
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
determine_ext,
|
determine_ext,
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
|
sanitized_Request,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -117,7 +117,7 @@ class MetacafeIE(InfoExtractor):
|
|||||||
'filters': '0',
|
'filters': '0',
|
||||||
'submit': "Continue - I'm over 18",
|
'submit': "Continue - I'm over 18",
|
||||||
}
|
}
|
||||||
request = compat_urllib_request.Request(self._FILTER_POST, compat_urllib_parse.urlencode(disclaimer_form))
|
request = sanitized_Request(self._FILTER_POST, compat_urllib_parse.urlencode(disclaimer_form))
|
||||||
request.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
request.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
||||||
self.report_age_confirmation()
|
self.report_age_confirmation()
|
||||||
self._download_webpage(request, None, False, 'Unable to confirm age')
|
self._download_webpage(request, None, False, 'Unable to confirm age')
|
||||||
@@ -142,7 +142,7 @@ class MetacafeIE(InfoExtractor):
|
|||||||
return self.url_result('theplatform:%s' % ext_id, 'ThePlatform')
|
return self.url_result('theplatform:%s' % ext_id, 'ThePlatform')
|
||||||
|
|
||||||
# Retrieve video webpage to extract further information
|
# Retrieve video webpage to extract further information
|
||||||
req = compat_urllib_request.Request('http://www.metacafe.com/watch/%s/' % video_id)
|
req = sanitized_Request('http://www.metacafe.com/watch/%s/' % video_id)
|
||||||
|
|
||||||
# AnyClip videos require the flashversion cookie so that we get the link
|
# AnyClip videos require the flashversion cookie so that we get the link
|
||||||
# to the mp4 file
|
# to the mp4 file
|
||||||
@@ -154,10 +154,10 @@ class MetacafeIE(InfoExtractor):
|
|||||||
# Extract URL, uploader and title from webpage
|
# Extract URL, uploader and title from webpage
|
||||||
self.report_extraction(video_id)
|
self.report_extraction(video_id)
|
||||||
video_url = None
|
video_url = None
|
||||||
mobj = re.search(r'(?m)&mediaURL=([^&]+)', webpage)
|
mobj = re.search(r'(?m)&(?:media|video)URL=([^&]+)', webpage)
|
||||||
if mobj is not None:
|
if mobj is not None:
|
||||||
mediaURL = compat_urllib_parse_unquote(mobj.group(1))
|
mediaURL = compat_urllib_parse_unquote(mobj.group(1))
|
||||||
video_ext = mediaURL[-3:]
|
video_ext = determine_ext(mediaURL)
|
||||||
|
|
||||||
# Extract gdaKey if available
|
# Extract gdaKey if available
|
||||||
mobj = re.search(r'(?m)&gdaKey=(.*?)&', webpage)
|
mobj = re.search(r'(?m)&gdaKey=(.*?)&', webpage)
|
||||||
@@ -229,7 +229,7 @@ class MetacafeIE(InfoExtractor):
|
|||||||
|
|
||||||
age_limit = (
|
age_limit = (
|
||||||
18
|
18
|
||||||
if re.search(r'"contentRating":"restricted"', webpage)
|
if re.search(r'(?:"contentRating":|"rating",)"restricted"', webpage)
|
||||||
else 0)
|
else 0)
|
||||||
|
|
||||||
if isinstance(video_url, list):
|
if isinstance(video_url, list):
|
||||||
|
@@ -2,14 +2,12 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import compat_urllib_parse
|
||||||
compat_urllib_parse,
|
|
||||||
compat_urllib_request,
|
|
||||||
)
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
int_or_none,
|
int_or_none,
|
||||||
parse_duration,
|
parse_duration,
|
||||||
parse_filesize,
|
parse_filesize,
|
||||||
|
sanitized_Request,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -39,7 +37,7 @@ class MinhatecaIE(InfoExtractor):
|
|||||||
('fileId', video_id),
|
('fileId', video_id),
|
||||||
('__RequestVerificationToken', token),
|
('__RequestVerificationToken', token),
|
||||||
]
|
]
|
||||||
req = compat_urllib_request.Request(
|
req = sanitized_Request(
|
||||||
'http://minhateca.com.br/action/License/Download',
|
'http://minhateca.com.br/action/License/Download',
|
||||||
data=compat_urllib_parse.urlencode(token_data))
|
data=compat_urllib_parse.urlencode(token_data))
|
||||||
req.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
req.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
||||||
|
@@ -8,6 +8,7 @@ from ..utils import (
|
|||||||
xpath_text,
|
xpath_text,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
|
sanitized_Request,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -51,6 +52,8 @@ class MioMioIE(InfoExtractor):
|
|||||||
mioplayer_path = self._search_regex(
|
mioplayer_path = self._search_regex(
|
||||||
r'src="(/mioplayer/[^"]+)"', webpage, 'ref_path')
|
r'src="(/mioplayer/[^"]+)"', webpage, 'ref_path')
|
||||||
|
|
||||||
|
http_headers = {'Referer': 'http://www.miomio.tv%s' % mioplayer_path}
|
||||||
|
|
||||||
xml_config = self._search_regex(
|
xml_config = self._search_regex(
|
||||||
r'flashvars="type=(?:sina|video)&(.+?)&',
|
r'flashvars="type=(?:sina|video)&(.+?)&',
|
||||||
webpage, 'xml config')
|
webpage, 'xml config')
|
||||||
@@ -60,14 +63,12 @@ class MioMioIE(InfoExtractor):
|
|||||||
'http://www.miomio.tv/mioplayer/mioplayerconfigfiles/xml.php?id=%s&r=%s' % (id, random.randint(100, 999)),
|
'http://www.miomio.tv/mioplayer/mioplayerconfigfiles/xml.php?id=%s&r=%s' % (id, random.randint(100, 999)),
|
||||||
video_id)
|
video_id)
|
||||||
|
|
||||||
# the following xml contains the actual configuration information on the video file(s)
|
vid_config_request = sanitized_Request(
|
||||||
vid_config = self._download_xml(
|
|
||||||
'http://www.miomio.tv/mioplayer/mioplayerconfigfiles/sina.php?{0}'.format(xml_config),
|
'http://www.miomio.tv/mioplayer/mioplayerconfigfiles/sina.php?{0}'.format(xml_config),
|
||||||
video_id)
|
headers=http_headers)
|
||||||
|
|
||||||
http_headers = {
|
# the following xml contains the actual configuration information on the video file(s)
|
||||||
'Referer': 'http://www.miomio.tv%s' % mioplayer_path,
|
vid_config = self._download_xml(vid_config_request, video_id)
|
||||||
}
|
|
||||||
|
|
||||||
if not int_or_none(xpath_text(vid_config, 'timelength')):
|
if not int_or_none(xpath_text(vid_config, 'timelength')):
|
||||||
raise ExtractorError('Unable to load videos!', expected=True)
|
raise ExtractorError('Unable to load videos!', expected=True)
|
||||||
|
@@ -86,7 +86,7 @@ class MITIE(TechTVMITIE):
|
|||||||
webpage = self._download_webpage(url, page_title)
|
webpage = self._download_webpage(url, page_title)
|
||||||
embed_url = self._search_regex(
|
embed_url = self._search_regex(
|
||||||
r'<iframe .*?src="(.+?)"', webpage, 'embed url')
|
r'<iframe .*?src="(.+?)"', webpage, 'embed url')
|
||||||
return self.url_result(embed_url, ie='TechTVMIT')
|
return self.url_result(embed_url)
|
||||||
|
|
||||||
|
|
||||||
class OCWMITIE(InfoExtractor):
|
class OCWMITIE(InfoExtractor):
|
||||||
|
@@ -1,7 +1,10 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import compat_urllib_parse
|
from ..compat import (
|
||||||
|
compat_urllib_parse,
|
||||||
|
compat_urlparse,
|
||||||
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
encode_dict,
|
encode_dict,
|
||||||
get_element_by_attribute,
|
get_element_by_attribute,
|
||||||
@@ -15,7 +18,7 @@ class MiTeleIE(InfoExtractor):
|
|||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.mitele.es/programas-tv/diario-de/la-redaccion/programa-144/',
|
'url': 'http://www.mitele.es/programas-tv/diario-de/la-redaccion/programa-144/',
|
||||||
'md5': 'ace7635b2a0b286aaa37d3ff192d2a8a',
|
'md5': '0ff1a13aebb35d9bc14081ff633dd324',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '0NF1jJnxS1Wu3pHrmvFyw2',
|
'id': '0NF1jJnxS1Wu3pHrmvFyw2',
|
||||||
'display_id': 'programa-144',
|
'display_id': 'programa-144',
|
||||||
@@ -34,6 +37,7 @@ class MiTeleIE(InfoExtractor):
|
|||||||
|
|
||||||
config_url = self._search_regex(
|
config_url = self._search_regex(
|
||||||
r'data-config\s*=\s*"([^"]+)"', webpage, 'data config url')
|
r'data-config\s*=\s*"([^"]+)"', webpage, 'data config url')
|
||||||
|
config_url = compat_urlparse.urljoin(url, config_url)
|
||||||
|
|
||||||
config = self._download_json(
|
config = self._download_json(
|
||||||
config_url, display_id, 'Downloading config JSON')
|
config_url, display_id, 'Downloading config JSON')
|
||||||
@@ -56,7 +60,7 @@ class MiTeleIE(InfoExtractor):
|
|||||||
'sta': '0',
|
'sta': '0',
|
||||||
}
|
}
|
||||||
media = self._download_json(
|
media = self._download_json(
|
||||||
'%s/?%s' % (gat, compat_urllib_parse.urlencode(encode_dict(token_data)).encode('utf-8')),
|
'%s/?%s' % (gat, compat_urllib_parse.urlencode(encode_dict(token_data))),
|
||||||
display_id, 'Downloading %s JSON' % location['loc'])
|
display_id, 'Downloading %s JSON' % location['loc'])
|
||||||
file_ = media.get('file')
|
file_ = media.get('file')
|
||||||
if not file_:
|
if not file_:
|
||||||
|
@@ -64,7 +64,8 @@ class MixcloudIE(InfoExtractor):
|
|||||||
|
|
||||||
preview_url = self._search_regex(
|
preview_url = self._search_regex(
|
||||||
r'\s(?:data-preview-url|m-preview)="([^"]+)"', webpage, 'preview url')
|
r'\s(?:data-preview-url|m-preview)="([^"]+)"', webpage, 'preview url')
|
||||||
song_url = preview_url.replace('/previews/', '/c/originals/')
|
song_url = re.sub(r'audiocdn(\d+)', r'stream\1', preview_url)
|
||||||
|
song_url = song_url.replace('/previews/', '/c/originals/')
|
||||||
if not self._check_url(song_url, track_id, 'mp3'):
|
if not self._check_url(song_url, track_id, 'mp3'):
|
||||||
song_url = song_url.replace('.mp3', '.m4a').replace('originals/', 'm4a/64/')
|
song_url = song_url.replace('.mp3', '.m4a').replace('originals/', 'm4a/64/')
|
||||||
if not self._check_url(song_url, track_id, 'm4a'):
|
if not self._check_url(song_url, track_id, 'm4a'):
|
||||||
|
@@ -5,13 +5,11 @@ import json
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import compat_urllib_parse
|
||||||
compat_urllib_parse,
|
|
||||||
compat_urllib_request,
|
|
||||||
)
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
|
sanitized_Request,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -80,7 +78,7 @@ class MoeVideoIE(InfoExtractor):
|
|||||||
]
|
]
|
||||||
r_json = json.dumps(r)
|
r_json = json.dumps(r)
|
||||||
post = compat_urllib_parse.urlencode({'r': r_json})
|
post = compat_urllib_parse.urlencode({'r': r_json})
|
||||||
req = compat_urllib_request.Request(self._API_URL, post)
|
req = sanitized_Request(self._API_URL, post)
|
||||||
req.add_header('Content-type', 'application/x-www-form-urlencoded')
|
req.add_header('Content-type', 'application/x-www-form-urlencoded')
|
||||||
|
|
||||||
response = self._download_json(req, video_id)
|
response = self._download_json(req, video_id)
|
||||||
|
@@ -7,8 +7,8 @@ from .common import InfoExtractor
|
|||||||
from ..compat import (
|
from ..compat import (
|
||||||
compat_urllib_parse_unquote,
|
compat_urllib_parse_unquote,
|
||||||
compat_urllib_parse_urlparse,
|
compat_urllib_parse_urlparse,
|
||||||
compat_urllib_request,
|
|
||||||
)
|
)
|
||||||
|
from ..utils import sanitized_Request
|
||||||
|
|
||||||
|
|
||||||
class MofosexIE(InfoExtractor):
|
class MofosexIE(InfoExtractor):
|
||||||
@@ -29,7 +29,7 @@ class MofosexIE(InfoExtractor):
|
|||||||
video_id = mobj.group('id')
|
video_id = mobj.group('id')
|
||||||
url = 'http://www.' + mobj.group('url')
|
url = 'http://www.' + mobj.group('url')
|
||||||
|
|
||||||
req = compat_urllib_request.Request(url)
|
req = sanitized_Request(url)
|
||||||
req.add_header('Cookie', 'age_verified=1')
|
req.add_header('Cookie', 'age_verified=1')
|
||||||
webpage = self._download_webpage(req, video_id)
|
webpage = self._download_webpage(req, video_id)
|
||||||
|
|
||||||
|
@@ -5,19 +5,17 @@ import os.path
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import compat_urllib_parse
|
||||||
compat_urllib_parse,
|
|
||||||
compat_urllib_request,
|
|
||||||
)
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
remove_start,
|
remove_start,
|
||||||
|
sanitized_Request,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class MonikerIE(InfoExtractor):
|
class MonikerIE(InfoExtractor):
|
||||||
IE_DESC = 'allmyvideos.net and vidspot.net'
|
IE_DESC = 'allmyvideos.net and vidspot.net'
|
||||||
_VALID_URL = r'https?://(?:www\.)?(?:allmyvideos|vidspot)\.net/(?P<id>[a-zA-Z0-9_-]+)'
|
_VALID_URL = r'https?://(?:www\.)?(?:allmyvideos|vidspot)\.net/(?:(?:2|v)/v-)?(?P<id>[a-zA-Z0-9_-]+)'
|
||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://allmyvideos.net/jih3nce3x6wn',
|
'url': 'http://allmyvideos.net/jih3nce3x6wn',
|
||||||
@@ -46,6 +44,18 @@ class MonikerIE(InfoExtractor):
|
|||||||
}, {
|
}, {
|
||||||
'url': 'https://www.vidspot.net/l2ngsmhs8ci5',
|
'url': 'https://www.vidspot.net/l2ngsmhs8ci5',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://vidspot.net/2/v-ywDf99',
|
||||||
|
'md5': '5f8254ce12df30479428b0152fb8e7ba',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'ywDf99',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'IL FAIT LE MALIN EN PORSHE CAYENNE ( mais pas pour longtemps)',
|
||||||
|
'description': 'IL FAIT LE MALIN EN PORSHE CAYENNE.',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://allmyvideos.net/v/v-HXZm5t',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
@@ -64,18 +74,30 @@ class MonikerIE(InfoExtractor):
|
|||||||
raise ExtractorError(
|
raise ExtractorError(
|
||||||
'%s returned error: %s' % (self.IE_NAME, error), expected=True)
|
'%s returned error: %s' % (self.IE_NAME, error), expected=True)
|
||||||
|
|
||||||
fields = re.findall(r'type="hidden" name="(.+?)"\s* value="?(.+?)">', orig_webpage)
|
builtin_url = self._search_regex(
|
||||||
data = dict(fields)
|
r'<iframe[^>]+src=(["\'])(?P<url>.+?/builtin-.+?)\1',
|
||||||
|
orig_webpage, 'builtin URL', default=None, group='url')
|
||||||
|
|
||||||
post = compat_urllib_parse.urlencode(data)
|
if builtin_url:
|
||||||
headers = {
|
req = sanitized_Request(builtin_url)
|
||||||
b'Content-Type': b'application/x-www-form-urlencoded',
|
req.add_header('Referer', url)
|
||||||
}
|
webpage = self._download_webpage(req, video_id, 'Downloading builtin page')
|
||||||
req = compat_urllib_request.Request(url, post, headers)
|
title = self._og_search_title(orig_webpage).strip()
|
||||||
webpage = self._download_webpage(
|
description = self._og_search_description(orig_webpage).strip()
|
||||||
req, video_id, note='Downloading video page ...')
|
else:
|
||||||
|
fields = re.findall(r'type="hidden" name="(.+?)"\s* value="?(.+?)">', orig_webpage)
|
||||||
|
data = dict(fields)
|
||||||
|
|
||||||
title = os.path.splitext(data['fname'])[0]
|
post = compat_urllib_parse.urlencode(data)
|
||||||
|
headers = {
|
||||||
|
b'Content-Type': b'application/x-www-form-urlencoded',
|
||||||
|
}
|
||||||
|
req = sanitized_Request(url, post, headers)
|
||||||
|
webpage = self._download_webpage(
|
||||||
|
req, video_id, note='Downloading video page ...')
|
||||||
|
|
||||||
|
title = os.path.splitext(data['fname'])[0]
|
||||||
|
description = None
|
||||||
|
|
||||||
# Could be several links with different quality
|
# Could be several links with different quality
|
||||||
links = re.findall(r'"file" : "?(.+?)",', webpage)
|
links = re.findall(r'"file" : "?(.+?)",', webpage)
|
||||||
@@ -89,5 +111,6 @@ class MonikerIE(InfoExtractor):
|
|||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': title,
|
'title': title,
|
||||||
|
'description': description,
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
}
|
}
|
||||||
|
@@ -3,12 +3,10 @@ from __future__ import unicode_literals
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import compat_urllib_parse
|
||||||
compat_urllib_request,
|
|
||||||
compat_urllib_parse,
|
|
||||||
)
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
|
sanitized_Request,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -59,7 +57,7 @@ class MooshareIE(InfoExtractor):
|
|||||||
'hash': hash_key,
|
'hash': hash_key,
|
||||||
}
|
}
|
||||||
|
|
||||||
request = compat_urllib_request.Request(
|
request = sanitized_Request(
|
||||||
'http://mooshare.biz/%s' % video_id, compat_urllib_parse.urlencode(download_form))
|
'http://mooshare.biz/%s' % video_id, compat_urllib_parse.urlencode(download_form))
|
||||||
request.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
request.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
||||||
|
|
||||||
|
@@ -1,80 +1,40 @@
|
|||||||
|
# coding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..utils import sanitized_Request
|
||||||
compat_str,
|
|
||||||
)
|
|
||||||
from ..utils import (
|
|
||||||
ExtractorError,
|
|
||||||
clean_html,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class MovieClipsIE(InfoExtractor):
|
class MovieClipsIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://movieclips\.com/(?P<id>[\da-zA-Z]+)(?:-(?P<display_id>[\da-z-]+))?'
|
_VALID_URL = r'https?://(?:www.)?movieclips\.com/videos/(?P<id>[^/?#]+)'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://movieclips.com/Wy7ZU-my-week-with-marilyn-movie-do-you-love-me/',
|
'url': 'http://www.movieclips.com/videos/warcraft-trailer-1-561180739597?autoPlay=true&playlistId=5',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'Wy7ZU',
|
'id': 'pKIGmG83AqD9',
|
||||||
'display_id': 'my-week-with-marilyn-movie-do-you-love-me',
|
'display_id': 'warcraft-trailer-1-561180739597',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'My Week with Marilyn - Do You Love Me?',
|
'title': 'Warcraft Trailer 1',
|
||||||
'description': 'md5:e86795bd332fe3cff461e7c8dc542acb',
|
'description': 'Watch Trailer 1 from Warcraft (2016). Legendary’s WARCRAFT is a 3D epic adventure of world-colliding conflict based.',
|
||||||
'thumbnail': 're:^https?://.*\.jpg$',
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
},
|
},
|
||||||
'params': {
|
'add_ie': ['ThePlatform'],
|
||||||
# rtmp download
|
|
||||||
'skip_download': True,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
display_id = self._match_id(url)
|
||||||
video_id = mobj.group('id')
|
|
||||||
display_id = mobj.group('display_id')
|
|
||||||
show_id = display_id or video_id
|
|
||||||
|
|
||||||
config = self._download_xml(
|
req = sanitized_Request(url)
|
||||||
'http://config.movieclips.com/player/config/%s' % video_id,
|
# it doesn't work if it thinks the browser it's too old
|
||||||
show_id, 'Downloading player config')
|
req.add_header('User-Agent', 'Mozilla/5.0 (X11; Linux x86_64; rv:10.0) Gecko/20150101 Firefox/43.0 (Chrome)')
|
||||||
|
webpage = self._download_webpage(req, display_id)
|
||||||
if config.find('./country-region').text == 'false':
|
theplatform_link = self._html_search_regex(r'src="(http://player.theplatform.com/p/.*?)"', webpage, 'theplatform link')
|
||||||
raise ExtractorError(
|
title = self._html_search_regex(r'<title[^>]*>([^>]+)-\s*\d+\s*|\s*Movieclips.com</title>', webpage, 'title')
|
||||||
'%s said: %s' % (self.IE_NAME, config.find('./region_alert').text), expected=True)
|
description = self._html_search_meta('description', webpage)
|
||||||
|
|
||||||
properties = config.find('./video/properties')
|
|
||||||
smil_file = properties.attrib['smil_file']
|
|
||||||
|
|
||||||
smil = self._download_xml(smil_file, show_id, 'Downloading SMIL')
|
|
||||||
base_url = smil.find('./head/meta').attrib['base']
|
|
||||||
|
|
||||||
formats = []
|
|
||||||
for video in smil.findall('./body/switch/video'):
|
|
||||||
vbr = int(video.attrib['system-bitrate']) / 1000
|
|
||||||
src = video.attrib['src']
|
|
||||||
formats.append({
|
|
||||||
'url': base_url,
|
|
||||||
'play_path': src,
|
|
||||||
'ext': src.split(':')[0],
|
|
||||||
'vbr': vbr,
|
|
||||||
'format_id': '%dk' % vbr,
|
|
||||||
})
|
|
||||||
|
|
||||||
self._sort_formats(formats)
|
|
||||||
|
|
||||||
title = '%s - %s' % (properties.attrib['clip_movie_title'], properties.attrib['clip_title'])
|
|
||||||
description = clean_html(compat_str(properties.attrib['clip_description']))
|
|
||||||
thumbnail = properties.attrib['image']
|
|
||||||
categories = properties.attrib['clip_categories'].split(',')
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'_type': 'url_transparent',
|
||||||
'display_id': display_id,
|
'url': theplatform_link,
|
||||||
'title': title,
|
'title': title,
|
||||||
|
'display_id': display_id,
|
||||||
'description': description,
|
'description': description,
|
||||||
'thumbnail': thumbnail,
|
|
||||||
'categories': categories,
|
|
||||||
'formats': formats,
|
|
||||||
}
|
}
|
||||||
|
@@ -1,27 +0,0 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from .novamov import NovaMovIE
|
|
||||||
|
|
||||||
|
|
||||||
class MovShareIE(NovaMovIE):
|
|
||||||
IE_NAME = 'movshare'
|
|
||||||
IE_DESC = 'MovShare'
|
|
||||||
|
|
||||||
_VALID_URL = NovaMovIE._VALID_URL_TEMPLATE % {'host': 'movshare\.(?:net|sx|ag)'}
|
|
||||||
|
|
||||||
_HOST = 'www.movshare.net'
|
|
||||||
|
|
||||||
_FILE_DELETED_REGEX = r'>This file no longer exists on our servers.<'
|
|
||||||
_TITLE_REGEX = r'<strong>Title:</strong> ([^<]+)</p>'
|
|
||||||
_DESCRIPTION_REGEX = r'<strong>Description:</strong> ([^<]+)</p>'
|
|
||||||
|
|
||||||
_TEST = {
|
|
||||||
'url': 'http://www.movshare.net/video/559e28be54d96',
|
|
||||||
'md5': 'abd31a2132947262c50429e1d16c1bfd',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '559e28be54d96',
|
|
||||||
'ext': 'flv',
|
|
||||||
'title': 'dissapeared image',
|
|
||||||
'description': 'optical illusion dissapeared image magic illusion',
|
|
||||||
}
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user