Compare commits
1502 Commits
2015.03.03
...
2015.07.18
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b0bff54b08 | ||
|
|
f29ac588dd | ||
|
|
0696667734 | ||
|
|
1793d71db6 | ||
|
|
4211e1941b | ||
|
|
4bdfef5a18 | ||
|
|
8a37f53685 | ||
|
|
4e1ad6e9a8 | ||
|
|
fb10e1aa57 | ||
|
|
3c283a381e | ||
|
|
dac4d5be12 | ||
|
|
530857182d | ||
|
|
9441f77faa | ||
|
|
3cc8b4c327 | ||
|
|
6b19647d57 | ||
|
|
7bd42d0d96 | ||
|
|
c60e8cfaf7 | ||
|
|
7fd002c006 | ||
|
|
db6c50f109 | ||
|
|
aa4789d632 | ||
|
|
ee8de13e14 | ||
|
|
7dde5f6a8d | ||
|
|
736f003f2e | ||
|
|
47af21e8f1 | ||
|
|
605cbef653 | ||
|
|
388ad0c05c | ||
|
|
2ebbb6f1f7 | ||
|
|
d54f1c7477 | ||
|
|
b78f5ec4c3 | ||
|
|
9fd3bf04b7 | ||
|
|
e97bb3de83 | ||
|
|
c2daf8dfa4 | ||
|
|
09b718c439 | ||
|
|
c177bb3a50 | ||
|
|
977a247a06 | ||
|
|
899a3e2f13 | ||
|
|
8ee4ecb48d | ||
|
|
f7e6f7fa23 | ||
|
|
1f80e360fc | ||
|
|
d7011316d0 | ||
|
|
d3671b344f | ||
|
|
a60cccbf9f | ||
|
|
3e72f5f10e | ||
|
|
b94b78971c | ||
|
|
4d08161ac2 | ||
|
|
8954e48140 | ||
|
|
aa99aa4e85 | ||
|
|
d79febcd06 | ||
|
|
13fc7f3a05 | ||
|
|
14309e1ddc | ||
|
|
5513967926 | ||
|
|
eacd875f3b | ||
|
|
c4fe07c7af | ||
|
|
1186e3f91a | ||
|
|
f354385bf5 | ||
|
|
cabe001590 | ||
|
|
89f691e141 | ||
|
|
4a63291144 | ||
|
|
593b77064c | ||
|
|
9fefc88656 | ||
|
|
a0f28f90fa | ||
|
|
851229a01f | ||
|
|
c9c854cea7 | ||
|
|
a38436e889 | ||
|
|
23fc384f2c | ||
|
|
1540119723 | ||
|
|
574f42d79a | ||
|
|
536b0700b0 | ||
|
|
5ba761eb85 | ||
|
|
611ac379bb | ||
|
|
03f32a7ead | ||
|
|
50ea2bb20d | ||
|
|
525daedd5a | ||
|
|
e118031ef8 | ||
|
|
45eedbe58c | ||
|
|
e37c932fca | ||
|
|
5eb778bf4d | ||
|
|
ab9b890b52 | ||
|
|
31c746e5dc | ||
|
|
f01f731107 | ||
|
|
70f0f5a8ca | ||
|
|
cc357c4db8 | ||
|
|
97f4aecfc1 | ||
|
|
2af0f87c8b | ||
|
|
b062d94eef | ||
|
|
6c1b0c0ed2 | ||
|
|
ddcdc684e2 | ||
|
|
eae89f92e6 | ||
|
|
01d115b06b | ||
|
|
79057965a8 | ||
|
|
dcd4d95c8e | ||
|
|
cf61d96df0 | ||
|
|
f8da79f828 | ||
|
|
9750e7d70e | ||
|
|
50aa2bb6b9 | ||
|
|
1d1dd597ed | ||
|
|
cfe5537ee5 | ||
|
|
7869eb3fc4 | ||
|
|
6dfa0602f0 | ||
|
|
75a40b2251 | ||
|
|
28fb109ed0 | ||
|
|
48607afac5 | ||
|
|
b6ea9ef21a | ||
|
|
b8dd44baa9 | ||
|
|
c4f1fde75b | ||
|
|
667170e2c7 | ||
|
|
53429e6551 | ||
|
|
ac8f97f2b3 | ||
|
|
41c0d2f8cb | ||
|
|
1f3a43dbe6 | ||
|
|
369e195a44 | ||
|
|
15006fedb9 | ||
|
|
e35b23f54d | ||
|
|
f72b0a6032 | ||
|
|
ac9ed061ec | ||
|
|
d919fa3344 | ||
|
|
79913fde35 | ||
|
|
da634d0a8b | ||
|
|
fac54cb426 | ||
|
|
3f19b9b7c1 | ||
|
|
86f2541695 | ||
|
|
181c4ccaaa | ||
|
|
ed848087d5 | ||
|
|
edd66be5be | ||
|
|
246995dbc8 | ||
|
|
b931fbe5ab | ||
|
|
e014ff015d | ||
|
|
4fa5f40232 | ||
|
|
9b15be97aa | ||
|
|
a7ada46bd9 | ||
|
|
9d16788ad9 | ||
|
|
6ce89aecc3 | ||
|
|
963d0ce7e3 | ||
|
|
0f08d7f851 | ||
|
|
44c514eb9c | ||
|
|
513cbdda93 | ||
|
|
e1ba152352 | ||
|
|
446e764500 | ||
|
|
901d00caa6 | ||
|
|
094790d2c9 | ||
|
|
1c0163a5cc | ||
|
|
8fa7e5817a | ||
|
|
01b89d5682 | ||
|
|
9f01c1a803 | ||
|
|
46f0f50016 | ||
|
|
b8070dbbd7 | ||
|
|
3b16d803c9 | ||
|
|
de195c23a6 | ||
|
|
d3b8908886 | ||
|
|
2688176c77 | ||
|
|
a5839317aa | ||
|
|
a0aab26a41 | ||
|
|
27713812a0 | ||
|
|
cf2c5fda4f | ||
|
|
a9684c0dbf | ||
|
|
c0bf5e1c4d | ||
|
|
a31e3e7dcb | ||
|
|
17b41a3337 | ||
|
|
89a683ae74 | ||
|
|
008661069b | ||
|
|
9296e92e1c | ||
|
|
a34af8d066 | ||
|
|
8726e04629 | ||
|
|
2a01c940ec | ||
|
|
4eab60cbd2 | ||
|
|
a0e060ac1e | ||
|
|
397a8ea96e | ||
|
|
15830339ef | ||
|
|
b29280285e | ||
|
|
1633491bff | ||
|
|
2b0fa1f7dd | ||
|
|
02b386f80a | ||
|
|
bf20b9c540 | ||
|
|
06a12933f3 | ||
|
|
6dd94d3a79 | ||
|
|
f2f89c762a | ||
|
|
e6c2d9ad29 | ||
|
|
83423254cc | ||
|
|
1c20ddc966 | ||
|
|
675e9f22ea | ||
|
|
77c6fb5b24 | ||
|
|
082a0140ef | ||
|
|
9e535ce055 | ||
|
|
d76dea001b | ||
|
|
af0f9b0e95 | ||
|
|
e2082ea942 | ||
|
|
68923e52a3 | ||
|
|
9281f6d253 | ||
|
|
4647845679 | ||
|
|
cf9cf7dd04 | ||
|
|
1316b54956 | ||
|
|
cbc1fadd6f | ||
|
|
4dd09c9add | ||
|
|
267dc07e6b | ||
|
|
d7b4d5dd50 | ||
|
|
7f220b2fac | ||
|
|
275c0423aa | ||
|
|
d3ee4bbc5a | ||
|
|
85a064861f | ||
|
|
d0b436bff2 | ||
|
|
92b2f18072 | ||
|
|
dfc4eca21f | ||
|
|
fc7ae675e2 | ||
|
|
804ad79985 | ||
|
|
da839880e9 | ||
|
|
e9d33454b5 | ||
|
|
d80891efc4 | ||
|
|
37c1e4025c | ||
|
|
59a83d3e5b | ||
|
|
13af92fdc4 | ||
|
|
0c20ee7d4b | ||
|
|
89d42c2c75 | ||
|
|
04611765a4 | ||
|
|
9dfc4fa1a1 | ||
|
|
43232d5c14 | ||
|
|
f7c272d4fa | ||
|
|
ede21449c8 | ||
|
|
d7c9a3e976 | ||
|
|
35eb649e9d | ||
|
|
e56a4c9e9b | ||
|
|
95506e37af | ||
|
|
e41840c522 | ||
|
|
2a46a27e6c | ||
|
|
0bcdc27653 | ||
|
|
ddf0f74de7 | ||
|
|
91b21b2334 | ||
|
|
66e568de3b | ||
|
|
f5ca97e393 | ||
|
|
8d06a62485 | ||
|
|
93f9420993 | ||
|
|
5b61070c70 | ||
|
|
dbe1a93526 | ||
|
|
aa5d9a79d6 | ||
|
|
86511ea417 | ||
|
|
1866432db7 | ||
|
|
33f1f81b8b | ||
|
|
9d0b581fea | ||
|
|
c05724cb18 | ||
|
|
f0714c9f86 | ||
|
|
cf386750c9 | ||
|
|
54f428f645 | ||
|
|
dc2bd20e55 | ||
|
|
c608ee491f | ||
|
|
738b926322 | ||
|
|
bea41c7f3f | ||
|
|
1bbe660dfa | ||
|
|
c4bd188da4 | ||
|
|
5414623791 | ||
|
|
c93d53f5e3 | ||
|
|
507683780e | ||
|
|
e8b9ee5e08 | ||
|
|
d16154d163 | ||
|
|
c342041fba | ||
|
|
bf42a9906d | ||
|
|
9603e8a7d9 | ||
|
|
c7c040b825 | ||
|
|
ac0474f89d | ||
|
|
bb512e57dc | ||
|
|
db652ea186 | ||
|
|
5a9cc19972 | ||
|
|
1a5fd4eebc | ||
|
|
8a1b49ff19 | ||
|
|
b971abe897 | ||
|
|
43b925ce74 | ||
|
|
62b742ece3 | ||
|
|
d16ef949ca | ||
|
|
23e7cba87f | ||
|
|
a8e6f30d8e | ||
|
|
9c49410898 | ||
|
|
802d74aa6b | ||
|
|
71f9e49e67 | ||
|
|
82ea1051b5 | ||
|
|
6c4d20cd6f | ||
|
|
04c27802c0 | ||
|
|
c3b7202f4f | ||
|
|
81103ef35d | ||
|
|
0eb5c1c62a | ||
|
|
a9de951744 | ||
|
|
a42a1bb09d | ||
|
|
9fbfc9bd4d | ||
|
|
242a998bdc | ||
|
|
9d1bf70234 | ||
|
|
b8c1cc1a51 | ||
|
|
eedd20ef96 | ||
|
|
7c197ad96d | ||
|
|
654fd03c73 | ||
|
|
cee16e0fa3 | ||
|
|
73c471e9ef | ||
|
|
533b99fbf9 | ||
|
|
f39eb98bab | ||
|
|
da77d856a1 | ||
|
|
b2575b38e7 | ||
|
|
0a3cf9ad3d | ||
|
|
00334d0de0 | ||
|
|
226b886ca8 | ||
|
|
bc93bdb5bb | ||
|
|
af214c3a79 | ||
|
|
4eb10f6621 | ||
|
|
7d7d469025 | ||
|
|
fd40bdc0be | ||
|
|
7e0480ae0e | ||
|
|
d80265ccd6 | ||
|
|
1b5a1ae257 | ||
|
|
d8d24a922a | ||
|
|
03339b7b5b | ||
|
|
2028c6e03d | ||
|
|
2988835af5 | ||
|
|
62cca96b72 | ||
|
|
b4dea075a3 | ||
|
|
533f67d3fa | ||
|
|
906e2f0eac | ||
|
|
b8091db6b9 | ||
|
|
381c067755 | ||
|
|
2182ab5187 | ||
|
|
a1593a4a0e | ||
|
|
12e9e8445d | ||
|
|
8084be78c5 | ||
|
|
1ac1c4c26e | ||
|
|
d4f58034f7 | ||
|
|
f843300fe5 | ||
|
|
03b9c94437 | ||
|
|
a219d175c6 | ||
|
|
3c3b4176bd | ||
|
|
022383139b | ||
|
|
1a1251e877 | ||
|
|
18b5e1e534 | ||
|
|
2fece970b8 | ||
|
|
e20d0c1e69 | ||
|
|
255f5694aa | ||
|
|
25701d5a2c | ||
|
|
a5158f38a3 | ||
|
|
c76799c555 | ||
|
|
2bb5b6d0a1 | ||
|
|
0bbba43ed0 | ||
|
|
98ca102441 | ||
|
|
3f3308cd75 | ||
|
|
6f96e308d0 | ||
|
|
756f574e4e | ||
|
|
78294e6a9c | ||
|
|
4e33577173 | ||
|
|
607841af64 | ||
|
|
396726244a | ||
|
|
c5895d5dbd | ||
|
|
b407e173e4 | ||
|
|
6a745c2c0f | ||
|
|
2da0cad6ae | ||
|
|
af1fa6234e | ||
|
|
c9ac7fa909 | ||
|
|
964afd0689 | ||
|
|
16d6973f8a | ||
|
|
edcd2d665b | ||
|
|
385c3e5e91 | ||
|
|
c8e337450b | ||
|
|
10464af5d1 | ||
|
|
cbcd1a5474 | ||
|
|
c9bebed294 | ||
|
|
360075e28a | ||
|
|
accf79b107 | ||
|
|
4d58b24c15 | ||
|
|
c33a8639a7 | ||
|
|
25fa8d66e6 | ||
|
|
974a6146fe | ||
|
|
0392ac98d2 | ||
|
|
5e3915cbe3 | ||
|
|
29b809de68 | ||
|
|
8f73e89ca0 | ||
|
|
0d0d5d3717 | ||
|
|
a69e8bfdd9 | ||
|
|
062a3fdf36 | ||
|
|
028a33d7f2 | ||
|
|
14835de9fb | ||
|
|
447053668f | ||
|
|
f3aecb27a4 | ||
|
|
7f0172b3e5 | ||
|
|
79fe954d79 | ||
|
|
0501bfa159 | ||
|
|
a155b7e76c | ||
|
|
5228b756af | ||
|
|
04e7596680 | ||
|
|
f1e66cb2eb | ||
|
|
4fd35ee072 | ||
|
|
ee69799262 | ||
|
|
636a9637f4 | ||
|
|
9383e66f94 | ||
|
|
99e6833c85 | ||
|
|
c203be3fb4 | ||
|
|
02175a7986 | ||
|
|
8117df4cd9 | ||
|
|
7c7dd9dc7f | ||
|
|
054932f403 | ||
|
|
aed473ccf9 | ||
|
|
8268e94cd4 | ||
|
|
4af98ecdfb | ||
|
|
4b9f9010b0 | ||
|
|
2a0fcf6113 | ||
|
|
67d95f177c | ||
|
|
44773ad125 | ||
|
|
5774ef35c4 | ||
|
|
b95cfa9170 | ||
|
|
afa1ded425 | ||
|
|
00ac23e6e0 | ||
|
|
7d0c934a3e | ||
|
|
8f75761f24 | ||
|
|
9fd24e3a22 | ||
|
|
755a9d3d1a | ||
|
|
ac499cb61c | ||
|
|
180940e02d | ||
|
|
976b03c56b | ||
|
|
450d89ddc1 | ||
|
|
463b2e5542 | ||
|
|
70a2002399 | ||
|
|
a617b10075 | ||
|
|
0029071adb | ||
|
|
ad49fe7c8f | ||
|
|
49bc802f81 | ||
|
|
af9cdee9cb | ||
|
|
b4e1576aee | ||
|
|
78e2b74bb9 | ||
|
|
65d161c480 | ||
|
|
9fcbd5db2a | ||
|
|
4f3bf679f5 | ||
|
|
0072afca8e | ||
|
|
61aa5ba36e | ||
|
|
9f4323252a | ||
|
|
8b6c896c4b | ||
|
|
185dbc4974 | ||
|
|
3d535e0471 | ||
|
|
d22dec74ff | ||
|
|
9872d3110c | ||
|
|
b859971873 | ||
|
|
e5095f1198 | ||
|
|
499a077761 | ||
|
|
5da7177729 | ||
|
|
3507766bd0 | ||
|
|
f37bdbe537 | ||
|
|
2da09ff8b0 | ||
|
|
5ccddb7ecf | ||
|
|
954c1d0529 | ||
|
|
494f20cbdc | ||
|
|
29902c8ec0 | ||
|
|
9f15bdabc8 | ||
|
|
fff3455f58 | ||
|
|
87446dc618 | ||
|
|
99ac0390f5 | ||
|
|
ff0f0b9172 | ||
|
|
97b570a94c | ||
|
|
a9d56c6843 | ||
|
|
f98470df69 | ||
|
|
eb8be1fe76 | ||
|
|
7ebd5376fe | ||
|
|
d84f1d14b5 | ||
|
|
70219b0f43 | ||
|
|
bd5bc0cd5a | ||
|
|
6e054aacca | ||
|
|
9d581f3d52 | ||
|
|
9bf99891d0 | ||
|
|
d9cf48e81e | ||
|
|
e1b9322b09 | ||
|
|
627b964825 | ||
|
|
a55e36f48d | ||
|
|
01e21b89ee | ||
|
|
788be3313d | ||
|
|
e1ec93304d | ||
|
|
edb99d4c18 | ||
|
|
68477c3dab | ||
|
|
65ba8b23f4 | ||
|
|
621ed9f5f4 | ||
|
|
b26733ba7f | ||
|
|
9836cfb8d6 | ||
|
|
665b6c1236 | ||
|
|
9414338a48 | ||
|
|
de390ea077 | ||
|
|
717b0239fd | ||
|
|
d00735a0c5 | ||
|
|
c23d5ce926 | ||
|
|
b5a3c7f109 | ||
|
|
9c5f685ef1 | ||
|
|
08bb8ef201 | ||
|
|
865ab62f43 | ||
|
|
9948113590 | ||
|
|
c4ee87022b | ||
|
|
ffba4edb06 | ||
|
|
958d0b659b | ||
|
|
aacda28b28 | ||
|
|
29e7e0781b | ||
|
|
7012620e2b | ||
|
|
f1da861018 | ||
|
|
05aa9c82d9 | ||
|
|
a9e58ecd3f | ||
|
|
223544552f | ||
|
|
3d8e9573a4 | ||
|
|
54eb81a087 | ||
|
|
c33c547d66 | ||
|
|
dfe7dd9bdb | ||
|
|
63ccf6474d | ||
|
|
e8ac61e840 | ||
|
|
f00a650705 | ||
|
|
4bde5ce992 | ||
|
|
d31573fa37 | ||
|
|
8b8cde2140 | ||
|
|
0e805e782b | ||
|
|
f5c78d118b | ||
|
|
9d4f213f90 | ||
|
|
168db222c6 | ||
|
|
3d6388e34e | ||
|
|
3ce9bc712a | ||
|
|
e52c0bd0eb | ||
|
|
56c837ccb7 | ||
|
|
55e5841f14 | ||
|
|
ed15e9ba02 | ||
|
|
eedda32e6b | ||
|
|
4c8fea92f3 | ||
|
|
d073055dcd | ||
|
|
e4ac7bb1e5 | ||
|
|
9bac8c57e3 | ||
|
|
3153a2c98d | ||
|
|
15b74b94be | ||
|
|
687cb3ad35 | ||
|
|
8f94784124 | ||
|
|
23dd1fc74c | ||
|
|
fa971259e6 | ||
|
|
b0cda32f72 | ||
|
|
08b7968e28 | ||
|
|
4b5fe1349f | ||
|
|
d23da75b32 | ||
|
|
06e027992d | ||
|
|
b5597738d4 | ||
|
|
bc03e58565 | ||
|
|
a00234f1c5 | ||
|
|
34c0f95db2 | ||
|
|
fcb04bcaca | ||
|
|
9464a194db | ||
|
|
9f4b9118cc | ||
|
|
60158217ef | ||
|
|
923e79e2e4 | ||
|
|
866b296d0f | ||
|
|
4053ee9104 | ||
|
|
47fd8c2f76 | ||
|
|
96b9690985 | ||
|
|
df15ef8dab | ||
|
|
002c0fb511 | ||
|
|
7584e38ce4 | ||
|
|
eb47569f8a | ||
|
|
d2a9de78df | ||
|
|
c5138a7ce4 | ||
|
|
c5fa81fe81 | ||
|
|
a074e92296 | ||
|
|
1ddb9456c4 | ||
|
|
58bde34a23 | ||
|
|
339516072b | ||
|
|
931bc3c3a7 | ||
|
|
db1e9ee771 | ||
|
|
a2d971309b | ||
|
|
d05a1dbe70 | ||
|
|
a80601f8d9 | ||
|
|
1c22238756 | ||
|
|
9ff811c5cd | ||
|
|
1ebc05df91 | ||
|
|
386bdfa698 | ||
|
|
1ae7ff771b | ||
|
|
5196b98897 | ||
|
|
e6e63e91a7 | ||
|
|
b4dd98358f | ||
|
|
181c7053e3 | ||
|
|
4d454c5e4b | ||
|
|
5c2191a605 | ||
|
|
bba5bfc890 | ||
|
|
1a5b77dc21 | ||
|
|
b2cf6543b2 | ||
|
|
0385d64223 | ||
|
|
6ebdfe43e4 | ||
|
|
fafec39d41 | ||
|
|
670861bd20 | ||
|
|
08f7db20c1 | ||
|
|
605ec701b7 | ||
|
|
d6aa68ce75 | ||
|
|
eb6cb9fbe9 | ||
|
|
84e1e036c2 | ||
|
|
1498940b10 | ||
|
|
806598b94d | ||
|
|
e26be70bca | ||
|
|
ca45246627 | ||
|
|
f9355dc989 | ||
|
|
9e0b579128 | ||
|
|
ff4a1279f2 | ||
|
|
9b254aa177 | ||
|
|
703d78bbf5 | ||
|
|
d9446c7319 | ||
|
|
b25b645d51 | ||
|
|
d90b3854ca | ||
|
|
bf24c3d017 | ||
|
|
f0bfaa2d7d | ||
|
|
f9f3e3df9a | ||
|
|
f8d5e1cfb5 | ||
|
|
c23848b3c5 | ||
|
|
6d00a2dcd1 | ||
|
|
b535170b21 | ||
|
|
1434184c57 | ||
|
|
7a372b64df | ||
|
|
5406af92bc | ||
|
|
7d65242dc3 | ||
|
|
544a8693b7 | ||
|
|
35a4f24a37 | ||
|
|
ff305edd64 | ||
|
|
efec4358b9 | ||
|
|
db3ca36403 | ||
|
|
42833b44b5 | ||
|
|
5d0a33eebc | ||
|
|
ba2df04b41 | ||
|
|
c6bbdadd79 | ||
|
|
b885bae634 | ||
|
|
680f9744c4 | ||
|
|
2c935c0c72 | ||
|
|
7198063d96 | ||
|
|
d41ebe146b | ||
|
|
4b4e1af059 | ||
|
|
80240b347e | ||
|
|
04b3b3df05 | ||
|
|
2ad5708c43 | ||
|
|
63f3cab4ae | ||
|
|
8cdf03a7a2 | ||
|
|
d78c834ead | ||
|
|
05a976cd99 | ||
|
|
34fb7e46ad | ||
|
|
abac15f3c6 | ||
|
|
b700055ba4 | ||
|
|
23905927e1 | ||
|
|
56be5f1567 | ||
|
|
1807ae22dd | ||
|
|
71646e4653 | ||
|
|
1335c3aca8 | ||
|
|
30455ce255 | ||
|
|
9bf87ae3aa | ||
|
|
abca34cbc0 | ||
|
|
d386878af9 | ||
|
|
685c74d315 | ||
|
|
69e0f1b445 | ||
|
|
79979c6897 | ||
|
|
ba64547616 | ||
|
|
ed5a637d62 | ||
|
|
8a278a1d7e | ||
|
|
77d9cb2f04 | ||
|
|
0459432d96 | ||
|
|
43150d7ac3 | ||
|
|
afe8b594be | ||
|
|
878563c847 | ||
|
|
06947add03 | ||
|
|
5cd47a5e4f | ||
|
|
53de95da5e | ||
|
|
663004ac2b | ||
|
|
6ad9cb224a | ||
|
|
e7752cd578 | ||
|
|
4d2f42361e | ||
|
|
4d8ee01389 | ||
|
|
d01924f488 | ||
|
|
bc56355ec6 | ||
|
|
ac20d95f97 | ||
|
|
1a83c731bd | ||
|
|
ca57a59883 | ||
|
|
b0d619fde2 | ||
|
|
cc7051efd7 | ||
|
|
5137adb94d | ||
|
|
0b9f7cd074 | ||
|
|
2632941f32 | ||
|
|
137597b0ea | ||
|
|
051df9ad99 | ||
|
|
f670ef1c8e | ||
|
|
d9d747a06a | ||
|
|
b813d8caf1 | ||
|
|
ecee572411 | ||
|
|
1b0427e6c4 | ||
|
|
2aa64b89b3 | ||
|
|
484c9d2d5b | ||
|
|
5d8dcb5342 | ||
|
|
2328f2fe68 | ||
|
|
4f514c7e88 | ||
|
|
5bdc520cf1 | ||
|
|
fc6e75dd57 | ||
|
|
4a5a898a8f | ||
|
|
ba9d16291b | ||
|
|
725652e924 | ||
|
|
8da0e0e946 | ||
|
|
588b82bbf8 | ||
|
|
bc0f937b55 | ||
|
|
baa43cbaf0 | ||
|
|
adb6b1b316 | ||
|
|
1c18de0019 | ||
|
|
4d52f2eb7f | ||
|
|
363cf58645 | ||
|
|
7e760fc188 | ||
|
|
ef2dcbe4ad | ||
|
|
9354a5fad4 | ||
|
|
1c97b0a777 | ||
|
|
2f3bdab2b9 | ||
|
|
0d7f036429 | ||
|
|
2cda13213d | ||
|
|
70d0d43b5e | ||
|
|
25c3a7348f | ||
|
|
9123d64592 | ||
|
|
b827a6015c | ||
|
|
d40a3b5b55 | ||
|
|
ef28a6cb26 | ||
|
|
1436a6835e | ||
|
|
e8cfacae37 | ||
|
|
3a7382950b | ||
|
|
eeb23eb7ea | ||
|
|
34fe5a94ba | ||
|
|
6181864290 | ||
|
|
e9ca615a98 | ||
|
|
62c95fd5fc | ||
|
|
25f14e9f93 | ||
|
|
ae670a6ed8 | ||
|
|
a7b8467ac0 | ||
|
|
15da7ce7fb | ||
|
|
e9eaf3fbcf | ||
|
|
3884dcf313 | ||
|
|
c4fc559f45 | ||
|
|
2bc4330303 | ||
|
|
12675275a1 | ||
|
|
3a105f7b20 | ||
|
|
1ae72fb23d | ||
|
|
7ec676bb3d | ||
|
|
29ea57283e | ||
|
|
5488973961 | ||
|
|
96d45a5489 | ||
|
|
7a012d5a16 | ||
|
|
fa6a16996e | ||
|
|
82245a6de7 | ||
|
|
ff28ede2d1 | ||
|
|
98b8ec8616 | ||
|
|
88f9d8748c | ||
|
|
7d57d2e18b | ||
|
|
38caa00d18 | ||
|
|
c827d4cfdb | ||
|
|
509c630db8 | ||
|
|
fbff30d2db | ||
|
|
86c7fdb17c | ||
|
|
62bd6589c7 | ||
|
|
2cc6d13547 | ||
|
|
bb8ca1d112 | ||
|
|
8e59539752 | ||
|
|
372744c544 | ||
|
|
83880949a1 | ||
|
|
3749e36e9f | ||
|
|
0b4253fa37 | ||
|
|
86ec1e487c | ||
|
|
fd4eefed39 | ||
|
|
b480e7874b | ||
|
|
41333b97b9 | ||
|
|
c1c924abfe | ||
|
|
1c7e2e64f6 | ||
|
|
7dff03636a | ||
|
|
5332fd91bf | ||
|
|
d4b963d0a6 | ||
|
|
6d3f5935e5 | ||
|
|
968ee17677 | ||
|
|
81ed3bb9c0 | ||
|
|
5115652828 | ||
|
|
1f92865494 | ||
|
|
e41f450f28 | ||
|
|
97fcf1bbd0 | ||
|
|
13763ce599 | ||
|
|
7fcb605b82 | ||
|
|
70484b9f8a | ||
|
|
69b46b3d95 | ||
|
|
95c5534f8e | ||
|
|
370b39e8ec | ||
|
|
3da8038918 | ||
|
|
a6762c4a22 | ||
|
|
98c2c0febc | ||
|
|
63cbd19f50 | ||
|
|
1934f3a0ea | ||
|
|
a909e6ad43 | ||
|
|
1dcb52188d | ||
|
|
28ebef0b1b | ||
|
|
f03a8a3c4e | ||
|
|
03f760b1c0 | ||
|
|
f900dc3fb9 | ||
|
|
95eb1adda8 | ||
|
|
c6ddbdb66c | ||
|
|
3800b908b1 | ||
|
|
69fe3a5f09 | ||
|
|
754270313a | ||
|
|
057ebeaca3 | ||
|
|
480065172d | ||
|
|
f2e0056579 | ||
|
|
32fffff2cc | ||
|
|
3c47824d6b | ||
|
|
0892090a56 | ||
|
|
d592b42f5c | ||
|
|
3b5f65a64c | ||
|
|
5c0b2c16a8 | ||
|
|
d39e0f05db | ||
|
|
6d14d08e06 | ||
|
|
32060c6d6b | ||
|
|
3dbec410a0 | ||
|
|
de765f6c31 | ||
|
|
dc455a5f88 | ||
|
|
bab19a8e91 | ||
|
|
322915014f | ||
|
|
79998cd5af | ||
|
|
50b9013064 | ||
|
|
bb03fdae0d | ||
|
|
4384cf9e7d | ||
|
|
d47e980d0d | ||
|
|
fe373287eb | ||
|
|
cbe443362f | ||
|
|
2c0c9dc46c | ||
|
|
0ceab84749 | ||
|
|
34e7dc81a9 | ||
|
|
4e6e9d21bd | ||
|
|
d1feb30811 | ||
|
|
43837189c1 | ||
|
|
249962ffa2 | ||
|
|
541168039d | ||
|
|
7ef00afe9d | ||
|
|
156fc83a55 | ||
|
|
46be82b811 | ||
|
|
09b412dafa | ||
|
|
5268a05e47 | ||
|
|
406224be52 | ||
|
|
3799834dcf | ||
|
|
553e412bda | ||
|
|
f22834a372 | ||
|
|
bd349a8704 | ||
|
|
bc08873cff | ||
|
|
aafe273990 | ||
|
|
c09593c04e | ||
|
|
84bf31aaf8 | ||
|
|
05d5392cda | ||
|
|
d9a743d917 | ||
|
|
ac6c358c2a | ||
|
|
ad0c0ad3b4 | ||
|
|
1ed34f3dd6 | ||
|
|
6a8f9cd22e | ||
|
|
e8b9ab8957 | ||
|
|
74f728249f | ||
|
|
d6a1738892 | ||
|
|
b326b07adc | ||
|
|
07d2921c6d | ||
|
|
22e462c97a | ||
|
|
dcf8077906 | ||
|
|
3408f6e64a | ||
|
|
e10dc0e1f0 | ||
|
|
ce5c1ae517 | ||
|
|
bbe718c97f | ||
|
|
01e4b1ee14 | ||
|
|
815ac0293e | ||
|
|
6568382d6f | ||
|
|
f943b7ddce | ||
|
|
ff9d68e7be | ||
|
|
7212560f4d | ||
|
|
1aa43d77c0 | ||
|
|
e038d5c4e3 | ||
|
|
dfad3aac98 | ||
|
|
df8418ffcf | ||
|
|
50aa43b3ae | ||
|
|
a90552663e | ||
|
|
883340c107 | ||
|
|
0fe2ff78e6 | ||
|
|
dc1eed93be | ||
|
|
b2f82360d7 | ||
|
|
782e0568ef | ||
|
|
90b4b0eabe | ||
|
|
cec04ef3a6 | ||
|
|
71fa56b887 | ||
|
|
b9b3ab45ea | ||
|
|
957b794c26 | ||
|
|
8001607e90 | ||
|
|
3e7202c1bc | ||
|
|
848edeab89 | ||
|
|
1748d67aea | ||
|
|
5477ca8239 | ||
|
|
d0fd305023 | ||
|
|
8dab1e9072 | ||
|
|
963aea5279 | ||
|
|
0a64aa7355 | ||
|
|
0669c89c55 | ||
|
|
2699da8041 | ||
|
|
98727e123f | ||
|
|
b29e0000e6 | ||
|
|
b3ed15b760 | ||
|
|
666a9a2b95 | ||
|
|
a4bcaad773 | ||
|
|
e65e4c8874 | ||
|
|
21f6330274 | ||
|
|
38c6902b90 | ||
|
|
2ddcd88129 | ||
|
|
dd8920653c | ||
|
|
c938c35f95 | ||
|
|
2eb0192155 | ||
|
|
d948e09b61 | ||
|
|
89966a5aea | ||
|
|
8e3df9dfee | ||
|
|
5890eef6b0 | ||
|
|
083c1bb960 | ||
|
|
861e65eb05 | ||
|
|
650cfd0cb0 | ||
|
|
e68ae99a41 | ||
|
|
8683b4d8d9 | ||
|
|
1dbd717eb4 | ||
|
|
6a8422b942 | ||
|
|
cb202fd286 | ||
|
|
67fc8ecd53 | ||
|
|
df8301fef5 | ||
|
|
4070b458ec | ||
|
|
ffbc3901d2 | ||
|
|
7a03280df4 | ||
|
|
482a1258de | ||
|
|
cd298882cd | ||
|
|
e01c56f9e1 | ||
|
|
4d72df4031 | ||
|
|
f7f1df1d82 | ||
|
|
c4a21bc9db | ||
|
|
621ffe7bf4 | ||
|
|
8dd5418803 | ||
|
|
965cb8d530 | ||
|
|
b2e8e7dab5 | ||
|
|
59d814f793 | ||
|
|
bb865f3a5e | ||
|
|
9ee53a49f0 | ||
|
|
79adb09baa | ||
|
|
cf0649f8b7 | ||
|
|
f8690631e2 | ||
|
|
5456d78f0c | ||
|
|
cbbece96a2 | ||
|
|
9d8ba307ef | ||
|
|
ec7c1e85e0 | ||
|
|
e70c7568c0 | ||
|
|
39b62db116 | ||
|
|
2edce52584 | ||
|
|
10831b5ec9 | ||
|
|
3a0f0c263a | ||
|
|
2419a376b9 | ||
|
|
e206740fd7 | ||
|
|
290a5a8d85 | ||
|
|
e2dc351d25 | ||
|
|
c86b61428b | ||
|
|
40b96352c9 | ||
|
|
189ba90996 | ||
|
|
c8183e661d | ||
|
|
053c94f1b3 | ||
|
|
b9d76a9571 | ||
|
|
a01cfc2951 | ||
|
|
4eb5c65bee | ||
|
|
06d07c4000 | ||
|
|
74f8654a53 | ||
|
|
9e105a858c | ||
|
|
cd8a07a764 | ||
|
|
aa49acd15a | ||
|
|
642f23bd81 | ||
|
|
2e24e6bd17 | ||
|
|
2a09c1b8ab | ||
|
|
a5ebf77d87 | ||
|
|
b874495b1f | ||
|
|
b860f5dfd4 | ||
|
|
b19fc36c81 | ||
|
|
d2d8248f68 | ||
|
|
f54bab4d67 | ||
|
|
bf6427d2fb | ||
|
|
672f1bd849 | ||
|
|
529d26c3e1 | ||
|
|
857f00ed94 | ||
|
|
e4a5e772f2 | ||
|
|
a542e372ab | ||
|
|
0d1bd5d62f | ||
|
|
9f3fa89f7c | ||
|
|
92995e6265 | ||
|
|
a4196c3ea5 | ||
|
|
db37e0c273 | ||
|
|
d0aefec99a | ||
|
|
66be4b89d7 | ||
|
|
870744ce8f | ||
|
|
2ad978532b | ||
|
|
5090d93f2c | ||
|
|
c8ff645766 | ||
|
|
25f7d1beba | ||
|
|
09aa111918 | ||
|
|
10fb7710e8 | ||
|
|
c0ea8ebb9b | ||
|
|
31fd9c7601 | ||
|
|
ddbed36455 | ||
|
|
a9b0d4e1f4 | ||
|
|
4d6a3ff411 | ||
|
|
7fb993e1f4 | ||
|
|
02f502f435 | ||
|
|
4515cb43ca | ||
|
|
d740333224 | ||
|
|
c610f38ba9 | ||
|
|
6447353f52 | ||
|
|
b46ed49996 | ||
|
|
cd9fdccde0 | ||
|
|
2a8137272d | ||
|
|
762155cc90 | ||
|
|
f8610ba1ca | ||
|
|
c99f4098c4 | ||
|
|
3eec9fef30 | ||
|
|
8c8826176d | ||
|
|
14a2d6789f | ||
|
|
7513f298b0 | ||
|
|
c04c3e334c | ||
|
|
f8e51f60b3 | ||
|
|
33b066bda0 | ||
|
|
14f41bc2fb | ||
|
|
008bee0f50 | ||
|
|
29492f3332 | ||
|
|
bc94bd510b | ||
|
|
9dd8e46a2d | ||
|
|
8be2bdfabd | ||
|
|
b4c0806963 | ||
|
|
cc38fa6cfb | ||
|
|
6de5dbafee | ||
|
|
60bf45c80d | ||
|
|
eb0f3e7ec0 | ||
|
|
ed553379df | ||
|
|
5c1e6f69c4 | ||
|
|
757cda0a96 | ||
|
|
e94443de80 | ||
|
|
0954cd8aa4 | ||
|
|
da55dac047 | ||
|
|
13a11b195f | ||
|
|
92dcba1e1c | ||
|
|
2fe1b5bd2a | ||
|
|
f91e1a8739 | ||
|
|
24e21613b6 | ||
|
|
c6391cd587 | ||
|
|
006ce15a0c | ||
|
|
edf4216119 | ||
|
|
ae8953409e | ||
|
|
bda44f31a1 | ||
|
|
6621ca39a3 | ||
|
|
14f7abfa71 | ||
|
|
0f0b5736da | ||
|
|
6728187ac0 | ||
|
|
17c8675853 | ||
|
|
cfbee8a431 | ||
|
|
736785ab63 | ||
|
|
3ded7bac16 | ||
|
|
b524a001d6 | ||
|
|
7b071e317b | ||
|
|
a380509259 | ||
|
|
c0dea0a782 | ||
|
|
70947ea7b1 | ||
|
|
81cd954a51 | ||
|
|
feccf29c87 | ||
|
|
5b5fbc0867 | ||
|
|
f158799bbe | ||
|
|
8b0e8990c2 | ||
|
|
880ee801cf | ||
|
|
163965d861 | ||
|
|
6e218b3f9a | ||
|
|
cc9b9df0b6 | ||
|
|
31f224008e | ||
|
|
f32cb5cb14 | ||
|
|
fec2d97ca2 | ||
|
|
f2eeafb061 | ||
|
|
8f4e8bf280 | ||
|
|
cc36e2295a | ||
|
|
d47aeb2252 | ||
|
|
14523ed969 | ||
|
|
592e97e855 | ||
|
|
53faa3ca5f | ||
|
|
c62566971f | ||
|
|
902be27cf9 | ||
|
|
bf12cbe07c | ||
|
|
f52e66505a | ||
|
|
ca75235d3d | ||
|
|
ecc6bd1341 | ||
|
|
ce81b1411d | ||
|
|
7691a7a3bd | ||
|
|
214e74bf6f | ||
|
|
c5826a491b | ||
|
|
d8e7ef04dc | ||
|
|
08f2a92c9c | ||
|
|
3220c50f9a | ||
|
|
024ebb2706 | ||
|
|
954352c4c0 | ||
|
|
4aec95f3c9 | ||
|
|
be531ef1ec | ||
|
|
65c1a750f5 | ||
|
|
5141249c59 | ||
|
|
6225984681 | ||
|
|
5cb91ceaa5 | ||
|
|
89c09e2a08 | ||
|
|
fbbb219409 | ||
|
|
820b064804 | ||
|
|
355c524bfa | ||
|
|
c052ce6cde | ||
|
|
c9a779695d | ||
|
|
a685ae511a | ||
|
|
5edea45fab | ||
|
|
8afff9f849 | ||
|
|
a2043572aa | ||
|
|
5d98908b26 | ||
|
|
d6fd958c5f | ||
|
|
d0eb724e22 | ||
|
|
afe4a8c769 | ||
|
|
9fc03aa87c | ||
|
|
c798f15b98 | ||
|
|
2dcc114f84 | ||
|
|
0dfe9bc9d2 | ||
|
|
4d1cdb5bfe | ||
|
|
9c5335a027 | ||
|
|
ae849ca170 | ||
|
|
94c1255782 | ||
|
|
476e1095fa | ||
|
|
8da1bb0418 | ||
|
|
01c58f8473 | ||
|
|
edfcf7abe2 | ||
|
|
37b44fe7c1 | ||
|
|
8f02ad4f12 | ||
|
|
51f1244600 | ||
|
|
7bd930368c | ||
|
|
fb69240ca0 | ||
|
|
830d53bfae | ||
|
|
c36a959549 | ||
|
|
e91b2d14e3 | ||
|
|
ac58e68bc3 | ||
|
|
504c1cedfe | ||
|
|
9a4d8fae82 | ||
|
|
7d2ba6394c | ||
|
|
b04b94da5f | ||
|
|
9933857f67 | ||
|
|
ed5641e249 | ||
|
|
a4257017ef | ||
|
|
18153f1b32 | ||
|
|
7a91d1fc43 | ||
|
|
af14ded75e | ||
|
|
65939effb5 | ||
|
|
66ee7b3234 | ||
|
|
cd47a628fc | ||
|
|
d7c78decb0 | ||
|
|
8749477ed0 | ||
|
|
7088f5b5fa | ||
|
|
5bb6328cb9 | ||
|
|
ce9f47de99 | ||
|
|
4c4780c25e | ||
|
|
64f1aba8f1 | ||
|
|
3359fb661f | ||
|
|
58a9f1b864 | ||
|
|
6ac41a4ef5 | ||
|
|
aa2af7ba74 | ||
|
|
ce73839fe4 | ||
|
|
1dc2726f8d | ||
|
|
af76e8174d | ||
|
|
402a3efc92 | ||
|
|
372f08c990 | ||
|
|
dd29eb7f81 | ||
|
|
bca788ab1d | ||
|
|
aef8fdba11 | ||
|
|
0a1603634b | ||
|
|
a662163fd5 | ||
|
|
bd7a6478a2 | ||
|
|
4a20c9f628 | ||
|
|
418c5cc3fc | ||
|
|
cc55d08832 | ||
|
|
de5c545648 | ||
|
|
a35099bd33 | ||
|
|
5f4b5cf044 | ||
|
|
beb10f843f | ||
|
|
29713e4268 | ||
|
|
8e4b83b96b | ||
|
|
ae603b500e | ||
|
|
d97aae7572 | ||
|
|
a55e2f04a0 | ||
|
|
6e53c91608 | ||
|
|
d2272fcf6e | ||
|
|
c7ac5dce8c | ||
|
|
5c1d459ae9 | ||
|
|
2e7daef502 | ||
|
|
6410229681 | ||
|
|
e40bd5f06b | ||
|
|
06b491eb7b | ||
|
|
3a9fadd6df | ||
|
|
0de9312a7e | ||
|
|
27fe5e3473 | ||
|
|
f67dcc09f5 | ||
|
|
fefc9d121d | ||
|
|
a319c33d8b | ||
|
|
218d6bcc05 | ||
|
|
7d25463972 | ||
|
|
aff84bec07 | ||
|
|
ac651e974e | ||
|
|
e21a55abcc | ||
|
|
bc03228ab5 | ||
|
|
f05d0e73c6 | ||
|
|
aed2d4b31e | ||
|
|
184a197441 | ||
|
|
ed676e8c0a | ||
|
|
8e1f937473 | ||
|
|
1a68d39211 | ||
|
|
4ba7d5b14c | ||
|
|
1a48181a9f | ||
|
|
6b70a4eb7d | ||
|
|
f01855813b | ||
|
|
4a3cdf81af | ||
|
|
f777397aca | ||
|
|
8fb2e5a4f5 | ||
|
|
4e8cc1e973 | ||
|
|
ff02a228e3 | ||
|
|
424266abb1 | ||
|
|
3fde134791 | ||
|
|
7c39a65543 | ||
|
|
8cf70de428 | ||
|
|
15ac8413c7 | ||
|
|
79c21abba7 | ||
|
|
d5c418f29f | ||
|
|
536b94e56f | ||
|
|
5c29dbd0c7 | ||
|
|
ba9e68f402 | ||
|
|
e9f65f8749 | ||
|
|
ae0dd4b298 | ||
|
|
f1ce35af1a | ||
|
|
6e617ed0b6 | ||
|
|
7cf97daf77 | ||
|
|
3d24d997ae | ||
|
|
115c281672 | ||
|
|
cce23e43a9 | ||
|
|
ff556f5c09 | ||
|
|
16fa01291b | ||
|
|
01534bf54f | ||
|
|
cd341b6e06 | ||
|
|
185a7e25e7 | ||
|
|
e81a474603 | ||
|
|
ff2be6e180 | ||
|
|
3da4b31359 | ||
|
|
4bbeb19fc7 | ||
|
|
a9cbab1735 | ||
|
|
6b7556a554 | ||
|
|
a3c7019e06 | ||
|
|
416b9c29f7 | ||
|
|
2ec8e04cac | ||
|
|
e03bfb30ce | ||
|
|
f5b669113f | ||
|
|
d08225edf4 | ||
|
|
8075d4f99d | ||
|
|
1a944d8a2a | ||
|
|
7cf02b6619 | ||
|
|
55cde6ef3c | ||
|
|
69c3af567d | ||
|
|
60e1fe0079 | ||
|
|
4669393070 | ||
|
|
ce3bfe5d57 | ||
|
|
2a0c2ca2b8 | ||
|
|
c89fbfb385 | ||
|
|
facecb84a1 | ||
|
|
ed06e9949b | ||
|
|
e15307a612 | ||
|
|
5cbb2699ee | ||
|
|
a2edf2e7ff | ||
|
|
1d31e7a2fc | ||
|
|
a2a4d5fa31 | ||
|
|
a28ccbabc6 | ||
|
|
edd7344820 | ||
|
|
c808ef81bb | ||
|
|
fd203fe357 | ||
|
|
5bb7ab9928 | ||
|
|
87270c8416 | ||
|
|
ebc2f7a2db | ||
|
|
7700207ec7 | ||
|
|
4d5d14f5cf | ||
|
|
72b249bf1f | ||
|
|
9b4774b21b | ||
|
|
2ddf083588 | ||
|
|
8343a03357 | ||
|
|
ad320e9b83 | ||
|
|
ecb750a446 | ||
|
|
5f88e02818 | ||
|
|
616af2f4b9 | ||
|
|
5a3b315b5f | ||
|
|
b7a2268e7b | ||
|
|
20d729228c | ||
|
|
af8c93086c | ||
|
|
79fd11ab8e | ||
|
|
cb88671e37 | ||
|
|
ff79552f13 | ||
|
|
643fe72717 | ||
|
|
4747e2183a | ||
|
|
c59e701e35 | ||
|
|
8e678af4ba | ||
|
|
70a1165b32 | ||
|
|
af14000215 | ||
|
|
998e6cdba0 | ||
|
|
2315fb5e5f | ||
|
|
157e9e5aa5 | ||
|
|
c496ec0848 | ||
|
|
15b67a268a | ||
|
|
31c4809827 | ||
|
|
ac0df2350a | ||
|
|
223b27f46c | ||
|
|
425142be60 | ||
|
|
7e17ec8c71 | ||
|
|
448830ce7b | ||
|
|
8896b614a9 | ||
|
|
a7fce980ad | ||
|
|
91757b0f37 | ||
|
|
fbfcc2972b | ||
|
|
db40364b87 | ||
|
|
094ce39c45 | ||
|
|
ae67d082fe | ||
|
|
8f76df7f37 | ||
|
|
5c19d18cbf | ||
|
|
838b93405b | ||
|
|
2676caf344 | ||
|
|
17941321ab | ||
|
|
48c971e073 | ||
|
|
f5e2efbbf0 | ||
|
|
5d1f0e607b | ||
|
|
b0872c19ea | ||
|
|
9f790b9901 | ||
|
|
c41a2ec4af | ||
|
|
575dad3c98 | ||
|
|
32d687f55e | ||
|
|
93f787070f | ||
|
|
f9544f6e8f | ||
|
|
336d19044c | ||
|
|
7866c9e173 | ||
|
|
1a4123de04 | ||
|
|
cf2e2eb1c0 | ||
|
|
2051acdeb2 | ||
|
|
cefdf970cc | ||
|
|
a1d0aa7b88 | ||
|
|
49aeedb8cb | ||
|
|
ef249a2cd7 | ||
|
|
a09141548a | ||
|
|
5379a2d40d | ||
|
|
c9450c7ab1 | ||
|
|
faa1b5c292 | ||
|
|
393d9fc6d2 | ||
|
|
4e6a228689 | ||
|
|
179d6678b1 | ||
|
|
85698c5086 | ||
|
|
a7d9ded45d | ||
|
|
531980d89c | ||
|
|
1887ecd4d6 | ||
|
|
cd32c2caba | ||
|
|
1c9a1457fc | ||
|
|
038b0eb1da | ||
|
|
f20bf146e2 | ||
|
|
01218f919b | ||
|
|
2684871bc1 | ||
|
|
ccf3960eec | ||
|
|
eecc0685c9 | ||
|
|
2ed849eccf | ||
|
|
3378d67a18 | ||
|
|
f3c0c667a6 | ||
|
|
0ae8bbac2d | ||
|
|
cbc3cfcab4 | ||
|
|
b30ef07c6c | ||
|
|
73900846b1 | ||
|
|
d1dc7e3991 | ||
|
|
3073a6d5e9 | ||
|
|
aae53774f2 | ||
|
|
7a757b7194 | ||
|
|
fa8ce26904 | ||
|
|
2c2c06e359 | ||
|
|
ee580538fa | ||
|
|
c3c5c31517 | ||
|
|
ed9a25dd61 | ||
|
|
9ef4f12b53 | ||
|
|
84f8101606 | ||
|
|
b1337948eb | ||
|
|
98f02fdde2 | ||
|
|
048fdc2292 | ||
|
|
2ca1c5aa9f | ||
|
|
674fb0fcc5 | ||
|
|
00bfe40e4d | ||
|
|
cd459b1d49 | ||
|
|
92a4793b3c | ||
|
|
dc03a42537 | ||
|
|
219da6bb68 | ||
|
|
0499cd866e | ||
|
|
13047f4135 | ||
|
|
af69cab21d | ||
|
|
d41a3fa1b4 | ||
|
|
733be371af | ||
|
|
576904bce6 | ||
|
|
cf47794f09 | ||
|
|
c06a9f8730 | ||
|
|
2e90dff2c2 | ||
|
|
90183a46d8 | ||
|
|
b68eedba23 | ||
|
|
d5b559393b | ||
|
|
1de4ac1385 | ||
|
|
39aa42ffbb | ||
|
|
ec1b9577ba | ||
|
|
3b4444f99a | ||
|
|
613b2d9dc6 | ||
|
|
8f4cc22455 | ||
|
|
7c42327e0e | ||
|
|
873383e9bd | ||
|
|
8508557e77 | ||
|
|
4d1652484f | ||
|
|
88cf6fb368 | ||
|
|
e7db87f700 | ||
|
|
2cb434e53e | ||
|
|
cd65491c30 | ||
|
|
082b1155a3 | ||
|
|
9202b1b787 | ||
|
|
a7e01c438d | ||
|
|
05be67e77d | ||
|
|
85741b9986 | ||
|
|
f247a199fe | ||
|
|
29171bc2d2 | ||
|
|
7be5a62ed7 | ||
|
|
3647136f24 | ||
|
|
13598940e3 | ||
|
|
0eb365868e | ||
|
|
28c6411e49 | ||
|
|
bba3fc7960 | ||
|
|
fcd877013e | ||
|
|
ba1d4c0488 | ||
|
|
517bcca299 | ||
|
|
1b53778175 | ||
|
|
b7a0304d92 | ||
|
|
545315a985 | ||
|
|
3f4327520c | ||
|
|
4a34f69ea6 | ||
|
|
fb7e68833c | ||
|
|
486dd09e0b | ||
|
|
054b99a330 | ||
|
|
65c5e044c7 | ||
|
|
11984c7467 | ||
|
|
3946864c8a | ||
|
|
b84037013e | ||
|
|
1dbfc62d75 | ||
|
|
d7d79106c7 | ||
|
|
1138491631 | ||
|
|
71705fa70d | ||
|
|
602814adab | ||
|
|
3a77719c5a | ||
|
|
7e195d0e92 | ||
|
|
e04793401d | ||
|
|
a3fbd18824 | ||
|
|
c6052b8c14 | ||
|
|
c792b5011f | ||
|
|
32aaeca775 | ||
|
|
1593194c63 | ||
|
|
614a7e1e23 | ||
|
|
2ebfeacabc | ||
|
|
f5d8f58a17 | ||
|
|
937daef4a7 | ||
|
|
dd77f14c64 | ||
|
|
c36cbe5a8a | ||
|
|
41b2194f86 | ||
|
|
d1e2e8f583 | ||
|
|
47fe42e1ab | ||
|
|
4c60393854 | ||
|
|
f848215dfc | ||
|
|
dcca581967 | ||
|
|
d475b3384c | ||
|
|
dd7831fe94 | ||
|
|
cc08b11d16 | ||
|
|
8bba753cca | ||
|
|
43d6280d0a | ||
|
|
e5a11a2293 | ||
|
|
f18ef2d144 | ||
|
|
1bb5c511a5 | ||
|
|
d55de57b67 | ||
|
|
a2aaf4dbc6 | ||
|
|
bdf6eee0ae | ||
|
|
8b910bda0c | ||
|
|
24993e3b39 | ||
|
|
11101076a1 | ||
|
|
f838875726 | ||
|
|
28778d6bae | ||
|
|
1132eae56d | ||
|
|
d34e79492d | ||
|
|
ab205b9dc8 | ||
|
|
7dcad95d4f | ||
|
|
8a48223a7b | ||
|
|
d47ae7f620 | ||
|
|
135c9c42bf | ||
|
|
0bf79ac455 | ||
|
|
98998cded6 | ||
|
|
14137b5781 | ||
|
|
a172d96292 | ||
|
|
23ba76bc0e | ||
|
|
61e00a9775 | ||
|
|
d1508cd68d | ||
|
|
9c85b5376d | ||
|
|
3c6f245083 | ||
|
|
f207019ce5 | ||
|
|
bd05aa4e24 | ||
|
|
8dc9d361c2 | ||
|
|
d0e958c71c | ||
|
|
a0bb7c5593 | ||
|
|
7feddd9fc7 | ||
|
|
55969016e9 | ||
|
|
9609f02e3c | ||
|
|
5c7495a194 | ||
|
|
5ee6fc974e | ||
|
|
c2ebea6580 | ||
|
|
12a129ec6d | ||
|
|
f28fe66970 | ||
|
|
123397317c | ||
|
|
dc570c4951 | ||
|
|
22d3628319 | ||
|
|
50c9949d7a | ||
|
|
376817c6d4 | ||
|
|
63fc800057 | ||
|
|
e0d0572b73 |
@@ -2,6 +2,7 @@ language: python
|
|||||||
python:
|
python:
|
||||||
- "2.6"
|
- "2.6"
|
||||||
- "2.7"
|
- "2.7"
|
||||||
|
- "3.2"
|
||||||
- "3.3"
|
- "3.3"
|
||||||
- "3.4"
|
- "3.4"
|
||||||
before_install:
|
before_install:
|
||||||
|
|||||||
19
AUTHORS
19
AUTHORS
@@ -113,3 +113,22 @@ Robin de Rooij
|
|||||||
Ryan Schmidt
|
Ryan Schmidt
|
||||||
Leslie P. Polzer
|
Leslie P. Polzer
|
||||||
Duncan Keall
|
Duncan Keall
|
||||||
|
Alexander Mamay
|
||||||
|
Devin J. Pohly
|
||||||
|
Eduardo Ferro Aldama
|
||||||
|
Jeff Buchbinder
|
||||||
|
Amish Bhadeshia
|
||||||
|
Joram Schrijver
|
||||||
|
Will W.
|
||||||
|
Mohammad Teimori Pabandi
|
||||||
|
Roman Le Négrate
|
||||||
|
Matthias Küch
|
||||||
|
Julian Richen
|
||||||
|
Ping O.
|
||||||
|
Mister Hat
|
||||||
|
Peter Ding
|
||||||
|
jackyzy823
|
||||||
|
George Brighton
|
||||||
|
Remita Amine
|
||||||
|
Aurélio A. Heckert
|
||||||
|
Bernhard Minks
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ If your report is shorter than two lines, it is almost certainly missing some of
|
|||||||
|
|
||||||
For bug reports, this means that your report should contain the *complete* output of youtube-dl when called with the -v flag. The error message you get for (most) bugs even says so, but you would not believe how many of our bug reports do not contain this information.
|
For bug reports, this means that your report should contain the *complete* output of youtube-dl when called with the -v flag. The error message you get for (most) bugs even says so, but you would not believe how many of our bug reports do not contain this information.
|
||||||
|
|
||||||
Site support requests **must contain an example URL**. An example URL is a URL you might want to download, like http://www.youtube.com/watch?v=BaW_jenozKc . There should be an obvious video present. Except under very special circumstances, the main page of a video service (e.g. http://www.youtube.com/ ) is *not* an example URL.
|
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.
|
||||||
|
|
||||||
### Are you using the latest version?
|
### Are you using the latest version?
|
||||||
|
|
||||||
|
|||||||
2
Makefile
2
Makefile
@@ -2,7 +2,7 @@ all: youtube-dl README.md CONTRIBUTING.md README.txt youtube-dl.1 youtube-dl.bas
|
|||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf youtube-dl.1.temp.md youtube-dl.1 youtube-dl.bash-completion README.txt MANIFEST build/ dist/ .coverage cover/ youtube-dl.tar.gz youtube-dl.zsh youtube-dl.fish *.dump *.part *.info.json *.mp4 *.flv *.mp3 *.avi CONTRIBUTING.md.tmp youtube-dl youtube-dl.exe
|
rm -rf youtube-dl.1.temp.md youtube-dl.1 youtube-dl.bash-completion README.txt MANIFEST build/ dist/ .coverage cover/ youtube-dl.tar.gz youtube-dl.zsh youtube-dl.fish *.dump *.part *.info.json *.mp4 *.flv *.mp3 *.avi CONTRIBUTING.md.tmp youtube-dl youtube-dl.exe
|
||||||
find -name "*.pyc" -delete
|
find . -name "*.pyc" -delete
|
||||||
|
|
||||||
PREFIX ?= /usr/local
|
PREFIX ?= /usr/local
|
||||||
BINDIR ?= $(PREFIX)/bin
|
BINDIR ?= $(PREFIX)/bin
|
||||||
|
|||||||
287
README.md
287
README.md
@@ -5,6 +5,7 @@ youtube-dl - download videos from youtube.com or other video platforms
|
|||||||
- [OPTIONS](#options)
|
- [OPTIONS](#options)
|
||||||
- [CONFIGURATION](#configuration)
|
- [CONFIGURATION](#configuration)
|
||||||
- [OUTPUT TEMPLATE](#output-template)
|
- [OUTPUT TEMPLATE](#output-template)
|
||||||
|
- [FORMAT SELECTION](#format-selection)
|
||||||
- [VIDEO SELECTION](#video-selection)
|
- [VIDEO SELECTION](#video-selection)
|
||||||
- [FAQ](#faq)
|
- [FAQ](#faq)
|
||||||
- [DEVELOPER INSTRUCTIONS](#developer-instructions)
|
- [DEVELOPER INSTRUCTIONS](#developer-instructions)
|
||||||
@@ -16,12 +17,12 @@ youtube-dl - download videos from youtube.com or other video platforms
|
|||||||
To install it right away for all UNIX users (Linux, OS X, etc.), type:
|
To install it right away for all UNIX users (Linux, OS X, etc.), type:
|
||||||
|
|
||||||
sudo curl https://yt-dl.org/latest/youtube-dl -o /usr/local/bin/youtube-dl
|
sudo curl https://yt-dl.org/latest/youtube-dl -o /usr/local/bin/youtube-dl
|
||||||
sudo chmod a+x /usr/local/bin/youtube-dl
|
sudo chmod a+rx /usr/local/bin/youtube-dl
|
||||||
|
|
||||||
If you do not have curl, you can alternatively use a recent wget:
|
If you do not have curl, you can alternatively use a recent wget:
|
||||||
|
|
||||||
sudo wget https://yt-dl.org/downloads/latest/youtube-dl -O /usr/local/bin/youtube-dl
|
sudo wget https://yt-dl.org/downloads/latest/youtube-dl -O /usr/local/bin/youtube-dl
|
||||||
sudo chmod a+x /usr/local/bin/youtube-dl
|
sudo chmod a+rx /usr/local/bin/youtube-dl
|
||||||
|
|
||||||
Windows users can [download a .exe file](https://yt-dl.org/latest/youtube-dl.exe) and place it in their home directory or any other location on their [PATH](http://en.wikipedia.org/wiki/PATH_%28variable%29).
|
Windows users can [download a .exe file](https://yt-dl.org/latest/youtube-dl.exe) and place it in their home directory or any other location on their [PATH](http://en.wikipedia.org/wiki/PATH_%28variable%29).
|
||||||
|
|
||||||
@@ -45,21 +46,22 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
youtube-dl [OPTIONS] URL [URL...]
|
youtube-dl [OPTIONS] URL [URL...]
|
||||||
|
|
||||||
# OPTIONS
|
# OPTIONS
|
||||||
-h, --help print this help text and exit
|
-h, --help Print this help text and exit
|
||||||
--version print program version and exit
|
--version Print program version and exit
|
||||||
-U, --update update this program to latest version. Make sure that you have sufficient permissions (run with sudo if needed)
|
-U, --update Update this program to latest version. Make sure that you have sufficient permissions (run with sudo if needed)
|
||||||
-i, --ignore-errors continue on download errors, for example to skip unavailable videos in a playlist
|
-i, --ignore-errors Continue on download errors, for example to skip unavailable videos in a playlist
|
||||||
--abort-on-error Abort downloading of further videos (in the playlist or the command line) if an error occurs
|
--abort-on-error Abort downloading of further videos (in the playlist or the command line) if an error occurs
|
||||||
--dump-user-agent display the current browser identification
|
--dump-user-agent Display the current browser identification
|
||||||
--list-extractors List all supported extractors and the URLs they would handle
|
--list-extractors List all supported extractors
|
||||||
--extractor-descriptions Output descriptions of all supported extractors
|
--extractor-descriptions Output descriptions of all supported extractors
|
||||||
|
--force-generic-extractor Force extraction to use the generic extractor
|
||||||
--default-search PREFIX Use this prefix for unqualified URLs. For example "gvsearch2:" downloads two videos from google videos for youtube-dl "large apple".
|
--default-search PREFIX Use this prefix for unqualified URLs. For example "gvsearch2:" downloads two videos from google videos for youtube-dl "large apple".
|
||||||
Use the value "auto" to let youtube-dl guess ("auto_warning" to emit a warning when guessing). "error" just throws an error. The
|
Use the value "auto" to let youtube-dl guess ("auto_warning" to emit a warning when guessing). "error" just throws an error. The
|
||||||
default value "fixup_error" repairs broken URLs, but emits an error if this is not possible instead of searching.
|
default value "fixup_error" repairs broken URLs, but emits an error if this is not possible instead of searching.
|
||||||
--ignore-config Do not read configuration files. When given in the global configuration file /etc/youtube-dl.conf: Do not read the user configuration
|
--ignore-config Do not read configuration files. When given in the global configuration file /etc/youtube-dl.conf: Do not read the user configuration
|
||||||
in ~/.config/youtube-dl/config (%APPDATA%/youtube-dl/config.txt on Windows)
|
in ~/.config/youtube-dl/config (%APPDATA%/youtube-dl/config.txt on Windows)
|
||||||
--flat-playlist Do not extract the videos of a playlist, only list them.
|
--flat-playlist Do not extract the videos of a playlist, only list them.
|
||||||
--no-color Do not emit color codes in output.
|
--no-color Do not emit color codes in output
|
||||||
|
|
||||||
## Network Options:
|
## Network Options:
|
||||||
--proxy URL Use the specified HTTP/HTTPS proxy. Pass in an empty string (--proxy "") for direct connection
|
--proxy URL Use the specified HTTP/HTTPS proxy. Pass in an empty string (--proxy "") for direct connection
|
||||||
@@ -71,70 +73,70 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
not present) is used for the actual downloading. (experimental)
|
not present) is used for the actual downloading. (experimental)
|
||||||
|
|
||||||
## Video Selection:
|
## Video Selection:
|
||||||
--playlist-start NUMBER playlist video to start at (default is 1)
|
--playlist-start NUMBER Playlist video to start at (default is 1)
|
||||||
--playlist-end NUMBER playlist video to end at (default is last)
|
--playlist-end NUMBER Playlist video to end at (default is last)
|
||||||
--playlist-items ITEM_SPEC playlist video items to download. Specify indices of the videos in the playlist seperated by commas like: "--playlist-items 1,2,5,8"
|
--playlist-items ITEM_SPEC Playlist video items to download. Specify indices of the videos in the playlist seperated by commas like: "--playlist-items 1,2,5,8"
|
||||||
if you want to download videos indexed 1, 2, 5, 8 in the playlist. You can specify range: "--playlist-items 1-3,7,10-13", it will
|
if you want to download videos indexed 1, 2, 5, 8 in the playlist. You can specify range: "--playlist-items 1-3,7,10-13", it will
|
||||||
download the videos at index 1, 2, 3, 7, 10, 11, 12 and 13.
|
download the videos at index 1, 2, 3, 7, 10, 11, 12 and 13.
|
||||||
--match-title REGEX download only matching titles (regex or caseless sub-string)
|
--match-title REGEX Download only matching titles (regex or caseless sub-string)
|
||||||
--reject-title REGEX skip download for matching titles (regex or caseless sub-string)
|
--reject-title REGEX Skip download for matching titles (regex or caseless sub-string)
|
||||||
--max-downloads NUMBER Abort after downloading NUMBER files
|
--max-downloads NUMBER Abort after downloading NUMBER files
|
||||||
--min-filesize SIZE Do not download any videos smaller than SIZE (e.g. 50k or 44.6m)
|
--min-filesize SIZE Do not download any videos smaller than SIZE (e.g. 50k or 44.6m)
|
||||||
--max-filesize SIZE Do not download any videos larger than SIZE (e.g. 50k or 44.6m)
|
--max-filesize SIZE Do not download any videos larger than SIZE (e.g. 50k or 44.6m)
|
||||||
--date DATE download only videos uploaded in this date
|
--date DATE Download only videos uploaded in this date
|
||||||
--datebefore DATE download only videos uploaded on or before this date (i.e. inclusive)
|
--datebefore DATE Download only videos uploaded on or before this date (i.e. inclusive)
|
||||||
--dateafter DATE download only videos uploaded on or after this date (i.e. inclusive)
|
--dateafter DATE Download only videos uploaded on or after this date (i.e. inclusive)
|
||||||
--min-views COUNT Do not download any videos with less than COUNT views
|
--min-views COUNT Do not download any videos with less than COUNT views
|
||||||
--max-views COUNT Do not download any videos with more than COUNT views
|
--max-views COUNT Do not download any videos with more than COUNT views
|
||||||
--match-filter FILTER (Experimental) Generic video filter. Specify any key (see help for -o for a list of available keys) to match if the key is present,
|
--match-filter FILTER Generic video filter (experimental). Specify any key (see help for -o for a list of available keys) to match if the key is present,
|
||||||
!key to check if the key is not present,key > NUMBER (like "comment_count > 12", also works with >=, <, <=, !=, =) to compare against
|
!key to check if the key is not present,key > NUMBER (like "comment_count > 12", also works with >=, <, <=, !=, =) to compare against
|
||||||
a number, and & to require multiple matches. Values which are not known are excluded unless you put a question mark (?) after the
|
a number, and & to require multiple matches. Values which are not known are excluded unless you put a question mark (?) after the
|
||||||
operator.For example, to only match videos that have been liked more than 100 times and disliked less than 50 times (or the dislike
|
operator.For example, to only match videos that have been liked more than 100 times and disliked less than 50 times (or the dislike
|
||||||
functionality is not available at the given service), but who also have a description, use --match-filter "like_count > 100 &
|
functionality is not available at the given service), but who also have a description, use --match-filter "like_count > 100 &
|
||||||
dislike_count <? 50 & description" .
|
dislike_count <? 50 & description" .
|
||||||
--no-playlist If the URL refers to a video and a playlist, download only the video.
|
--no-playlist Download only the video, if the URL refers to a video and a playlist.
|
||||||
--yes-playlist If the URL refers to a video and a playlist, download the playlist.
|
--yes-playlist Download the playlist, if the URL refers to a video and a playlist.
|
||||||
--age-limit YEARS download only videos suitable for the given age
|
--age-limit YEARS Download only videos suitable for the given age
|
||||||
--download-archive FILE Download only videos not listed in the archive file. Record the IDs of all downloaded videos in it.
|
--download-archive FILE Download only videos not listed in the archive file. Record the IDs of all downloaded videos in it.
|
||||||
--include-ads Download advertisements as well (experimental)
|
--include-ads Download advertisements as well (experimental)
|
||||||
|
|
||||||
## Download Options:
|
## Download Options:
|
||||||
-r, --rate-limit LIMIT maximum download rate in bytes per second (e.g. 50K or 4.2M)
|
-r, --rate-limit LIMIT Maximum download rate in bytes per second (e.g. 50K or 4.2M)
|
||||||
-R, --retries RETRIES number of retries (default is 10), or "infinite".
|
-R, --retries RETRIES Number of retries (default is 10), or "infinite".
|
||||||
--buffer-size SIZE size of download buffer (e.g. 1024 or 16K) (default is 1024)
|
--buffer-size SIZE Size of download buffer (e.g. 1024 or 16K) (default is 1024)
|
||||||
--no-resize-buffer do not automatically adjust the buffer size. By default, the buffer size is automatically resized from an initial value of SIZE.
|
--no-resize-buffer Do not automatically adjust the buffer size. By default, the buffer size is automatically resized from an initial value of SIZE.
|
||||||
--playlist-reverse Download playlist videos in reverse order
|
--playlist-reverse Download playlist videos in reverse order
|
||||||
--xattr-set-filesize (experimental) set file xattribute ytdl.filesize with expected filesize
|
--xattr-set-filesize Set file xattribute ytdl.filesize with expected filesize (experimental)
|
||||||
--hls-prefer-native (experimental) Use the native HLS downloader instead of ffmpeg.
|
--hls-prefer-native Use the native HLS downloader instead of ffmpeg (experimental)
|
||||||
--external-downloader COMMAND Use the specified external downloader. Currently supports aria2c,curl,wget
|
--external-downloader COMMAND Use the specified external downloader. Currently supports aria2c,curl,httpie,wget
|
||||||
--external-downloader-args ARGS Give these arguments to the external downloader.
|
--external-downloader-args ARGS Give these arguments to the external downloader
|
||||||
|
|
||||||
## Filesystem Options:
|
## Filesystem Options:
|
||||||
-a, --batch-file FILE file containing URLs to download ('-' for stdin)
|
-a, --batch-file FILE File containing URLs to download ('-' for stdin)
|
||||||
--id use only video ID in file name
|
--id Use only video ID in file name
|
||||||
-o, --output TEMPLATE output filename template. Use %(title)s to get the title, %(uploader)s for the uploader name, %(uploader_id)s for the uploader
|
-o, --output TEMPLATE Output filename template. Use %(title)s to get the title, %(uploader)s for the uploader name, %(uploader_id)s for the uploader
|
||||||
nickname if different, %(autonumber)s to get an automatically incremented number, %(ext)s for the filename extension, %(format)s for
|
nickname if different, %(autonumber)s to get an automatically incremented number, %(ext)s for the filename extension, %(format)s for
|
||||||
the format description (like "22 - 1280x720" or "HD"), %(format_id)s for the unique id of the format (like Youtube's itags: "137"),
|
the format description (like "22 - 1280x720" or "HD"), %(format_id)s for the unique id of the format (like YouTube's itags: "137"),
|
||||||
%(upload_date)s for the upload date (YYYYMMDD), %(extractor)s for the provider (youtube, metacafe, etc), %(id)s for the video id,
|
%(upload_date)s for the upload date (YYYYMMDD), %(extractor)s for the provider (youtube, metacafe, etc), %(id)s for the video id,
|
||||||
%(playlist_title)s, %(playlist_id)s, or %(playlist)s (=title if present, ID otherwise) for the playlist the video is in,
|
%(playlist_title)s, %(playlist_id)s, or %(playlist)s (=title if present, ID otherwise) for the playlist the video is in,
|
||||||
%(playlist_index)s for the position in the playlist. %(height)s and %(width)s for the width and height of the video format.
|
%(playlist_index)s for the position in the playlist. %(height)s and %(width)s for the width and height of the video format.
|
||||||
%(resolution)s for a textual description of the resolution of the video format. %% for a literal percent. Use - to output to stdout.
|
%(resolution)s for a textual description of the resolution of the video format. %% for a literal percent. Use - to output to stdout.
|
||||||
Can also be used to download to a different directory, for example with -o '/my/downloads/%(uploader)s/%(title)s-%(id)s.%(ext)s' .
|
Can also be used to download to a different directory, for example with -o '/my/downloads/%(uploader)s/%(title)s-%(id)s.%(ext)s' .
|
||||||
--autonumber-size NUMBER Specifies the number of digits in %(autonumber)s when it is present in output filename template or --auto-number option is given
|
--autonumber-size NUMBER Specify the number of digits in %(autonumber)s when it is present in output filename template or --auto-number option is given
|
||||||
--restrict-filenames Restrict filenames to only ASCII characters, and avoid "&" and spaces in filenames
|
--restrict-filenames Restrict filenames to only ASCII characters, and avoid "&" and spaces in filenames
|
||||||
-A, --auto-number [deprecated; use -o "%(autonumber)s-%(title)s.%(ext)s" ] number downloaded files starting from 00000
|
-A, --auto-number [deprecated; use -o "%(autonumber)s-%(title)s.%(ext)s" ] Number downloaded files starting from 00000
|
||||||
-t, --title [deprecated] use title in file name (default)
|
-t, --title [deprecated] Use title in file name (default)
|
||||||
-l, --literal [deprecated] alias of --title
|
-l, --literal [deprecated] Alias of --title
|
||||||
-w, --no-overwrites do not overwrite files
|
-w, --no-overwrites Do not overwrite files
|
||||||
-c, --continue force resume of partially downloaded files. By default, youtube-dl will resume downloads if possible.
|
-c, --continue Force resume of partially downloaded files. By default, youtube-dl will resume downloads if possible.
|
||||||
--no-continue do not resume partially downloaded files (restart from beginning)
|
--no-continue Do not resume partially downloaded files (restart from beginning)
|
||||||
--no-part do not use .part files - write directly into output file
|
--no-part Do not use .part files - write directly into output file
|
||||||
--no-mtime do not use the Last-modified header to set the file modification time
|
--no-mtime Do not use the Last-modified header to set the file modification time
|
||||||
--write-description write video description to a .description file
|
--write-description Write video description to a .description file
|
||||||
--write-info-json write video metadata to a .info.json file
|
--write-info-json Write video metadata to a .info.json file
|
||||||
--write-annotations write video annotations to a .annotation file
|
--write-annotations Write video annotations to a .annotations.xml file
|
||||||
--load-info FILE json file containing the video information (created with the "--write-json" option)
|
--load-info FILE JSON file containing the video information (created with the "--write-info-json" option)
|
||||||
--cookies FILE file to read cookies from and dump cookie jar in
|
--cookies FILE File to read cookies from and dump cookie jar in
|
||||||
--cache-dir DIR Location in the filesystem where youtube-dl can store some downloaded information permanently. By default $XDG_CACHE_HOME/youtube-dl
|
--cache-dir DIR Location in the filesystem where youtube-dl can store some downloaded information permanently. By default $XDG_CACHE_HOME/youtube-dl
|
||||||
or ~/.cache/youtube-dl . At the moment, only YouTube player files (for videos with obfuscated signatures) are cached, but that may
|
or ~/.cache/youtube-dl . At the moment, only YouTube player files (for videos with obfuscated signatures) are cached, but that may
|
||||||
change.
|
change.
|
||||||
@@ -142,93 +144,87 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
--rm-cache-dir Delete all filesystem cache files
|
--rm-cache-dir Delete all filesystem cache files
|
||||||
|
|
||||||
## Thumbnail images:
|
## Thumbnail images:
|
||||||
--write-thumbnail write thumbnail image to disk
|
--write-thumbnail Write thumbnail image to disk
|
||||||
--write-all-thumbnails write all thumbnail image formats to disk
|
--write-all-thumbnails Write all thumbnail image formats to disk
|
||||||
--list-thumbnails Simulate and list all available thumbnail formats
|
--list-thumbnails Simulate and list all available thumbnail formats
|
||||||
|
|
||||||
## Verbosity / Simulation Options:
|
## Verbosity / Simulation Options:
|
||||||
-q, --quiet activates quiet mode
|
-q, --quiet Activate quiet mode
|
||||||
--no-warnings Ignore warnings
|
--no-warnings Ignore warnings
|
||||||
-s, --simulate do not download the video and do not write anything to disk
|
-s, --simulate Do not download the video and do not write anything to disk
|
||||||
--skip-download do not download the video
|
--skip-download Do not download the video
|
||||||
-g, --get-url simulate, quiet but print URL
|
-g, --get-url Simulate, quiet but print URL
|
||||||
-e, --get-title simulate, quiet but print title
|
-e, --get-title Simulate, quiet but print title
|
||||||
--get-id simulate, quiet but print id
|
--get-id Simulate, quiet but print id
|
||||||
--get-thumbnail simulate, quiet but print thumbnail URL
|
--get-thumbnail Simulate, quiet but print thumbnail URL
|
||||||
--get-description simulate, quiet but print video description
|
--get-description Simulate, quiet but print video description
|
||||||
--get-duration simulate, quiet but print video length
|
--get-duration Simulate, quiet but print video length
|
||||||
--get-filename simulate, quiet but print output filename
|
--get-filename Simulate, quiet but print output filename
|
||||||
--get-format simulate, quiet but print output format
|
--get-format Simulate, quiet but print output format
|
||||||
-j, --dump-json simulate, quiet but print JSON information. See --output for a description of available keys.
|
-j, --dump-json Simulate, quiet but print JSON information. See --output for a description of available keys.
|
||||||
-J, --dump-single-json simulate, quiet but print JSON information for each command-line argument. If the URL refers to a playlist, dump the whole playlist
|
-J, --dump-single-json Simulate, quiet but print JSON information for each command-line argument. If the URL refers to a playlist, dump the whole playlist
|
||||||
information in a single line.
|
information in a single line.
|
||||||
--print-json Be quiet and print the video information as JSON (video is still being downloaded).
|
--print-json Be quiet and print the video information as JSON (video is still being downloaded).
|
||||||
--newline output progress bar as new lines
|
--newline Output progress bar as new lines
|
||||||
--no-progress do not print progress bar
|
--no-progress Do not print progress bar
|
||||||
--console-title display progress in console titlebar
|
--console-title Display progress in console titlebar
|
||||||
-v, --verbose print various debugging information
|
-v, --verbose Print various debugging information
|
||||||
--dump-intermediate-pages print downloaded pages to debug problems (very verbose)
|
--dump-pages Print downloaded pages encoded using base64 to debug problems (very verbose)
|
||||||
--write-pages Write downloaded intermediary pages to files in the current directory to debug problems
|
--write-pages Write downloaded intermediary pages to files in the current directory to debug problems
|
||||||
--print-traffic Display sent and read HTTP traffic
|
--print-traffic Display sent and read HTTP traffic
|
||||||
-C, --call-home Contact the youtube-dl server for debugging.
|
-C, --call-home Contact the youtube-dl server for debugging
|
||||||
--no-call-home Do NOT contact the youtube-dl server for debugging.
|
--no-call-home Do NOT contact the youtube-dl server for debugging
|
||||||
|
|
||||||
## Workarounds:
|
## Workarounds:
|
||||||
--encoding ENCODING Force the specified encoding (experimental)
|
--encoding ENCODING Force the specified encoding (experimental)
|
||||||
--no-check-certificate Suppress HTTPS certificate validation.
|
--no-check-certificate Suppress HTTPS certificate validation
|
||||||
--prefer-insecure Use an unencrypted connection to retrieve information about the video. (Currently supported only for YouTube)
|
--prefer-insecure Use an unencrypted connection to retrieve information about the video. (Currently supported only for YouTube)
|
||||||
--user-agent UA specify a custom user agent
|
--user-agent UA Specify a custom user agent
|
||||||
--referer URL specify a custom referer, use if the video access is restricted to one domain
|
--referer URL Specify a custom referer, use if the video access is restricted to one domain
|
||||||
--add-header FIELD:VALUE specify a custom HTTP header and its value, separated by a colon ':'. You can use this option multiple times
|
--add-header FIELD:VALUE Specify a custom HTTP header and its value, separated by a colon ':'. You can use this option multiple times
|
||||||
--bidi-workaround Work around terminals that lack bidirectional text support. Requires bidiv or fribidi executable in PATH
|
--bidi-workaround Work around terminals that lack bidirectional text support. Requires bidiv or fribidi executable in PATH
|
||||||
--sleep-interval SECONDS Number of seconds to sleep before each download.
|
--sleep-interval SECONDS Number of seconds to sleep before each download.
|
||||||
|
|
||||||
## Video Format Options:
|
## Video Format Options:
|
||||||
-f, --format FORMAT video format code, specify the order of preference using slashes, as in -f 22/17/18 . Instead of format codes, you can select by
|
-f, --format FORMAT Video format code, see the "FORMAT SELECTION" for all the info
|
||||||
extension for the extensions aac, m4a, mp3, mp4, ogg, wav, webm. You can also use the special names "best", "bestvideo", "bestaudio",
|
--all-formats Download all available video formats
|
||||||
"worst". You can filter the video results by putting a condition in brackets, as in -f "best[height=720]" (or -f "[filesize>10M]").
|
--prefer-free-formats Prefer free video formats unless a specific one is requested
|
||||||
This works for filesize, height, width, tbr, abr, vbr, asr, and fps and the comparisons <, <=, >, >=, =, != and for ext, acodec,
|
-F, --list-formats List all available formats
|
||||||
vcodec, container, and protocol and the comparisons =, != . Formats for which the value is not known are excluded unless you put a
|
--youtube-skip-dash-manifest Do not download the DASH manifests and related data on YouTube videos
|
||||||
question mark (?) after the operator. You can combine format filters, so -f "[height <=? 720][tbr>500]" selects up to 720p videos
|
|
||||||
(or videos where the height is not known) with a bitrate of at least 500 KBit/s. By default, youtube-dl will pick the best quality.
|
|
||||||
Use commas to download multiple audio formats, such as -f 136/137/mp4/bestvideo,140/m4a/bestaudio. You can merge the video and audio
|
|
||||||
of two formats into a single file using -f <video-format>+<audio-format> (requires ffmpeg or avconv), for example -f
|
|
||||||
bestvideo+bestaudio.
|
|
||||||
--all-formats download all available video formats
|
|
||||||
--prefer-free-formats prefer free video formats unless a specific one is requested
|
|
||||||
--max-quality FORMAT highest quality format to download
|
|
||||||
-F, --list-formats list all available formats
|
|
||||||
--youtube-skip-dash-manifest Do not download the DASH manifest on YouTube videos
|
|
||||||
--merge-output-format FORMAT If a merge is required (e.g. bestvideo+bestaudio), output to given container format. One of mkv, mp4, ogg, webm, flv. Ignored if no
|
--merge-output-format FORMAT If a merge is required (e.g. bestvideo+bestaudio), output to given container format. One of mkv, mp4, ogg, webm, flv. Ignored if no
|
||||||
merge is required
|
merge is required
|
||||||
|
|
||||||
## Subtitle Options:
|
## Subtitle Options:
|
||||||
--write-sub write subtitle file
|
--write-sub Write subtitle file
|
||||||
--write-auto-sub write automatic subtitle file (youtube only)
|
--write-auto-sub Write automatic subtitle file (YouTube only)
|
||||||
--all-subs downloads all the available subtitles of the video
|
--all-subs Download all the available subtitles of the video
|
||||||
--list-subs lists all available subtitles for the video
|
--list-subs List all available subtitles for the video
|
||||||
--sub-format FORMAT subtitle format, accepts formats preference, for example: "ass/srt/best"
|
--sub-format FORMAT Subtitle format, accepts formats preference, for example: "srt" or "ass/srt/best"
|
||||||
--sub-lang LANGS languages of the subtitles to download (optional) separated by commas, use IETF language tags like 'en,pt'
|
--sub-lang LANGS Languages of the subtitles to download (optional) separated by commas, use IETF language tags like 'en,pt'
|
||||||
|
|
||||||
## Authentication Options:
|
## Authentication Options:
|
||||||
-u, --username USERNAME login with this account ID
|
-u, --username USERNAME Login with this account ID
|
||||||
-p, --password PASSWORD account password. If this option is left out, youtube-dl will ask interactively.
|
-p, --password PASSWORD Account password. If this option is left out, youtube-dl will ask interactively.
|
||||||
-2, --twofactor TWOFACTOR two-factor auth code
|
-2, --twofactor TWOFACTOR Two-factor auth code
|
||||||
-n, --netrc use .netrc authentication data
|
-n, --netrc Use .netrc authentication data
|
||||||
--video-password PASSWORD video password (vimeo, smotri)
|
--video-password PASSWORD Video password (vimeo, smotri)
|
||||||
|
|
||||||
## Post-processing Options:
|
## Post-processing Options:
|
||||||
-x, --extract-audio convert video files to audio-only files (requires ffmpeg or avconv and ffprobe or avprobe)
|
-x, --extract-audio Convert video files to audio-only files (requires ffmpeg or avconv and ffprobe or avprobe)
|
||||||
--audio-format FORMAT "best", "aac", "vorbis", "mp3", "m4a", "opus", or "wav"; "best" by default
|
--audio-format FORMAT Specify audio format: "best", "aac", "vorbis", "mp3", "m4a", "opus", or "wav"; "best" by default
|
||||||
--audio-quality QUALITY ffmpeg/avconv audio quality specification, insert a value between 0 (better) and 9 (worse) for VBR or a specific bitrate like 128K
|
--audio-quality QUALITY Specify ffmpeg/avconv audio quality, insert a value between 0 (better) and 9 (worse) for VBR or a specific bitrate like 128K (default
|
||||||
(default 5)
|
5)
|
||||||
--recode-video FORMAT Encode the video to another format if necessary (currently supported: mp4|flv|ogg|webm|mkv)
|
--recode-video FORMAT Encode the video to another format if necessary (currently supported: mp4|flv|ogg|webm|mkv|avi)
|
||||||
-k, --keep-video keeps the video file on disk after the post-processing; the video is erased by default
|
--postprocessor-args ARGS Give these arguments to the postprocessor
|
||||||
--no-post-overwrites do not overwrite post-processed files; the post-processed files are overwritten by default
|
-k, --keep-video Keep the video file on disk after the post-processing; the video is erased by default
|
||||||
--embed-subs embed subtitles in the video (only for mp4 videos)
|
--no-post-overwrites Do not overwrite post-processed files; the post-processed files are overwritten by default
|
||||||
--embed-thumbnail embed thumbnail in the audio as cover art
|
--embed-subs Embed subtitles in the video (only for mkv and mp4 videos)
|
||||||
--add-metadata write metadata to the video file
|
--embed-thumbnail Embed thumbnail in the audio as cover art
|
||||||
--xattrs write metadata to the video file's xattrs (using dublin core and xdg standards)
|
--add-metadata Write metadata to the video file
|
||||||
|
--metadata-from-title FORMAT Parse additional metadata like song title / artist from the video title. The format syntax is the same as --output, the parsed
|
||||||
|
parameters replace existing values. Additional templates: %(album)s, %(artist)s. Example: --metadata-from-title "%(artist)s -
|
||||||
|
%(title)s" matches a title like "Coldplay - Paradise"
|
||||||
|
--xattrs Write metadata to the video file's xattrs (using dublin core and xdg standards)
|
||||||
--fixup POLICY Automatically correct known faults of the file. One of never (do nothing), warn (only emit a warning), detect_or_warn (the default;
|
--fixup POLICY Automatically correct known faults of the file. One of never (do nothing), warn (only emit a warning), detect_or_warn (the default;
|
||||||
fix file if we can, warn otherwise)
|
fix file if we can, warn otherwise)
|
||||||
--prefer-avconv Prefer avconv over ffmpeg for running the postprocessors (default)
|
--prefer-avconv Prefer avconv over ffmpeg for running the postprocessors (default)
|
||||||
@@ -242,6 +238,26 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
|
|
||||||
You can configure youtube-dl by placing default arguments (such as `--extract-audio --no-mtime` to always extract the audio and not copy the mtime) into `/etc/youtube-dl.conf` and/or `~/.config/youtube-dl/config`. On Windows, the configuration file locations are `%APPDATA%\youtube-dl\config.txt` and `C:\Users\<user name>\youtube-dl.conf`.
|
You can configure youtube-dl by placing default arguments (such as `--extract-audio --no-mtime` to always extract the audio and not copy the mtime) into `/etc/youtube-dl.conf` and/or `~/.config/youtube-dl/config`. On Windows, the configuration file locations are `%APPDATA%\youtube-dl\config.txt` and `C:\Users\<user name>\youtube-dl.conf`.
|
||||||
|
|
||||||
|
### 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 shell command history. You can achieve this using [`.netrc` file](http://stackoverflow.com/tags/.netrc/info) on per extractor basis. For that you will need to create `.netrc` file in your `$HOME` and restrict permissions to read/write by you only:
|
||||||
|
```
|
||||||
|
touch $HOME/.netrc
|
||||||
|
chmod a-rwx,u+rw $HOME/.netrc
|
||||||
|
```
|
||||||
|
After that you can add credentials for extractor in the following format, where *extractor* is the name of extractor in lowercase:
|
||||||
|
```
|
||||||
|
machine <extractor> login <login> password <password>
|
||||||
|
```
|
||||||
|
For example:
|
||||||
|
```
|
||||||
|
machine youtube login myaccount@gmail.com password my_youtube_password
|
||||||
|
machine twitch login my_twitch_account_name password my_twitch_password
|
||||||
|
```
|
||||||
|
To activate authentication with `.netrc` file you should pass `--netrc` to youtube-dl or to place it in [configuration file](#configuration).
|
||||||
|
|
||||||
|
On Windows you may also need to setup `%HOME%` environment variable manually.
|
||||||
|
|
||||||
# OUTPUT TEMPLATE
|
# OUTPUT TEMPLATE
|
||||||
|
|
||||||
The `-o` option allows users to indicate a template for the output file names. The basic usage is not to set any template arguments when downloading a single file, like in `youtube-dl -o funny_video.flv "http://some/video"`. However, it may contain special sequences that will be replaced when downloading each video. The special sequences have the format `%(NAME)s`. To clarify, that is a percent symbol followed by a name in parenthesis, followed by a lowercase S. Allowed names are:
|
The `-o` option allows users to indicate a template for the output file names. The basic usage is not to set any template arguments when downloading a single file, like in `youtube-dl -o funny_video.flv "http://some/video"`. However, it may contain special sequences that will be replaced when downloading each video. The special sequences have the format `%(NAME)s`. To clarify, that is a percent symbol followed by a name in parenthesis, followed by a lowercase S. Allowed names are:
|
||||||
@@ -268,6 +284,17 @@ $ youtube-dl --get-filename -o "%(title)s.%(ext)s" BaW_jenozKc --restrict-filena
|
|||||||
youtube-dl_test_video_.mp4 # A simple file name
|
youtube-dl_test_video_.mp4 # A simple file name
|
||||||
```
|
```
|
||||||
|
|
||||||
|
# FORMAT SELECTION
|
||||||
|
|
||||||
|
By default youtube-dl tries to download the best quality, but sometimes you may want to download other format.
|
||||||
|
The simplest case is requesting a specific format, for example `-f 22`. You can get the list of available formats using `--list-formats`, you can also use a file extension (currently it supports aac, m4a, mp3, mp4, ogg, wav, webm) or the special names `best`, `bestvideo`, `bestaudio` and `worst`.
|
||||||
|
|
||||||
|
If you want to download multiple videos and they don't have the same formats available, you can specify the order of preference using slashes, as in `-f 22/17/18`. You can also filter the video results by putting a condition in brackets, as in `-f "best[height=720]"` (or `-f "[filesize>10M]"`). This works for filesize, height, width, tbr, abr, vbr, asr, and fps and the comparisons <, <=, >, >=, =, != and for ext, acodec, vcodec, container, and protocol and the comparisons =, != . Formats for which the value is not known are excluded unless you put a question mark (?) after the operator. You can combine format filters, so `-f "[height <=? 720][tbr>500]"` selects up to 720p videos (or videos where the height is not known) with a bitrate of at least 500 KBit/s. Use commas to download multiple formats, such as `-f 136/137/mp4/bestvideo,140/m4a/bestaudio`. You can merge the video and audio of two formats into a single file using `-f <video-format>+<audio-format>` (requires ffmpeg or avconv), for example `-f bestvideo+bestaudio`.
|
||||||
|
|
||||||
|
Since the end of April 2015 and version 2015.04.26 youtube-dl uses `-f bestvideo+bestaudio/best` as default format selection (see #5447, #5456). If ffmpeg or avconv are installed this results in downloading `bestvideo` and `bestaudio` separately and muxing them together into a single file giving the best overall quality available. Otherwise it falls back to `best` and results in downloading best available quality served as a single file. `best` is also needed for videos that don't come from YouTube because they don't provide the audio and video in two different files. If you want to only download some dash formats (for example if you are not interested in getting videos with a resolution higher than 1080p), you can add `-f bestvideo[height<=?1080]+bestaudio/best` to your configuration file. Note that if you use youtube-dl to stream to `stdout` (and most likely to pipe it to your media player then), i.e. you explicitly specify output template as `-o -`, youtube-dl still uses `-f best` format selection in order to start content delivery immediately to your player and not to wait until `bestvideo` and `bestaudio` are downloaded and muxed.
|
||||||
|
|
||||||
|
If you want to preserve the old format selection behavior (prior to youtube-dl 2015.04.26), i.e. you want to download best available quality media served as a single file, you should explicitly specify your choice with `-f best`. You may want to add it to the [configuration file](#configuration) in order not to type it every time you run youtube-dl.
|
||||||
|
|
||||||
# VIDEO SELECTION
|
# VIDEO SELECTION
|
||||||
|
|
||||||
Videos can be filtered by their upload date using the options `--date`, `--datebefore` or `--dateafter`, they accept dates in two formats:
|
Videos can be filtered by their upload date using the options `--date`, `--datebefore` or `--dateafter`, they accept dates in two formats:
|
||||||
@@ -318,9 +345,9 @@ YouTube changed their playlist format in March 2014 and later on, so you'll need
|
|||||||
|
|
||||||
If you have installed youtube-dl with a package manager, pip, setup.py or a tarball, please use that to update. Note that Ubuntu packages do not seem to get updated anymore. Since we are not affiliated with Ubuntu, there is little we can do. Feel free to [report bugs](https://bugs.launchpad.net/ubuntu/+source/youtube-dl/+filebug) to the [Ubuntu packaging guys](mailto:ubuntu-motu@lists.ubuntu.com?subject=outdated%20version%20of%20youtube-dl) - all they have to do is update the package to a somewhat recent version. See above for a way to update.
|
If you have installed youtube-dl with a package manager, pip, setup.py or a tarball, please use that to update. Note that Ubuntu packages do not seem to get updated anymore. Since we are not affiliated with Ubuntu, there is little we can do. Feel free to [report bugs](https://bugs.launchpad.net/ubuntu/+source/youtube-dl/+filebug) to the [Ubuntu packaging guys](mailto:ubuntu-motu@lists.ubuntu.com?subject=outdated%20version%20of%20youtube-dl) - all they have to do is update the package to a somewhat recent version. See above for a way to update.
|
||||||
|
|
||||||
### Do I always have to pass in `--max-quality FORMAT`, or `-citw`?
|
### Do I always have to pass `-citw`?
|
||||||
|
|
||||||
By default, youtube-dl intends to have the best options (incidentally, if you have a convincing case that these should be different, [please file an issue where you explain that](https://yt-dl.org/bug)). Therefore, it is unnecessary and sometimes harmful to copy long option strings from webpages. In particular, `--max-quality` *limits* the video quality (so if you want the best quality, do NOT pass it in), and the only option out of `-citw` that is regularly useful is `-i`.
|
By default, youtube-dl intends to have the best options (incidentally, if you have a convincing case that these should be different, [please file an issue where you explain that](https://yt-dl.org/bug)). Therefore, it is unnecessary and sometimes harmful to copy long option strings from webpages. In particular, the only option out of `-citw` that is regularly useful is `-i`.
|
||||||
|
|
||||||
### Can you please put the -b option back?
|
### Can you please put the -b option back?
|
||||||
|
|
||||||
@@ -352,13 +379,29 @@ YouTube has switched to a new video info format in July 2011 which is not suppor
|
|||||||
|
|
||||||
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` ###
|
||||||
|
|
||||||
|
That's actually the output from your shell. Since ampersand is one of the special shell characters it's interpreted by 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).
|
||||||
|
|
||||||
|
For example if your URL is https://www.youtube.com/watch?t=4&v=BaW_jenozKc you should end up with following command:
|
||||||
|
|
||||||
|
```youtube-dl 'https://www.youtube.com/watch?t=4&v=BaW_jenozKc'```
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
```youtube-dl https://www.youtube.com/watch?t=4\&v=BaW_jenozKc```
|
||||||
|
|
||||||
|
For Windows you have to use the double quotes:
|
||||||
|
|
||||||
|
```youtube-dl "https://www.youtube.com/watch?t=4&v=BaW_jenozKc"```
|
||||||
|
|
||||||
### ExtractorError: Could not find JS function u'OF'
|
### ExtractorError: Could not find JS function u'OF'
|
||||||
|
|
||||||
In February 2015, the new YouTube player contained a character sequence in a string that was misinterpreted by old versions of youtube-dl. See [above](#how-do-i-update-youtube-dl) for how to update youtube-dl.
|
In February 2015, the new YouTube player contained a character sequence in a string that was misinterpreted by old versions of youtube-dl. See [above](#how-do-i-update-youtube-dl) for how to update youtube-dl.
|
||||||
|
|
||||||
### HTTP Error 429: Too Many Requests or 402: Payment Required
|
### HTTP Error 429: Too Many Requests or 402: Payment Required
|
||||||
|
|
||||||
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 `--network-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 ###
|
||||||
|
|
||||||
@@ -404,6 +447,18 @@ A note on the service that they don't host the infringing content, but just link
|
|||||||
|
|
||||||
Support requests for services that **do** purchase the rights to distribute their content are perfectly fine though. If in doubt, you can simply include a source that mentions the legitimate purchase of content.
|
Support requests for services that **do** purchase the rights to distribute their content are perfectly fine though. If in doubt, you can simply include a source that mentions the legitimate purchase of content.
|
||||||
|
|
||||||
|
### How can I speed up work on my issue?
|
||||||
|
|
||||||
|
(Also known as: Help, my important issue not being solved!) The youtube-dl core developer team is quite small. While we do our best to solve as many issues as possible, sometimes that can take quite a while. To speed up your issue, here's what you can do:
|
||||||
|
|
||||||
|
First of all, please do report the issue [at our issue tracker](https://yt-dl.org/bugs). That allows us to coordinate all efforts by users and developers, and serves as a unified point. Unfortunately, the youtube-dl project has grown too large to use personal email as an effective communication channel.
|
||||||
|
|
||||||
|
Please read the [bug reporting instructions](#bugs) below. A lot of bugs lack all the necessary information. If you can, offer proxy, VPN, or shell access to the youtube-dl developers. If you are able to, test the issue from multiple computers in multiple countries to exclude local censorship or misconfiguration issues.
|
||||||
|
|
||||||
|
If nobody is interested in solving your issue, you are welcome to take matters into your own hands and submit a pull request (or coerce/pay somebody else to do so).
|
||||||
|
|
||||||
|
Feel free to bump the issue from time to time by writing a small comment ("Issue is still present in youtube-dl version ...from France, but fixed from Belgium"), but please not more than once a month. Please do not declare your issue as `important` or `urgent`.
|
||||||
|
|
||||||
### How can I detect whether a given URL is supported by youtube-dl?
|
### How can I detect whether a given URL is supported by youtube-dl?
|
||||||
|
|
||||||
For one, have a look at the [list of supported sites](docs/supportedsites.md). Note that it can sometimes happen that the site changes its URL scheme (say, from http://example.com/video/1234567 to http://example.com/v/1234567 ) and youtube-dl reports an URL of a service in that list as unsupported. In that case, simply report a bug.
|
For one, have a look at the [list of supported sites](docs/supportedsites.md). Note that it can sometimes happen that the site changes its URL scheme (say, from http://example.com/video/1234567 to http://example.com/v/1234567 ) and youtube-dl reports an URL of a service in that list as unsupported. In that case, simply report a bug.
|
||||||
@@ -503,6 +558,7 @@ youtube-dl makes the best effort to be a good command-line program, and thus sho
|
|||||||
From a Python program, you can embed youtube-dl in a more powerful fashion, like this:
|
From a Python program, you can embed youtube-dl in a more powerful fashion, like this:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
from __future__ import unicode_literals
|
||||||
import youtube_dl
|
import youtube_dl
|
||||||
|
|
||||||
ydl_opts = {}
|
ydl_opts = {}
|
||||||
@@ -515,6 +571,7 @@ Most likely, you'll want to use various options. For a list of what can be done,
|
|||||||
Here's a more complete example of a program that outputs only errors (and a short message after the download is finished), and downloads/converts the video to an mp3 file:
|
Here's a more complete example of a program that outputs only errors (and a short message after the download is finished), and downloads/converts the video to an mp3 file:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
from __future__ import unicode_literals
|
||||||
import youtube_dl
|
import youtube_dl
|
||||||
|
|
||||||
|
|
||||||
@@ -572,7 +629,9 @@ If your report is shorter than two lines, it is almost certainly missing some of
|
|||||||
|
|
||||||
For bug reports, this means that your report should contain the *complete* output of youtube-dl when called with the -v flag. The error message you get for (most) bugs even says so, but you would not believe how many of our bug reports do not contain this information.
|
For bug reports, this means that your report should contain the *complete* output of youtube-dl when called with the -v flag. The error message you get for (most) bugs even says so, but you would not believe how many of our bug reports do not contain this information.
|
||||||
|
|
||||||
Site support requests **must contain an example URL**. An example URL is a URL you might want to download, like http://www.youtube.com/watch?v=BaW_jenozKc . There should be an obvious video present. Except under very special circumstances, the main page of a video service (e.g. http://www.youtube.com/ ) is *not* an example URL.
|
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.
|
||||||
|
|
||||||
### Are you using the latest version?
|
### Are you using the latest version?
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ for test in get_testcases():
|
|||||||
if METHOD == 'EURISTIC':
|
if METHOD == 'EURISTIC':
|
||||||
try:
|
try:
|
||||||
webpage = compat_urllib_request.urlopen(test['url'], timeout=10).read()
|
webpage = compat_urllib_request.urlopen(test['url'], timeout=10).read()
|
||||||
except:
|
except Exception:
|
||||||
print('\nFail: {0}'.format(test['name']))
|
print('\nFail: {0}'.format(test['name']))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|||||||
42
devscripts/generate_aes_testdata.py
Normal file
42
devscripts/generate_aes_testdata.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import codecs
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
from youtube_dl.utils import intlist_to_bytes
|
||||||
|
from youtube_dl.aes import aes_encrypt, key_expansion
|
||||||
|
|
||||||
|
secret_msg = b'Secret message goes here'
|
||||||
|
|
||||||
|
|
||||||
|
def hex_str(int_list):
|
||||||
|
return codecs.encode(intlist_to_bytes(int_list), 'hex')
|
||||||
|
|
||||||
|
|
||||||
|
def openssl_encode(algo, key, iv):
|
||||||
|
cmd = ['openssl', 'enc', '-e', '-' + algo, '-K', hex_str(key), '-iv', hex_str(iv)]
|
||||||
|
prog = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||||
|
out, _ = prog.communicate(secret_msg)
|
||||||
|
return out
|
||||||
|
|
||||||
|
iv = key = [0x20, 0x15] + 14 * [0]
|
||||||
|
|
||||||
|
r = openssl_encode('aes-128-cbc', key, iv)
|
||||||
|
print('aes_cbc_decrypt')
|
||||||
|
print(repr(r))
|
||||||
|
|
||||||
|
password = key
|
||||||
|
new_key = aes_encrypt(password, key_expansion(password))
|
||||||
|
r = openssl_encode('aes-128-ctr', new_key, iv)
|
||||||
|
print('aes_decrypt_text 16')
|
||||||
|
print(repr(r))
|
||||||
|
|
||||||
|
password = key + 16 * [0]
|
||||||
|
new_key = aes_encrypt(password, key_expansion(password)) * (32 // 16)
|
||||||
|
r = openssl_encode('aes-256-ctr', new_key, iv)
|
||||||
|
print('aes_decrypt_text 32')
|
||||||
|
print(repr(r))
|
||||||
@@ -2,18 +2,22 @@
|
|||||||
- **1tv**: Первый канал
|
- **1tv**: Первый канал
|
||||||
- **1up.com**
|
- **1up.com**
|
||||||
- **220.ro**
|
- **220.ro**
|
||||||
|
- **22tracks:genre**
|
||||||
|
- **22tracks:track**
|
||||||
- **24video**
|
- **24video**
|
||||||
- **3sat**
|
- **3sat**
|
||||||
- **4tube**
|
- **4tube**
|
||||||
- **56.com**
|
- **56.com**
|
||||||
- **5min**
|
- **5min**
|
||||||
- **8tracks**
|
- **8tracks**
|
||||||
|
- **91porn**
|
||||||
- **9gag**
|
- **9gag**
|
||||||
- **abc.net.au**
|
- **abc.net.au**
|
||||||
- **Abc7News**
|
- **Abc7News**
|
||||||
- **AcademicEarth:Course**
|
- **AcademicEarth:Course**
|
||||||
- **AddAnime**
|
- **AddAnime**
|
||||||
- **AdobeTV**
|
- **AdobeTV**
|
||||||
|
- **AdobeTVVideo**
|
||||||
- **AdultSwim**
|
- **AdultSwim**
|
||||||
- **Aftenposten**
|
- **Aftenposten**
|
||||||
- **Aftonbladet**
|
- **Aftonbladet**
|
||||||
@@ -24,8 +28,7 @@
|
|||||||
- **anitube.se**
|
- **anitube.se**
|
||||||
- **AnySex**
|
- **AnySex**
|
||||||
- **Aparat**
|
- **Aparat**
|
||||||
- **AppleDailyAnimationNews**
|
- **AppleDaily**: 臺灣蘋果日報
|
||||||
- **AppleDailyRealtimeNews**
|
|
||||||
- **AppleTrailers**
|
- **AppleTrailers**
|
||||||
- **archive.org**: archive.org videos
|
- **archive.org**: archive.org videos
|
||||||
- **ARD**
|
- **ARD**
|
||||||
@@ -42,11 +45,13 @@
|
|||||||
- **audiomack**
|
- **audiomack**
|
||||||
- **audiomack:album**
|
- **audiomack:album**
|
||||||
- **Azubu**
|
- **Azubu**
|
||||||
|
- **BaiduVideo**: 百度视频
|
||||||
- **bambuser**
|
- **bambuser**
|
||||||
- **bambuser:channel**
|
- **bambuser:channel**
|
||||||
- **Bandcamp**
|
- **Bandcamp**
|
||||||
- **Bandcamp:album**
|
- **Bandcamp:album**
|
||||||
- **bbc.co.uk**: BBC iPlayer
|
- **bbc.co.uk**: BBC iPlayer
|
||||||
|
- **BeatportPro**
|
||||||
- **Beeg**
|
- **Beeg**
|
||||||
- **BehindKink**
|
- **BehindKink**
|
||||||
- **Bet**
|
- **Bet**
|
||||||
@@ -60,6 +65,8 @@
|
|||||||
- **BR**: Bayerischer Rundfunk Mediathek
|
- **BR**: Bayerischer Rundfunk Mediathek
|
||||||
- **Break**
|
- **Break**
|
||||||
- **Brightcove**
|
- **Brightcove**
|
||||||
|
- **bt:article**: Bergens Tidende Articles
|
||||||
|
- **bt:vestlendingen**: Bergens Tidende - Vestlendingen
|
||||||
- **BuzzFeed**
|
- **BuzzFeed**
|
||||||
- **BYUtv**
|
- **BYUtv**
|
||||||
- **Camdemy**
|
- **Camdemy**
|
||||||
@@ -95,28 +102,36 @@
|
|||||||
- **CondeNast**: Condé Nast media group: Condé Nast, GQ, Glamour, Vanity Fair, Vogue, W Magazine, WIRED
|
- **CondeNast**: Condé Nast media group: Condé Nast, GQ, Glamour, Vanity Fair, Vogue, W Magazine, WIRED
|
||||||
- **Cracked**
|
- **Cracked**
|
||||||
- **Criterion**
|
- **Criterion**
|
||||||
|
- **CrooksAndLiars**
|
||||||
- **Crunchyroll**
|
- **Crunchyroll**
|
||||||
- **crunchyroll:playlist**
|
- **crunchyroll:playlist**
|
||||||
- **CSpan**: C-SPAN
|
- **CSpan**: C-SPAN
|
||||||
- **CtsNews**
|
- **CtsNews**: 華視新聞
|
||||||
- **culturebox.francetvinfo.fr**
|
- **culturebox.francetvinfo.fr**
|
||||||
- **dailymotion**
|
- **dailymotion**
|
||||||
- **dailymotion:playlist**
|
- **dailymotion:playlist**
|
||||||
- **dailymotion:user**
|
- **dailymotion:user**
|
||||||
|
- **DailymotionCloud**
|
||||||
- **daum.net**
|
- **daum.net**
|
||||||
- **DBTV**
|
- **DBTV**
|
||||||
- **DctpTv**
|
- **DctpTv**
|
||||||
- **DeezerPlaylist**
|
- **DeezerPlaylist**
|
||||||
- **defense.gouv.fr**
|
- **defense.gouv.fr**
|
||||||
|
- **DHM**: Filmarchiv - Deutsches Historisches Museum
|
||||||
- **Discovery**
|
- **Discovery**
|
||||||
- **divxstage**: DivxStage
|
- **divxstage**: DivxStage
|
||||||
- **Dotsub**
|
- **Dotsub**
|
||||||
|
- **DouyuTV**: 斗鱼
|
||||||
|
- **dramafever**
|
||||||
|
- **dramafever:series**
|
||||||
- **DRBonanza**
|
- **DRBonanza**
|
||||||
- **Dropbox**
|
- **Dropbox**
|
||||||
- **DrTuber**
|
- **DrTuber**
|
||||||
- **DRTV**
|
- **DRTV**
|
||||||
- **Dump**
|
- **Dump**
|
||||||
|
- **Dumpert**
|
||||||
- **dvtv**: http://video.aktualne.cz/
|
- **dvtv**: http://video.aktualne.cz/
|
||||||
|
- **EaglePlatform**
|
||||||
- **EbaumsWorld**
|
- **EbaumsWorld**
|
||||||
- **EchoMsk**
|
- **EchoMsk**
|
||||||
- **eHow**
|
- **eHow**
|
||||||
@@ -131,6 +146,7 @@
|
|||||||
- **Eporner**
|
- **Eporner**
|
||||||
- **EroProfile**
|
- **EroProfile**
|
||||||
- **Escapist**
|
- **Escapist**
|
||||||
|
- **ESPN** (Currently broken)
|
||||||
- **EveryonesMixtape**
|
- **EveryonesMixtape**
|
||||||
- **exfm**: ex.fm
|
- **exfm**: ex.fm
|
||||||
- **ExpoTV**
|
- **ExpoTV**
|
||||||
@@ -140,12 +156,14 @@
|
|||||||
- **fc2**
|
- **fc2**
|
||||||
- **fernsehkritik.tv**
|
- **fernsehkritik.tv**
|
||||||
- **fernsehkritik.tv:postecke**
|
- **fernsehkritik.tv:postecke**
|
||||||
- **Firedrive**
|
|
||||||
- **Firstpost**
|
- **Firstpost**
|
||||||
|
- **FiveTV**
|
||||||
- **Flickr**
|
- **Flickr**
|
||||||
- **Folketinget**: Folketinget (ft.dk; Danish parliament)
|
- **Folketinget**: Folketinget (ft.dk; Danish parliament)
|
||||||
|
- **FootyRoom**
|
||||||
- **Foxgay**
|
- **Foxgay**
|
||||||
- **FoxNews**
|
- **FoxNews**
|
||||||
|
- **FoxSports**
|
||||||
- **france2.fr:generation-quoi**
|
- **france2.fr:generation-quoi**
|
||||||
- **FranceCulture**
|
- **FranceCulture**
|
||||||
- **FranceInter**
|
- **FranceInter**
|
||||||
@@ -158,11 +176,14 @@
|
|||||||
- **Gamekings**
|
- **Gamekings**
|
||||||
- **GameOne**
|
- **GameOne**
|
||||||
- **gameone:playlist**
|
- **gameone:playlist**
|
||||||
|
- **Gamersyde**
|
||||||
- **GameSpot**
|
- **GameSpot**
|
||||||
- **GameStar**
|
- **GameStar**
|
||||||
- **Gametrailers**
|
- **Gametrailers**
|
||||||
|
- **Gazeta**
|
||||||
- **GDCVault**
|
- **GDCVault**
|
||||||
- **generic**: Generic downloader that works on some sites
|
- **generic**: Generic downloader that works on some sites
|
||||||
|
- **Gfycat**
|
||||||
- **GiantBomb**
|
- **GiantBomb**
|
||||||
- **Giga**
|
- **Giga**
|
||||||
- **Glide**: Glide mobile video messages (glide.me)
|
- **Glide**: Glide mobile video messages (glide.me)
|
||||||
@@ -170,9 +191,8 @@
|
|||||||
- **GodTube**
|
- **GodTube**
|
||||||
- **GoldenMoustache**
|
- **GoldenMoustache**
|
||||||
- **Golem**
|
- **Golem**
|
||||||
- **GorillaVid**: GorillaVid.in, daclips.in, movpod.in and fastvideo.in
|
- **GorillaVid**: GorillaVid.in, daclips.in, movpod.in, fastvideo.in and realvid.net
|
||||||
- **Goshgay**
|
- **Goshgay**
|
||||||
- **Grooveshark**
|
|
||||||
- **Groupon**
|
- **Groupon**
|
||||||
- **Hark**
|
- **Hark**
|
||||||
- **HearThisAt**
|
- **HearThisAt**
|
||||||
@@ -202,6 +222,7 @@
|
|||||||
- **instagram:user**: Instagram user profile
|
- **instagram:user**: Instagram user profile
|
||||||
- **InternetVideoArchive**
|
- **InternetVideoArchive**
|
||||||
- **IPrima**
|
- **IPrima**
|
||||||
|
- **iqiyi**: 爱奇艺
|
||||||
- **ivi**: ivi.ru
|
- **ivi**: ivi.ru
|
||||||
- **ivi:compilation**: ivi.ru compilations
|
- **ivi:compilation**: ivi.ru compilations
|
||||||
- **Izlesene**
|
- **Izlesene**
|
||||||
@@ -211,8 +232,10 @@
|
|||||||
- **jpopsuki.tv**
|
- **jpopsuki.tv**
|
||||||
- **Jukebox**
|
- **Jukebox**
|
||||||
- **Kaltura**
|
- **Kaltura**
|
||||||
|
- **KanalPlay**: Kanal 5/9/11 Play
|
||||||
- **Kankan**
|
- **Kankan**
|
||||||
- **Karaoketv**
|
- **Karaoketv**
|
||||||
|
- **KarriereVideos**
|
||||||
- **keek**
|
- **keek**
|
||||||
- **KeezMovies**
|
- **KeezMovies**
|
||||||
- **KhanAcademy**
|
- **KhanAcademy**
|
||||||
@@ -220,11 +243,19 @@
|
|||||||
- **kontrtube**: KontrTube.ru - Труба зовёт
|
- **kontrtube**: KontrTube.ru - Труба зовёт
|
||||||
- **KrasView**: Красвью
|
- **KrasView**: Красвью
|
||||||
- **Ku6**
|
- **Ku6**
|
||||||
|
- **kuwo:album**: 酷我音乐 - 专辑
|
||||||
|
- **kuwo:category**: 酷我音乐 - 分类
|
||||||
|
- **kuwo:chart**: 酷我音乐 - 排行榜
|
||||||
|
- **kuwo:mv**: 酷我音乐 - MV
|
||||||
|
- **kuwo:singer**: 酷我音乐 - 歌手
|
||||||
|
- **kuwo:song**: 酷我音乐
|
||||||
- **la7.tv**
|
- **la7.tv**
|
||||||
- **Laola1Tv**
|
- **Laola1Tv**
|
||||||
- **Letv**
|
- **Letv**: 乐视网
|
||||||
- **LetvPlaylist**
|
- **LetvPlaylist**
|
||||||
- **LetvTv**
|
- **LetvTv**
|
||||||
|
- **Libsyn**
|
||||||
|
- **life:embed**
|
||||||
- **lifenews**: LIFE | NEWS
|
- **lifenews**: LIFE | NEWS
|
||||||
- **LiveLeak**
|
- **LiveLeak**
|
||||||
- **livestream**
|
- **livestream**
|
||||||
@@ -239,11 +270,13 @@
|
|||||||
- **Malemotion**
|
- **Malemotion**
|
||||||
- **MDR**
|
- **MDR**
|
||||||
- **media.ccc.de**
|
- **media.ccc.de**
|
||||||
|
- **MegaVideoz**
|
||||||
- **metacafe**
|
- **metacafe**
|
||||||
- **Metacritic**
|
- **Metacritic**
|
||||||
- **Mgoon**
|
- **Mgoon**
|
||||||
- **Minhateca**
|
- **Minhateca**
|
||||||
- **MinistryGrid**
|
- **MinistryGrid**
|
||||||
|
- **miomio.tv**
|
||||||
- **mitele.es**
|
- **mitele.es**
|
||||||
- **mixcloud**
|
- **mixcloud**
|
||||||
- **MLB**
|
- **MLB**
|
||||||
@@ -256,6 +289,7 @@
|
|||||||
- **Motherless**
|
- **Motherless**
|
||||||
- **Motorsport**: motorsport.com
|
- **Motorsport**: motorsport.com
|
||||||
- **MovieClips**
|
- **MovieClips**
|
||||||
|
- **MovieFap**
|
||||||
- **Moviezine**
|
- **Moviezine**
|
||||||
- **movshare**: MovShare
|
- **movshare**: MovShare
|
||||||
- **MPORA**
|
- **MPORA**
|
||||||
@@ -269,23 +303,34 @@
|
|||||||
- **MySpace**
|
- **MySpace**
|
||||||
- **MySpace:album**
|
- **MySpace:album**
|
||||||
- **MySpass**
|
- **MySpass**
|
||||||
|
- **Myvi**
|
||||||
- **myvideo**
|
- **myvideo**
|
||||||
- **MyVidster**
|
- **MyVidster**
|
||||||
|
- **N-JOY**
|
||||||
- **n-tv.de**
|
- **n-tv.de**
|
||||||
- **NationalGeographic**
|
- **NationalGeographic**
|
||||||
- **Naver**
|
- **Naver**
|
||||||
- **NBA**
|
- **NBA**
|
||||||
- **NBC**
|
- **NBC**
|
||||||
- **NBCNews**
|
- **NBCNews**
|
||||||
|
- **NBCSports**
|
||||||
|
- **NBCSportsVPlayer**
|
||||||
- **ndr**: NDR.de - Mediathek
|
- **ndr**: NDR.de - Mediathek
|
||||||
- **NDTV**
|
- **NDTV**
|
||||||
- **NerdCubedFeed**
|
- **NerdCubedFeed**
|
||||||
- **Nerdist**
|
- **Nerdist**
|
||||||
|
- **netease:album**: 网易云音乐 - 专辑
|
||||||
|
- **netease:djradio**: 网易云音乐 - 电台
|
||||||
|
- **netease:mv**: 网易云音乐 - MV
|
||||||
|
- **netease:playlist**: 网易云音乐 - 歌单
|
||||||
|
- **netease:program**: 网易云音乐 - 电台节目
|
||||||
|
- **netease:singer**: 网易云音乐 - 歌手
|
||||||
|
- **netease:song**: 网易云音乐
|
||||||
- **Netzkino**
|
- **Netzkino**
|
||||||
- **Newgrounds**
|
- **Newgrounds**
|
||||||
- **Newstube**
|
- **Newstube**
|
||||||
- **NextMedia**
|
- **NextMedia**: 蘋果日報
|
||||||
- **NextMediaActionNews**
|
- **NextMediaActionNews**: 蘋果日報 - 動新聞
|
||||||
- **nfb**: National Film Board of Canada
|
- **nfb**: National Film Board of Canada
|
||||||
- **nfl.com**
|
- **nfl.com**
|
||||||
- **nhl.com**
|
- **nhl.com**
|
||||||
@@ -296,36 +341,48 @@
|
|||||||
- **Noco**
|
- **Noco**
|
||||||
- **Normalboots**
|
- **Normalboots**
|
||||||
- **NosVideo**
|
- **NosVideo**
|
||||||
|
- **Nova**: TN.cz, Prásk.tv, Nova.cz, Novaplus.cz, FANDA.tv, Krásná.cz and Doma.cz
|
||||||
- **novamov**: NovaMov
|
- **novamov**: NovaMov
|
||||||
- **Nowness**
|
- **Nowness**
|
||||||
|
- **NowTV**
|
||||||
- **nowvideo**: NowVideo
|
- **nowvideo**: NowVideo
|
||||||
- **npo.nl**
|
- **npo**: npo.nl and ntr.nl
|
||||||
|
- **npo**: npo.nl and ntr.nl
|
||||||
- **npo.nl:live**
|
- **npo.nl:live**
|
||||||
- **npo.nl:radio**
|
- **npo.nl:radio**
|
||||||
- **npo.nl:radio:fragment**
|
- **npo.nl:radio:fragment**
|
||||||
- **NRK**
|
- **NRK**
|
||||||
- **NRKTV**
|
- **NRKPlaylist**
|
||||||
|
- **NRKTV**: NRK TV and NRK Radio
|
||||||
- **ntv.ru**
|
- **ntv.ru**
|
||||||
- **Nuvid**
|
- **Nuvid**
|
||||||
- **NYTimes**
|
- **NYTimes**
|
||||||
|
- **NYTimesArticle**
|
||||||
- **ocw.mit.edu**
|
- **ocw.mit.edu**
|
||||||
- **Odnoklassniki**
|
- **Odnoklassniki**
|
||||||
- **OktoberfestTV**
|
- **OktoberfestTV**
|
||||||
- **on.aol.com**
|
- **on.aol.com**
|
||||||
|
- **OnionStudios**
|
||||||
- **Ooyala**
|
- **Ooyala**
|
||||||
|
- **OoyalaExternal**
|
||||||
- **OpenFilm**
|
- **OpenFilm**
|
||||||
- **orf:fm4**: radio FM4
|
- **orf:fm4**: radio FM4
|
||||||
|
- **orf:iptv**: iptv.ORF.at
|
||||||
- **orf:oe1**: Radio Österreich 1
|
- **orf:oe1**: Radio Österreich 1
|
||||||
- **orf:tvthek**: ORF TVthek
|
- **orf:tvthek**: ORF TVthek
|
||||||
- **parliamentlive.tv**: UK parliament videos
|
- **parliamentlive.tv**: UK parliament videos
|
||||||
- **Patreon**
|
- **Patreon**
|
||||||
- **PBS**
|
- **PBS**
|
||||||
|
- **PhilharmonieDeParis**: Philharmonie de Paris
|
||||||
- **Phoenix**
|
- **Phoenix**
|
||||||
- **Photobucket**
|
- **Photobucket**
|
||||||
|
- **Pinkbike**
|
||||||
|
- **Pladform**
|
||||||
- **PlanetaPlay**
|
- **PlanetaPlay**
|
||||||
- **play.fm**
|
- **play.fm**
|
||||||
- **played.to**
|
- **played.to**
|
||||||
- **Playvid**
|
- **Playvid**
|
||||||
|
- **Playwire**
|
||||||
- **plus.google**: Google Plus
|
- **plus.google**: Google Plus
|
||||||
- **pluzz.francetv.fr**
|
- **pluzz.francetv.fr**
|
||||||
- **podomatic**
|
- **podomatic**
|
||||||
@@ -333,18 +390,27 @@
|
|||||||
- **PornHub**
|
- **PornHub**
|
||||||
- **PornHubPlaylist**
|
- **PornHubPlaylist**
|
||||||
- **Pornotube**
|
- **Pornotube**
|
||||||
|
- **PornoVoisines**
|
||||||
- **PornoXO**
|
- **PornoXO**
|
||||||
|
- **PrimeShareTV**
|
||||||
- **PromptFile**
|
- **PromptFile**
|
||||||
- **prosiebensat1**: ProSiebenSat.1 Digital
|
- **prosiebensat1**: ProSiebenSat.1 Digital
|
||||||
- **Puls4**
|
- **Puls4**
|
||||||
- **Pyvideo**
|
- **Pyvideo**
|
||||||
|
- **qqmusic**: QQ音乐
|
||||||
|
- **qqmusic:album**: QQ音乐 - 专辑
|
||||||
|
- **qqmusic:playlist**: QQ音乐 - 歌单
|
||||||
|
- **qqmusic:singer**: QQ音乐 - 歌手
|
||||||
|
- **qqmusic:toplist**: QQ音乐 - 排行榜
|
||||||
- **QuickVid**
|
- **QuickVid**
|
||||||
- **R7**
|
- **R7**
|
||||||
- **radio.de**
|
- **radio.de**
|
||||||
- **radiobremen**
|
- **radiobremen**
|
||||||
- **radiofrance**
|
- **radiofrance**
|
||||||
|
- **RadioJavan**
|
||||||
- **Rai**
|
- **Rai**
|
||||||
- **RBMARadio**
|
- **RBMARadio**
|
||||||
|
- **RDS**: RDS.ca
|
||||||
- **RedTube**
|
- **RedTube**
|
||||||
- **Restudy**
|
- **Restudy**
|
||||||
- **ReverbNation**
|
- **ReverbNation**
|
||||||
@@ -355,10 +421,10 @@
|
|||||||
- **Rte**
|
- **Rte**
|
||||||
- **rtl.nl**: rtl.nl and rtlxl.nl
|
- **rtl.nl**: rtl.nl and rtlxl.nl
|
||||||
- **RTL2**
|
- **RTL2**
|
||||||
- **RTLnow**
|
|
||||||
- **RTP**
|
- **RTP**
|
||||||
- **RTS**: RTS.ch
|
- **RTS**: RTS.ch
|
||||||
- **rtve.es:alacarta**: RTVE a la carta
|
- **rtve.es:alacarta**: RTVE a la carta
|
||||||
|
- **rtve.es:infantil**: RTVE infantil
|
||||||
- **rtve.es:live**: RTVE.es live streams
|
- **rtve.es:live**: RTVE.es live streams
|
||||||
- **RUHD**
|
- **RUHD**
|
||||||
- **rutube**: Rutube videos
|
- **rutube**: Rutube videos
|
||||||
@@ -367,6 +433,9 @@
|
|||||||
- **rutube:movie**: Rutube movies
|
- **rutube:movie**: Rutube movies
|
||||||
- **rutube:person**: Rutube person videos
|
- **rutube:person**: Rutube person videos
|
||||||
- **RUTV**: RUTV.RU
|
- **RUTV**: RUTV.RU
|
||||||
|
- **Ruutu**
|
||||||
|
- **safari**: safaribooksonline.com online video
|
||||||
|
- **safari:course**: safaribooksonline.com online courses
|
||||||
- **Sandia**: Sandia National Laboratories
|
- **Sandia**: Sandia National Laboratories
|
||||||
- **Sapo**: SAPO Vídeos
|
- **Sapo**: SAPO Vídeos
|
||||||
- **savefrom.net**
|
- **savefrom.net**
|
||||||
@@ -376,6 +445,7 @@
|
|||||||
- **Screencast**
|
- **Screencast**
|
||||||
- **ScreencastOMatic**
|
- **ScreencastOMatic**
|
||||||
- **ScreenwaveMedia**
|
- **ScreenwaveMedia**
|
||||||
|
- **SenateISVP**
|
||||||
- **ServingSys**
|
- **ServingSys**
|
||||||
- **Sexu**
|
- **Sexu**
|
||||||
- **SexyKarma**: Sexy Karma and Watch Indian Porn
|
- **SexyKarma**: Sexy Karma and Watch Indian Porn
|
||||||
@@ -388,9 +458,12 @@
|
|||||||
- **smotri:broadcast**: Smotri.com broadcasts
|
- **smotri:broadcast**: Smotri.com broadcasts
|
||||||
- **smotri:community**: Smotri.com community videos
|
- **smotri:community**: Smotri.com community videos
|
||||||
- **smotri:user**: Smotri.com user videos
|
- **smotri:user**: Smotri.com user videos
|
||||||
|
- **SnagFilms**
|
||||||
|
- **SnagFilmsEmbed**
|
||||||
- **Snotr**
|
- **Snotr**
|
||||||
- **Sockshare**
|
|
||||||
- **Sohu**
|
- **Sohu**
|
||||||
|
- **soompi**
|
||||||
|
- **soompi:show**
|
||||||
- **soundcloud**
|
- **soundcloud**
|
||||||
- **soundcloud:playlist**
|
- **soundcloud:playlist**
|
||||||
- **soundcloud:set**
|
- **soundcloud:set**
|
||||||
@@ -398,8 +471,12 @@
|
|||||||
- **soundgasm**
|
- **soundgasm**
|
||||||
- **soundgasm:profile**
|
- **soundgasm:profile**
|
||||||
- **southpark.cc.com**
|
- **southpark.cc.com**
|
||||||
|
- **southpark.cc.com:español**
|
||||||
- **southpark.de**
|
- **southpark.de**
|
||||||
|
- **southpark.nl**
|
||||||
|
- **southparkstudios.dk**
|
||||||
- **Space**
|
- **Space**
|
||||||
|
- **SpankBang**
|
||||||
- **Spankwire**
|
- **Spankwire**
|
||||||
- **Spiegel**
|
- **Spiegel**
|
||||||
- **Spiegel:Article**: Articles on spiegel.de
|
- **Spiegel:Article**: Articles on spiegel.de
|
||||||
@@ -407,14 +484,18 @@
|
|||||||
- **Spike**
|
- **Spike**
|
||||||
- **Sport5**
|
- **Sport5**
|
||||||
- **SportBox**
|
- **SportBox**
|
||||||
|
- **SportBoxEmbed**
|
||||||
- **SportDeutschland**
|
- **SportDeutschland**
|
||||||
|
- **Srf**
|
||||||
- **SRMediathek**: Saarländischer Rundfunk
|
- **SRMediathek**: Saarländischer Rundfunk
|
||||||
|
- **SSA**
|
||||||
- **stanfordoc**: Stanford Open ClassRoom
|
- **stanfordoc**: Stanford Open ClassRoom
|
||||||
- **Steam**
|
- **Steam**
|
||||||
- **streamcloud.eu**
|
- **streamcloud.eu**
|
||||||
- **StreamCZ**
|
- **StreamCZ**
|
||||||
- **StreetVoice**
|
- **StreetVoice**
|
||||||
- **SunPorno**
|
- **SunPorno**
|
||||||
|
- **SVT**
|
||||||
- **SVTPlay**: SVT Play and Öppet arkiv
|
- **SVTPlay**: SVT Play and Öppet arkiv
|
||||||
- **SWRMediathek**
|
- **SWRMediathek**
|
||||||
- **Syfy**
|
- **Syfy**
|
||||||
@@ -429,8 +510,7 @@
|
|||||||
- **TeamFour**
|
- **TeamFour**
|
||||||
- **TechTalks**
|
- **TechTalks**
|
||||||
- **techtv.mit.edu**
|
- **techtv.mit.edu**
|
||||||
- **TED**
|
- **ted**
|
||||||
- **tegenlicht.vpro.nl**
|
|
||||||
- **TeleBruxelles**
|
- **TeleBruxelles**
|
||||||
- **telecinco.es**
|
- **telecinco.es**
|
||||||
- **TeleMB**
|
- **TeleMB**
|
||||||
@@ -441,6 +521,7 @@
|
|||||||
- **TheOnion**
|
- **TheOnion**
|
||||||
- **ThePlatform**
|
- **ThePlatform**
|
||||||
- **TheSixtyOne**
|
- **TheSixtyOne**
|
||||||
|
- **ThisAmericanLife**
|
||||||
- **ThisAV**
|
- **ThisAV**
|
||||||
- **THVideo**
|
- **THVideo**
|
||||||
- **THVideoPlaylist**
|
- **THVideoPlaylist**
|
||||||
@@ -448,6 +529,7 @@
|
|||||||
- **tlc.com**
|
- **tlc.com**
|
||||||
- **tlc.de**
|
- **tlc.de**
|
||||||
- **TMZ**
|
- **TMZ**
|
||||||
|
- **TMZArticle**
|
||||||
- **TNAFlix**
|
- **TNAFlix**
|
||||||
- **tou.tv**
|
- **tou.tv**
|
||||||
- **Toypics**: Toypics user profile
|
- **Toypics**: Toypics user profile
|
||||||
@@ -456,13 +538,18 @@
|
|||||||
- **Trilulilu**
|
- **Trilulilu**
|
||||||
- **TruTube**
|
- **TruTube**
|
||||||
- **Tube8**
|
- **Tube8**
|
||||||
|
- **TubiTv**
|
||||||
- **Tudou**
|
- **Tudou**
|
||||||
- **Tumblr**
|
- **Tumblr**
|
||||||
- **TuneIn**
|
- **TuneIn**
|
||||||
- **Turbo**
|
- **Turbo**
|
||||||
- **Tutv**
|
- **Tutv**
|
||||||
- **tv.dfb.de**
|
- **tv.dfb.de**
|
||||||
|
- **TV2**
|
||||||
|
- **TV2Article**
|
||||||
- **TV4**: tv4.se and tv4play.se
|
- **TV4**: tv4.se and tv4play.se
|
||||||
|
- **TVC**
|
||||||
|
- **TVCArticle**
|
||||||
- **tvigle**: Интернет-телевидение Tvigle.ru
|
- **tvigle**: Интернет-телевидение Tvigle.ru
|
||||||
- **tvp.pl**
|
- **tvp.pl**
|
||||||
- **tvp.pl:Series**
|
- **tvp.pl:Series**
|
||||||
@@ -475,19 +562,24 @@
|
|||||||
- **twitch:stream**
|
- **twitch:stream**
|
||||||
- **twitch:video**
|
- **twitch:video**
|
||||||
- **twitch:vod**
|
- **twitch:vod**
|
||||||
|
- **TwitterCard**
|
||||||
- **Ubu**
|
- **Ubu**
|
||||||
- **udemy**
|
- **udemy**
|
||||||
- **udemy:course**
|
- **udemy:course**
|
||||||
|
- **UDNEmbed**: 聯合影音
|
||||||
|
- **Ultimedia**
|
||||||
- **Unistra**
|
- **Unistra**
|
||||||
- **Urort**: NRK P3 Urørt
|
- **Urort**: NRK P3 Urørt
|
||||||
- **ustream**
|
- **ustream**
|
||||||
- **ustream:channel**
|
- **ustream:channel**
|
||||||
|
- **Varzesh3**
|
||||||
- **Vbox7**
|
- **Vbox7**
|
||||||
- **VeeHD**
|
- **VeeHD**
|
||||||
- **Veoh**
|
- **Veoh**
|
||||||
|
- **Vessel**
|
||||||
- **Vesti**: Вести.Ru
|
- **Vesti**: Вести.Ru
|
||||||
- **Vevo**
|
- **Vevo**
|
||||||
- **VGTV**
|
- **VGTV**: VGTV and BTTV
|
||||||
- **vh1.com**
|
- **vh1.com**
|
||||||
- **Vice**
|
- **Vice**
|
||||||
- **Viddler**
|
- **Viddler**
|
||||||
@@ -505,7 +597,9 @@
|
|||||||
- **Vidzi**
|
- **Vidzi**
|
||||||
- **vier**
|
- **vier**
|
||||||
- **vier:videos**
|
- **vier:videos**
|
||||||
|
- **Viewster**
|
||||||
- **viki**
|
- **viki**
|
||||||
|
- **viki:channel**
|
||||||
- **vimeo**
|
- **vimeo**
|
||||||
- **vimeo:album**
|
- **vimeo:album**
|
||||||
- **vimeo:channel**
|
- **vimeo:channel**
|
||||||
@@ -514,12 +608,13 @@
|
|||||||
- **vimeo:review**: Review pages on vimeo
|
- **vimeo:review**: Review pages on vimeo
|
||||||
- **vimeo:user**
|
- **vimeo:user**
|
||||||
- **vimeo:watchlater**: Vimeo watch later list, "vimeowatchlater" keyword (requires authentication)
|
- **vimeo:watchlater**: Vimeo watch later list, "vimeowatchlater" keyword (requires authentication)
|
||||||
- **Vimple**: Vimple.ru
|
- **Vimple**: Vimple - one-click video hosting
|
||||||
- **Vine**
|
- **Vine**
|
||||||
- **vine:user**
|
- **vine:user**
|
||||||
- **vk.com**
|
- **vk.com**
|
||||||
- **vk.com:user-videos**: vk.com:All of a user's videos
|
- **vk.com:user-videos**: vk.com:All of a user's videos
|
||||||
- **Vodlocker**
|
- **Vodlocker**
|
||||||
|
- **VoiceRepublic**
|
||||||
- **Vporn**
|
- **Vporn**
|
||||||
- **VRT**
|
- **VRT**
|
||||||
- **vube**: Vube.com
|
- **vube**: Vube.com
|
||||||
@@ -533,28 +628,36 @@
|
|||||||
- **wdr:mobile**
|
- **wdr:mobile**
|
||||||
- **WDRMaus**: Sendung mit der Maus
|
- **WDRMaus**: Sendung mit der Maus
|
||||||
- **WebOfStories**
|
- **WebOfStories**
|
||||||
|
- **WebOfStoriesPlaylist**
|
||||||
- **Weibo**
|
- **Weibo**
|
||||||
- **Wimp**
|
- **Wimp**
|
||||||
- **Wistia**
|
- **Wistia**
|
||||||
|
- **WNL**
|
||||||
- **WorldStarHipHop**
|
- **WorldStarHipHop**
|
||||||
- **wrzuta.pl**
|
- **wrzuta.pl**
|
||||||
- **WSJ**: Wall Street Journal
|
- **WSJ**: Wall Street Journal
|
||||||
- **XBef**
|
- **XBef**
|
||||||
- **XboxClips**
|
- **XboxClips**
|
||||||
- **XHamster**
|
- **XHamster**
|
||||||
|
- **XHamsterEmbed**
|
||||||
- **XMinus**
|
- **XMinus**
|
||||||
- **XNXX**
|
- **XNXX**
|
||||||
|
- **Xstream**
|
||||||
- **XTube**
|
- **XTube**
|
||||||
- **XTubeUser**: XTube user profile
|
- **XTubeUser**: XTube user profile
|
||||||
- **Xuite**
|
- **Xuite**: 隨意窩Xuite影音
|
||||||
- **XVideos**
|
- **XVideos**
|
||||||
- **XXXYMovies**
|
- **XXXYMovies**
|
||||||
- **Yahoo**: Yahoo screen and movies
|
- **Yahoo**: Yahoo screen and movies
|
||||||
- **Yam**
|
- **Yam**: 蕃薯藤yam天空部落
|
||||||
|
- **yandexmusic:album**: Яндекс.Музыка - Альбом
|
||||||
|
- **yandexmusic:playlist**: Яндекс.Музыка - Плейлист
|
||||||
|
- **yandexmusic:track**: Яндекс.Музыка - Трек
|
||||||
- **YesJapan**
|
- **YesJapan**
|
||||||
|
- **yinyuetai:video**: 音悦Tai
|
||||||
- **Ynet**
|
- **Ynet**
|
||||||
- **YouJizz**
|
- **YouJizz**
|
||||||
- **Youku**
|
- **youku**: 优酷
|
||||||
- **YouPorn**
|
- **YouPorn**
|
||||||
- **YourUpload**
|
- **YourUpload**
|
||||||
- **youtube**: YouTube.com
|
- **youtube**: YouTube.com
|
||||||
@@ -569,7 +672,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:watch_later**: Youtube watch later list, ":ytwatchlater" for short (requires authentication)
|
- **youtube:watchlater**: Youtube watch later list, ":ytwatchlater" for short (requires authentication)
|
||||||
- **Zapiks**
|
- **Zapiks**
|
||||||
- **ZDF**
|
- **ZDF**
|
||||||
- **ZDFChannel**
|
- **ZDFChannel**
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ def expect_info_dict(self, got_dict, expected_dict):
|
|||||||
'invalid value for field %s, expected %r, got %r' % (info_field, expected, got))
|
'invalid value for field %s, expected %r, got %r' % (info_field, expected, got))
|
||||||
|
|
||||||
# Check for the presence of mandatory fields
|
# Check for the presence of mandatory fields
|
||||||
if got_dict.get('_type') != 'playlist':
|
if got_dict.get('_type') not in ('playlist', 'multi_video'):
|
||||||
for key in ('id', 'url', 'title', 'ext'):
|
for key in ('id', 'url', 'title', 'ext'):
|
||||||
self.assertTrue(got_dict.get(key), 'Missing mandatory field %s' % key)
|
self.assertTrue(got_dict.get(key), 'Missing mandatory field %s' % key)
|
||||||
# Check for mandatory fields that are automatically set by YoutubeDL
|
# Check for mandatory fields that are automatically set by YoutubeDL
|
||||||
|
|||||||
@@ -7,8 +7,7 @@
|
|||||||
"forcethumbnail": false,
|
"forcethumbnail": false,
|
||||||
"forcetitle": false,
|
"forcetitle": false,
|
||||||
"forceurl": false,
|
"forceurl": false,
|
||||||
"format": null,
|
"format": "best",
|
||||||
"format_limit": null,
|
|
||||||
"ignoreerrors": false,
|
"ignoreerrors": false,
|
||||||
"listformats": null,
|
"listformats": null,
|
||||||
"logtostderr": false,
|
"logtostderr": false,
|
||||||
|
|||||||
@@ -12,8 +12,12 @@ import copy
|
|||||||
|
|
||||||
from test.helper import FakeYDL, assertRegexpMatches
|
from test.helper import FakeYDL, assertRegexpMatches
|
||||||
from youtube_dl import YoutubeDL
|
from youtube_dl import YoutubeDL
|
||||||
|
from youtube_dl.compat import compat_str
|
||||||
from youtube_dl.extractor import YoutubeIE
|
from youtube_dl.extractor import YoutubeIE
|
||||||
from youtube_dl.postprocessor.common import PostProcessor
|
from youtube_dl.postprocessor.common import PostProcessor
|
||||||
|
from youtube_dl.utils import match_filter_func
|
||||||
|
|
||||||
|
TEST_URL = 'http://localhost/sample.mp4'
|
||||||
|
|
||||||
|
|
||||||
class YDL(FakeYDL):
|
class YDL(FakeYDL):
|
||||||
@@ -46,8 +50,8 @@ class TestFormatSelection(unittest.TestCase):
|
|||||||
ydl = YDL()
|
ydl = YDL()
|
||||||
ydl.params['prefer_free_formats'] = True
|
ydl.params['prefer_free_formats'] = True
|
||||||
formats = [
|
formats = [
|
||||||
{'ext': 'webm', 'height': 460, 'url': 'x'},
|
{'ext': 'webm', 'height': 460, 'url': TEST_URL},
|
||||||
{'ext': 'mp4', 'height': 460, 'url': 'y'},
|
{'ext': 'mp4', 'height': 460, 'url': TEST_URL},
|
||||||
]
|
]
|
||||||
info_dict = _make_result(formats)
|
info_dict = _make_result(formats)
|
||||||
yie = YoutubeIE(ydl)
|
yie = YoutubeIE(ydl)
|
||||||
@@ -60,8 +64,8 @@ class TestFormatSelection(unittest.TestCase):
|
|||||||
ydl = YDL()
|
ydl = YDL()
|
||||||
ydl.params['prefer_free_formats'] = True
|
ydl.params['prefer_free_formats'] = True
|
||||||
formats = [
|
formats = [
|
||||||
{'ext': 'webm', 'height': 720, 'url': 'a'},
|
{'ext': 'webm', 'height': 720, 'url': TEST_URL},
|
||||||
{'ext': 'mp4', 'height': 1080, 'url': 'b'},
|
{'ext': 'mp4', 'height': 1080, 'url': TEST_URL},
|
||||||
]
|
]
|
||||||
info_dict['formats'] = formats
|
info_dict['formats'] = formats
|
||||||
yie = YoutubeIE(ydl)
|
yie = YoutubeIE(ydl)
|
||||||
@@ -74,9 +78,9 @@ class TestFormatSelection(unittest.TestCase):
|
|||||||
ydl = YDL()
|
ydl = YDL()
|
||||||
ydl.params['prefer_free_formats'] = False
|
ydl.params['prefer_free_formats'] = False
|
||||||
formats = [
|
formats = [
|
||||||
{'ext': 'webm', 'height': 720, 'url': '_'},
|
{'ext': 'webm', 'height': 720, 'url': TEST_URL},
|
||||||
{'ext': 'mp4', 'height': 720, 'url': '_'},
|
{'ext': 'mp4', 'height': 720, 'url': TEST_URL},
|
||||||
{'ext': 'flv', 'height': 720, 'url': '_'},
|
{'ext': 'flv', 'height': 720, 'url': TEST_URL},
|
||||||
]
|
]
|
||||||
info_dict['formats'] = formats
|
info_dict['formats'] = formats
|
||||||
yie = YoutubeIE(ydl)
|
yie = YoutubeIE(ydl)
|
||||||
@@ -88,8 +92,8 @@ class TestFormatSelection(unittest.TestCase):
|
|||||||
ydl = YDL()
|
ydl = YDL()
|
||||||
ydl.params['prefer_free_formats'] = False
|
ydl.params['prefer_free_formats'] = False
|
||||||
formats = [
|
formats = [
|
||||||
{'ext': 'flv', 'height': 720, 'url': '_'},
|
{'ext': 'flv', 'height': 720, 'url': TEST_URL},
|
||||||
{'ext': 'webm', 'height': 720, 'url': '_'},
|
{'ext': 'webm', 'height': 720, 'url': TEST_URL},
|
||||||
]
|
]
|
||||||
info_dict['formats'] = formats
|
info_dict['formats'] = formats
|
||||||
yie = YoutubeIE(ydl)
|
yie = YoutubeIE(ydl)
|
||||||
@@ -98,45 +102,12 @@ class TestFormatSelection(unittest.TestCase):
|
|||||||
downloaded = ydl.downloaded_info_dicts[0]
|
downloaded = ydl.downloaded_info_dicts[0]
|
||||||
self.assertEqual(downloaded['ext'], 'flv')
|
self.assertEqual(downloaded['ext'], 'flv')
|
||||||
|
|
||||||
def test_format_limit(self):
|
|
||||||
formats = [
|
|
||||||
{'format_id': 'meh', 'url': 'http://example.com/meh', 'preference': 1},
|
|
||||||
{'format_id': 'good', 'url': 'http://example.com/good', 'preference': 2},
|
|
||||||
{'format_id': 'great', 'url': 'http://example.com/great', 'preference': 3},
|
|
||||||
{'format_id': 'excellent', 'url': 'http://example.com/exc', 'preference': 4},
|
|
||||||
]
|
|
||||||
info_dict = _make_result(formats)
|
|
||||||
|
|
||||||
ydl = YDL()
|
|
||||||
ydl.process_ie_result(info_dict)
|
|
||||||
downloaded = ydl.downloaded_info_dicts[0]
|
|
||||||
self.assertEqual(downloaded['format_id'], 'excellent')
|
|
||||||
|
|
||||||
ydl = YDL({'format_limit': 'good'})
|
|
||||||
assert ydl.params['format_limit'] == 'good'
|
|
||||||
ydl.process_ie_result(info_dict.copy())
|
|
||||||
downloaded = ydl.downloaded_info_dicts[0]
|
|
||||||
self.assertEqual(downloaded['format_id'], 'good')
|
|
||||||
|
|
||||||
ydl = YDL({'format_limit': 'great', 'format': 'all'})
|
|
||||||
ydl.process_ie_result(info_dict.copy())
|
|
||||||
self.assertEqual(ydl.downloaded_info_dicts[0]['format_id'], 'meh')
|
|
||||||
self.assertEqual(ydl.downloaded_info_dicts[1]['format_id'], 'good')
|
|
||||||
self.assertEqual(ydl.downloaded_info_dicts[2]['format_id'], 'great')
|
|
||||||
self.assertTrue('3' in ydl.msgs[0])
|
|
||||||
|
|
||||||
ydl = YDL()
|
|
||||||
ydl.params['format_limit'] = 'excellent'
|
|
||||||
ydl.process_ie_result(info_dict.copy())
|
|
||||||
downloaded = ydl.downloaded_info_dicts[0]
|
|
||||||
self.assertEqual(downloaded['format_id'], 'excellent')
|
|
||||||
|
|
||||||
def test_format_selection(self):
|
def test_format_selection(self):
|
||||||
formats = [
|
formats = [
|
||||||
{'format_id': '35', 'ext': 'mp4', 'preference': 1, 'url': '_'},
|
{'format_id': '35', 'ext': 'mp4', 'preference': 1, 'url': TEST_URL},
|
||||||
{'format_id': '45', 'ext': 'webm', 'preference': 2, 'url': '_'},
|
{'format_id': '45', 'ext': 'webm', 'preference': 2, 'url': TEST_URL},
|
||||||
{'format_id': '47', 'ext': 'webm', 'preference': 3, 'url': '_'},
|
{'format_id': '47', 'ext': 'webm', 'preference': 3, 'url': TEST_URL},
|
||||||
{'format_id': '2', 'ext': 'flv', 'preference': 4, 'url': '_'},
|
{'format_id': '2', 'ext': 'flv', 'preference': 4, 'url': TEST_URL},
|
||||||
]
|
]
|
||||||
info_dict = _make_result(formats)
|
info_dict = _make_result(formats)
|
||||||
|
|
||||||
@@ -167,10 +138,10 @@ class TestFormatSelection(unittest.TestCase):
|
|||||||
|
|
||||||
def test_format_selection_audio(self):
|
def test_format_selection_audio(self):
|
||||||
formats = [
|
formats = [
|
||||||
{'format_id': 'audio-low', 'ext': 'webm', 'preference': 1, 'vcodec': 'none', 'url': '_'},
|
{'format_id': 'audio-low', 'ext': 'webm', 'preference': 1, 'vcodec': 'none', 'url': TEST_URL},
|
||||||
{'format_id': 'audio-mid', 'ext': 'webm', 'preference': 2, 'vcodec': 'none', 'url': '_'},
|
{'format_id': 'audio-mid', 'ext': 'webm', 'preference': 2, 'vcodec': 'none', 'url': TEST_URL},
|
||||||
{'format_id': 'audio-high', 'ext': 'flv', 'preference': 3, 'vcodec': 'none', 'url': '_'},
|
{'format_id': 'audio-high', 'ext': 'flv', 'preference': 3, 'vcodec': 'none', 'url': TEST_URL},
|
||||||
{'format_id': 'vid', 'ext': 'mp4', 'preference': 4, 'url': '_'},
|
{'format_id': 'vid', 'ext': 'mp4', 'preference': 4, 'url': TEST_URL},
|
||||||
]
|
]
|
||||||
info_dict = _make_result(formats)
|
info_dict = _make_result(formats)
|
||||||
|
|
||||||
@@ -185,8 +156,8 @@ class TestFormatSelection(unittest.TestCase):
|
|||||||
self.assertEqual(downloaded['format_id'], 'audio-low')
|
self.assertEqual(downloaded['format_id'], 'audio-low')
|
||||||
|
|
||||||
formats = [
|
formats = [
|
||||||
{'format_id': 'vid-low', 'ext': 'mp4', 'preference': 1, 'url': '_'},
|
{'format_id': 'vid-low', 'ext': 'mp4', 'preference': 1, 'url': TEST_URL},
|
||||||
{'format_id': 'vid-high', 'ext': 'mp4', 'preference': 2, 'url': '_'},
|
{'format_id': 'vid-high', 'ext': 'mp4', 'preference': 2, 'url': TEST_URL},
|
||||||
]
|
]
|
||||||
info_dict = _make_result(formats)
|
info_dict = _make_result(formats)
|
||||||
|
|
||||||
@@ -228,9 +199,9 @@ class TestFormatSelection(unittest.TestCase):
|
|||||||
|
|
||||||
def test_format_selection_video(self):
|
def test_format_selection_video(self):
|
||||||
formats = [
|
formats = [
|
||||||
{'format_id': 'dash-video-low', 'ext': 'mp4', 'preference': 1, 'acodec': 'none', 'url': '_'},
|
{'format_id': 'dash-video-low', 'ext': 'mp4', 'preference': 1, 'acodec': 'none', 'url': TEST_URL},
|
||||||
{'format_id': 'dash-video-high', 'ext': 'mp4', 'preference': 2, 'acodec': 'none', 'url': '_'},
|
{'format_id': 'dash-video-high', 'ext': 'mp4', 'preference': 2, 'acodec': 'none', 'url': TEST_URL},
|
||||||
{'format_id': 'vid', 'ext': 'mp4', 'preference': 3, 'url': '_'},
|
{'format_id': 'vid', 'ext': 'mp4', 'preference': 3, 'url': TEST_URL},
|
||||||
]
|
]
|
||||||
info_dict = _make_result(formats)
|
info_dict = _make_result(formats)
|
||||||
|
|
||||||
@@ -267,7 +238,7 @@ class TestFormatSelection(unittest.TestCase):
|
|||||||
f2['url'] = 'url:' + f2id
|
f2['url'] = 'url:' + f2id
|
||||||
|
|
||||||
info_dict = _make_result([f1, f2], extractor='youtube')
|
info_dict = _make_result([f1, f2], extractor='youtube')
|
||||||
ydl = YDL()
|
ydl = YDL({'format': 'best/bestvideo'})
|
||||||
yie = YoutubeIE(ydl)
|
yie = YoutubeIE(ydl)
|
||||||
yie._sort_formats(info_dict['formats'])
|
yie._sort_formats(info_dict['formats'])
|
||||||
ydl.process_ie_result(info_dict)
|
ydl.process_ie_result(info_dict)
|
||||||
@@ -275,7 +246,7 @@ class TestFormatSelection(unittest.TestCase):
|
|||||||
self.assertEqual(downloaded['format_id'], f1id)
|
self.assertEqual(downloaded['format_id'], f1id)
|
||||||
|
|
||||||
info_dict = _make_result([f2, f1], extractor='youtube')
|
info_dict = _make_result([f2, f1], extractor='youtube')
|
||||||
ydl = YDL()
|
ydl = YDL({'format': 'best/bestvideo'})
|
||||||
yie = YoutubeIE(ydl)
|
yie = YoutubeIE(ydl)
|
||||||
yie._sort_formats(info_dict['formats'])
|
yie._sort_formats(info_dict['formats'])
|
||||||
ydl.process_ie_result(info_dict)
|
ydl.process_ie_result(info_dict)
|
||||||
@@ -337,6 +308,8 @@ class TestFormatSelection(unittest.TestCase):
|
|||||||
downloaded = ydl.downloaded_info_dicts[0]
|
downloaded = ydl.downloaded_info_dicts[0]
|
||||||
self.assertEqual(downloaded['format_id'], 'G')
|
self.assertEqual(downloaded['format_id'], 'G')
|
||||||
|
|
||||||
|
|
||||||
|
class TestYoutubeDL(unittest.TestCase):
|
||||||
def test_subtitles(self):
|
def test_subtitles(self):
|
||||||
def s_formats(lang, autocaption=False):
|
def s_formats(lang, autocaption=False):
|
||||||
return [{
|
return [{
|
||||||
@@ -438,27 +411,148 @@ class TestFormatSelection(unittest.TestCase):
|
|||||||
def run(self, info):
|
def run(self, info):
|
||||||
with open(audiofile, 'wt') as f:
|
with open(audiofile, 'wt') as f:
|
||||||
f.write('EXAMPLE')
|
f.write('EXAMPLE')
|
||||||
info['filepath']
|
return [info['filepath']], info
|
||||||
return False, info
|
|
||||||
|
|
||||||
def run_pp(params):
|
def run_pp(params, PP):
|
||||||
with open(filename, 'wt') as f:
|
with open(filename, 'wt') as f:
|
||||||
f.write('EXAMPLE')
|
f.write('EXAMPLE')
|
||||||
ydl = YoutubeDL(params)
|
ydl = YoutubeDL(params)
|
||||||
ydl.add_post_processor(SimplePP())
|
ydl.add_post_processor(PP())
|
||||||
ydl.post_process(filename, {'filepath': filename})
|
ydl.post_process(filename, {'filepath': filename})
|
||||||
|
|
||||||
run_pp({'keepvideo': True})
|
run_pp({'keepvideo': True}, SimplePP)
|
||||||
self.assertTrue(os.path.exists(filename), '%s doesn\'t exist' % filename)
|
self.assertTrue(os.path.exists(filename), '%s doesn\'t exist' % filename)
|
||||||
self.assertTrue(os.path.exists(audiofile), '%s doesn\'t exist' % audiofile)
|
self.assertTrue(os.path.exists(audiofile), '%s doesn\'t exist' % audiofile)
|
||||||
os.unlink(filename)
|
os.unlink(filename)
|
||||||
os.unlink(audiofile)
|
os.unlink(audiofile)
|
||||||
|
|
||||||
run_pp({'keepvideo': False})
|
run_pp({'keepvideo': False}, SimplePP)
|
||||||
self.assertFalse(os.path.exists(filename), '%s exists' % filename)
|
self.assertFalse(os.path.exists(filename), '%s exists' % filename)
|
||||||
self.assertTrue(os.path.exists(audiofile), '%s doesn\'t exist' % audiofile)
|
self.assertTrue(os.path.exists(audiofile), '%s doesn\'t exist' % audiofile)
|
||||||
os.unlink(audiofile)
|
os.unlink(audiofile)
|
||||||
|
|
||||||
|
class ModifierPP(PostProcessor):
|
||||||
|
def run(self, info):
|
||||||
|
with open(info['filepath'], 'wt') as f:
|
||||||
|
f.write('MODIFIED')
|
||||||
|
return [], info
|
||||||
|
|
||||||
|
run_pp({'keepvideo': False}, ModifierPP)
|
||||||
|
self.assertTrue(os.path.exists(filename), '%s doesn\'t exist' % filename)
|
||||||
|
os.unlink(filename)
|
||||||
|
|
||||||
|
def test_match_filter(self):
|
||||||
|
class FilterYDL(YDL):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(FilterYDL, self).__init__(*args, **kwargs)
|
||||||
|
self.params['simulate'] = True
|
||||||
|
|
||||||
|
def process_info(self, info_dict):
|
||||||
|
super(YDL, self).process_info(info_dict)
|
||||||
|
|
||||||
|
def _match_entry(self, info_dict, incomplete):
|
||||||
|
res = super(FilterYDL, self)._match_entry(info_dict, incomplete)
|
||||||
|
if res is None:
|
||||||
|
self.downloaded_info_dicts.append(info_dict)
|
||||||
|
return res
|
||||||
|
|
||||||
|
first = {
|
||||||
|
'id': '1',
|
||||||
|
'url': TEST_URL,
|
||||||
|
'title': 'one',
|
||||||
|
'extractor': 'TEST',
|
||||||
|
'duration': 30,
|
||||||
|
'filesize': 10 * 1024,
|
||||||
|
}
|
||||||
|
second = {
|
||||||
|
'id': '2',
|
||||||
|
'url': TEST_URL,
|
||||||
|
'title': 'two',
|
||||||
|
'extractor': 'TEST',
|
||||||
|
'duration': 10,
|
||||||
|
'description': 'foo',
|
||||||
|
'filesize': 5 * 1024,
|
||||||
|
}
|
||||||
|
videos = [first, second]
|
||||||
|
|
||||||
|
def get_videos(filter_=None):
|
||||||
|
ydl = FilterYDL({'match_filter': filter_})
|
||||||
|
for v in videos:
|
||||||
|
ydl.process_ie_result(v, download=True)
|
||||||
|
return [v['id'] for v in ydl.downloaded_info_dicts]
|
||||||
|
|
||||||
|
res = get_videos()
|
||||||
|
self.assertEqual(res, ['1', '2'])
|
||||||
|
|
||||||
|
def f(v):
|
||||||
|
if v['id'] == '1':
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return 'Video id is not 1'
|
||||||
|
res = get_videos(f)
|
||||||
|
self.assertEqual(res, ['1'])
|
||||||
|
|
||||||
|
f = match_filter_func('duration < 30')
|
||||||
|
res = get_videos(f)
|
||||||
|
self.assertEqual(res, ['2'])
|
||||||
|
|
||||||
|
f = match_filter_func('description = foo')
|
||||||
|
res = get_videos(f)
|
||||||
|
self.assertEqual(res, ['2'])
|
||||||
|
|
||||||
|
f = match_filter_func('description =? foo')
|
||||||
|
res = get_videos(f)
|
||||||
|
self.assertEqual(res, ['1', '2'])
|
||||||
|
|
||||||
|
f = match_filter_func('filesize > 5KiB')
|
||||||
|
res = get_videos(f)
|
||||||
|
self.assertEqual(res, ['1'])
|
||||||
|
|
||||||
|
def test_playlist_items_selection(self):
|
||||||
|
entries = [{
|
||||||
|
'id': compat_str(i),
|
||||||
|
'title': compat_str(i),
|
||||||
|
'url': TEST_URL,
|
||||||
|
} for i in range(1, 5)]
|
||||||
|
playlist = {
|
||||||
|
'_type': 'playlist',
|
||||||
|
'id': 'test',
|
||||||
|
'entries': entries,
|
||||||
|
'extractor': 'test:playlist',
|
||||||
|
'extractor_key': 'test:playlist',
|
||||||
|
'webpage_url': 'http://example.com',
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_ids(params):
|
||||||
|
ydl = YDL(params)
|
||||||
|
# make a copy because the dictionary can be modified
|
||||||
|
ydl.process_ie_result(playlist.copy())
|
||||||
|
return [int(v['id']) for v in ydl.downloaded_info_dicts]
|
||||||
|
|
||||||
|
result = get_ids({})
|
||||||
|
self.assertEqual(result, [1, 2, 3, 4])
|
||||||
|
|
||||||
|
result = get_ids({'playlistend': 10})
|
||||||
|
self.assertEqual(result, [1, 2, 3, 4])
|
||||||
|
|
||||||
|
result = get_ids({'playlistend': 2})
|
||||||
|
self.assertEqual(result, [1, 2])
|
||||||
|
|
||||||
|
result = get_ids({'playliststart': 10})
|
||||||
|
self.assertEqual(result, [])
|
||||||
|
|
||||||
|
result = get_ids({'playliststart': 2})
|
||||||
|
self.assertEqual(result, [2, 3, 4])
|
||||||
|
|
||||||
|
result = get_ids({'playlist_items': '2-4'})
|
||||||
|
self.assertEqual(result, [2, 3, 4])
|
||||||
|
|
||||||
|
result = get_ids({'playlist_items': '2,4'})
|
||||||
|
self.assertEqual(result, [2, 4])
|
||||||
|
|
||||||
|
result = get_ids({'playlist_items': '10'})
|
||||||
|
self.assertEqual(result, [])
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
55
test/test_aes.py
Normal file
55
test/test_aes.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
# Allow direct execution
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
from youtube_dl.aes import aes_decrypt, aes_encrypt, aes_cbc_decrypt, aes_decrypt_text
|
||||||
|
from youtube_dl.utils import bytes_to_intlist, intlist_to_bytes
|
||||||
|
import base64
|
||||||
|
|
||||||
|
# the encrypted data can be generate with 'devscripts/generate_aes_testdata.py'
|
||||||
|
|
||||||
|
|
||||||
|
class TestAES(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.key = self.iv = [0x20, 0x15] + 14 * [0]
|
||||||
|
self.secret_msg = b'Secret message goes here'
|
||||||
|
|
||||||
|
def test_encrypt(self):
|
||||||
|
msg = b'message'
|
||||||
|
key = list(range(16))
|
||||||
|
encrypted = aes_encrypt(bytes_to_intlist(msg), key)
|
||||||
|
decrypted = intlist_to_bytes(aes_decrypt(encrypted, key))
|
||||||
|
self.assertEqual(decrypted, msg)
|
||||||
|
|
||||||
|
def test_cbc_decrypt(self):
|
||||||
|
data = bytes_to_intlist(
|
||||||
|
b"\x97\x92+\xe5\x0b\xc3\x18\x91ky9m&\xb3\xb5@\xe6'\xc2\x96.\xc8u\x88\xab9-[\x9e|\xf1\xcd"
|
||||||
|
)
|
||||||
|
decrypted = intlist_to_bytes(aes_cbc_decrypt(data, self.key, self.iv))
|
||||||
|
self.assertEqual(decrypted.rstrip(b'\x08'), self.secret_msg)
|
||||||
|
|
||||||
|
def test_decrypt_text(self):
|
||||||
|
password = intlist_to_bytes(self.key).decode('utf-8')
|
||||||
|
encrypted = base64.b64encode(
|
||||||
|
intlist_to_bytes(self.iv[:8]) +
|
||||||
|
b'\x17\x15\x93\xab\x8d\x80V\xcdV\xe0\t\xcdo\xc2\xa5\xd8ksM\r\xe27N\xae'
|
||||||
|
).decode('utf-8')
|
||||||
|
decrypted = (aes_decrypt_text(encrypted, password, 16))
|
||||||
|
self.assertEqual(decrypted, self.secret_msg)
|
||||||
|
|
||||||
|
password = intlist_to_bytes(self.key).decode('utf-8')
|
||||||
|
encrypted = base64.b64encode(
|
||||||
|
intlist_to_bytes(self.iv[:8]) +
|
||||||
|
b'\x0b\xe6\xa4\xd9z\x0e\xb8\xb9\xd0\xd4i_\x85\x1d\x99\x98_\xe5\x80\xe7.\xbf\xa5\x83'
|
||||||
|
).decode('utf-8')
|
||||||
|
decrypted = (aes_decrypt_text(encrypted, password, 32))
|
||||||
|
self.assertEqual(decrypted, self.secret_msg)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
@@ -59,7 +59,7 @@ class TestAllURLsMatching(unittest.TestCase):
|
|||||||
self.assertMatch('www.youtube.com/NASAgovVideo/videos', ['youtube:user'])
|
self.assertMatch('www.youtube.com/NASAgovVideo/videos', ['youtube:user'])
|
||||||
|
|
||||||
def test_youtube_feeds(self):
|
def test_youtube_feeds(self):
|
||||||
self.assertMatch('https://www.youtube.com/feed/watch_later', ['youtube:watch_later'])
|
self.assertMatch('https://www.youtube.com/feed/watch_later', ['youtube:watchlater'])
|
||||||
self.assertMatch('https://www.youtube.com/feed/subscriptions', ['youtube:subscriptions'])
|
self.assertMatch('https://www.youtube.com/feed/subscriptions', ['youtube:subscriptions'])
|
||||||
self.assertMatch('https://www.youtube.com/feed/recommended', ['youtube:recommended'])
|
self.assertMatch('https://www.youtube.com/feed/recommended', ['youtube:recommended'])
|
||||||
self.assertMatch('https://www.youtube.com/my_favorites', ['youtube:favorites'])
|
self.assertMatch('https://www.youtube.com/my_favorites', ['youtube:favorites'])
|
||||||
@@ -104,11 +104,11 @@ class TestAllURLsMatching(unittest.TestCase):
|
|||||||
self.assertMatch(':tds', ['ComedyCentralShows'])
|
self.assertMatch(':tds', ['ComedyCentralShows'])
|
||||||
|
|
||||||
def test_vimeo_matching(self):
|
def test_vimeo_matching(self):
|
||||||
self.assertMatch('http://vimeo.com/channels/tributes', ['vimeo:channel'])
|
self.assertMatch('https://vimeo.com/channels/tributes', ['vimeo:channel'])
|
||||||
self.assertMatch('http://vimeo.com/channels/31259', ['vimeo:channel'])
|
self.assertMatch('https://vimeo.com/channels/31259', ['vimeo:channel'])
|
||||||
self.assertMatch('http://vimeo.com/channels/31259/53576664', ['vimeo'])
|
self.assertMatch('https://vimeo.com/channels/31259/53576664', ['vimeo'])
|
||||||
self.assertMatch('http://vimeo.com/user7108434', ['vimeo:user'])
|
self.assertMatch('https://vimeo.com/user7108434', ['vimeo:user'])
|
||||||
self.assertMatch('http://vimeo.com/user7108434/videos', ['vimeo:user'])
|
self.assertMatch('https://vimeo.com/user7108434/videos', ['vimeo:user'])
|
||||||
self.assertMatch('https://vimeo.com/user21297594/review/75524534/3c257a1b5d', ['vimeo:review'])
|
self.assertMatch('https://vimeo.com/user21297594/review/75524534/3c257a1b5d', ['vimeo:review'])
|
||||||
|
|
||||||
# https://github.com/rg3/youtube-dl/issues/1930
|
# https://github.com/rg3/youtube-dl/issues/1930
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ from youtube_dl.utils import get_filesystem_encoding
|
|||||||
from youtube_dl.compat import (
|
from youtube_dl.compat import (
|
||||||
compat_getenv,
|
compat_getenv,
|
||||||
compat_expanduser,
|
compat_expanduser,
|
||||||
|
compat_urllib_parse_unquote,
|
||||||
|
compat_urllib_parse_unquote_plus,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -42,5 +44,28 @@ class TestCompat(unittest.TestCase):
|
|||||||
dir(youtube_dl.compat))) - set(['unicode_literals'])
|
dir(youtube_dl.compat))) - set(['unicode_literals'])
|
||||||
self.assertEqual(all_names, sorted(present_names))
|
self.assertEqual(all_names, sorted(present_names))
|
||||||
|
|
||||||
|
def test_compat_urllib_parse_unquote(self):
|
||||||
|
self.assertEqual(compat_urllib_parse_unquote('abc%20def'), 'abc def')
|
||||||
|
self.assertEqual(compat_urllib_parse_unquote('%7e/abc+def'), '~/abc+def')
|
||||||
|
self.assertEqual(compat_urllib_parse_unquote(''), '')
|
||||||
|
self.assertEqual(compat_urllib_parse_unquote('%'), '%')
|
||||||
|
self.assertEqual(compat_urllib_parse_unquote('%%'), '%%')
|
||||||
|
self.assertEqual(compat_urllib_parse_unquote('%%%'), '%%%')
|
||||||
|
self.assertEqual(compat_urllib_parse_unquote('%2F'), '/')
|
||||||
|
self.assertEqual(compat_urllib_parse_unquote('%2f'), '/')
|
||||||
|
self.assertEqual(compat_urllib_parse_unquote('%E6%B4%A5%E6%B3%A2'), '津波')
|
||||||
|
self.assertEqual(
|
||||||
|
compat_urllib_parse_unquote('''<meta property="og:description" content="%E2%96%81%E2%96%82%E2%96%83%E2%96%84%25%E2%96%85%E2%96%86%E2%96%87%E2%96%88" />
|
||||||
|
%<a href="https://ar.wikipedia.org/wiki/%D8%AA%D8%B3%D9%88%D9%86%D8%A7%D9%85%D9%8A">%a'''),
|
||||||
|
'''<meta property="og:description" content="▁▂▃▄%▅▆▇█" />
|
||||||
|
%<a href="https://ar.wikipedia.org/wiki/تسونامي">%a''')
|
||||||
|
self.assertEqual(
|
||||||
|
compat_urllib_parse_unquote('''%28%5E%E2%97%A3_%E2%97%A2%5E%29%E3%81%A3%EF%B8%BB%E3%83%87%E2%95%90%E4%B8%80 %E2%87%80 %E2%87%80 %E2%87%80 %E2%87%80 %E2%87%80 %E2%86%B6%I%Break%25Things%'''),
|
||||||
|
'''(^◣_◢^)っ︻デ═一 ⇀ ⇀ ⇀ ⇀ ⇀ ↶%I%Break%Things%''')
|
||||||
|
|
||||||
|
def test_compat_urllib_parse_unquote_plus(self):
|
||||||
|
self.assertEqual(compat_urllib_parse_unquote_plus('abc%20def'), 'abc def')
|
||||||
|
self.assertEqual(compat_urllib_parse_unquote_plus('%7e/abc+def'), '~/abc def')
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ def generator(test_case):
|
|||||||
break
|
break
|
||||||
|
|
||||||
if is_playlist:
|
if is_playlist:
|
||||||
self.assertEqual(res_dict['_type'], 'playlist')
|
self.assertTrue(res_dict['_type'] in ['playlist', 'multi_video'])
|
||||||
self.assertTrue('entries' in res_dict)
|
self.assertTrue('entries' in res_dict)
|
||||||
expect_info_dict(self, res_dict, test_case.get('info_dict', {}))
|
expect_info_dict(self, res_dict, test_case.get('info_dict', {}))
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
# coding: utf-8
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
@@ -6,6 +8,9 @@ import unittest
|
|||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
from youtube_dl.utils import encodeArgument
|
||||||
|
|
||||||
rootDir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
rootDir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
@@ -27,5 +32,12 @@ class TestExecution(unittest.TestCase):
|
|||||||
def test_main_exec(self):
|
def test_main_exec(self):
|
||||||
subprocess.check_call([sys.executable, 'youtube_dl/__main__.py', '--version'], cwd=rootDir, stdout=_DEV_NULL)
|
subprocess.check_call([sys.executable, 'youtube_dl/__main__.py', '--version'], cwd=rootDir, stdout=_DEV_NULL)
|
||||||
|
|
||||||
|
def test_cmdline_umlauts(self):
|
||||||
|
p = subprocess.Popen(
|
||||||
|
[sys.executable, 'youtube_dl/__main__.py', encodeArgument('ä'), '--version'],
|
||||||
|
cwd=rootDir, stdout=_DEV_NULL, stderr=subprocess.PIPE)
|
||||||
|
_, stderr = p.communicate()
|
||||||
|
self.assertFalse(stderr)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import unittest
|
|||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
from youtube_dl import YoutubeDL
|
from youtube_dl import YoutubeDL
|
||||||
from youtube_dl.compat import compat_http_server
|
from youtube_dl.compat import compat_http_server, compat_urllib_request
|
||||||
import ssl
|
import ssl
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
@@ -68,5 +68,52 @@ class TestHTTP(unittest.TestCase):
|
|||||||
r = ydl.extract_info('https://localhost:%d/video.html' % self.port)
|
r = ydl.extract_info('https://localhost:%d/video.html' % self.port)
|
||||||
self.assertEqual(r['url'], 'https://localhost:%d/vid.mp4' % self.port)
|
self.assertEqual(r['url'], 'https://localhost:%d/vid.mp4' % self.port)
|
||||||
|
|
||||||
|
|
||||||
|
def _build_proxy_handler(name):
|
||||||
|
class HTTPTestRequestHandler(compat_http_server.BaseHTTPRequestHandler):
|
||||||
|
proxy_name = name
|
||||||
|
|
||||||
|
def log_message(self, format, *args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def do_GET(self):
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-Type', 'text/plain; charset=utf-8')
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write('{self.proxy_name}: {self.path}'.format(self=self).encode('utf-8'))
|
||||||
|
return HTTPTestRequestHandler
|
||||||
|
|
||||||
|
|
||||||
|
class TestProxy(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.proxy = compat_http_server.HTTPServer(
|
||||||
|
('localhost', 0), _build_proxy_handler('normal'))
|
||||||
|
self.port = self.proxy.socket.getsockname()[1]
|
||||||
|
self.proxy_thread = threading.Thread(target=self.proxy.serve_forever)
|
||||||
|
self.proxy_thread.daemon = True
|
||||||
|
self.proxy_thread.start()
|
||||||
|
|
||||||
|
self.cn_proxy = compat_http_server.HTTPServer(
|
||||||
|
('localhost', 0), _build_proxy_handler('cn'))
|
||||||
|
self.cn_port = self.cn_proxy.socket.getsockname()[1]
|
||||||
|
self.cn_proxy_thread = threading.Thread(target=self.cn_proxy.serve_forever)
|
||||||
|
self.cn_proxy_thread.daemon = True
|
||||||
|
self.cn_proxy_thread.start()
|
||||||
|
|
||||||
|
def test_proxy(self):
|
||||||
|
cn_proxy = 'localhost:{0}'.format(self.cn_port)
|
||||||
|
ydl = YoutubeDL({
|
||||||
|
'proxy': 'localhost:{0}'.format(self.port),
|
||||||
|
'cn_verification_proxy': cn_proxy,
|
||||||
|
})
|
||||||
|
url = 'http://foo.com/bar'
|
||||||
|
response = ydl.urlopen(url).read().decode('utf-8')
|
||||||
|
self.assertEqual(response, 'normal: {0}'.format(url))
|
||||||
|
|
||||||
|
req = compat_urllib_request.Request(url)
|
||||||
|
req.add_header('Ytdl-request-proxy', cn_proxy)
|
||||||
|
response = ydl.urlopen(req).read().decode('utf-8')
|
||||||
|
self.assertEqual(response, 'cn: {0}'.format(url))
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
17
test/test_postprocessors.py
Normal file
17
test/test_postprocessors.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
# Allow direct execution
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
from youtube_dl.postprocessor import MetadataFromTitlePP
|
||||||
|
|
||||||
|
|
||||||
|
class TestMetadataFromTitle(unittest.TestCase):
|
||||||
|
def test_format_to_regex(self):
|
||||||
|
pp = MetadataFromTitlePP(None, '%(title)s - %(artist)s')
|
||||||
|
self.assertEqual(pp._titleregex, '(?P<title>.+)\ \-\ (?P<artist>.+)')
|
||||||
@@ -26,6 +26,7 @@ from youtube_dl.extractor import (
|
|||||||
VikiIE,
|
VikiIE,
|
||||||
ThePlatformIE,
|
ThePlatformIE,
|
||||||
RTVEALaCartaIE,
|
RTVEALaCartaIE,
|
||||||
|
FunnyOrDieIE,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -265,7 +266,7 @@ class TestNRKSubtitles(BaseTestSubtitles):
|
|||||||
self.DL.params['allsubtitles'] = True
|
self.DL.params['allsubtitles'] = True
|
||||||
subtitles = self.getSubtitles()
|
subtitles = self.getSubtitles()
|
||||||
self.assertEqual(set(subtitles.keys()), set(['no']))
|
self.assertEqual(set(subtitles.keys()), set(['no']))
|
||||||
self.assertEqual(md5(subtitles['no']), '1d221e6458c95c5494dcd38e6a1f129a')
|
self.assertEqual(md5(subtitles['no']), '544fa917d3197fcbee64634559221cc2')
|
||||||
|
|
||||||
|
|
||||||
class TestRaiSubtitles(BaseTestSubtitles):
|
class TestRaiSubtitles(BaseTestSubtitles):
|
||||||
@@ -320,5 +321,17 @@ class TestRtveSubtitles(BaseTestSubtitles):
|
|||||||
self.assertEqual(md5(subtitles['es']), '69e70cae2d40574fb7316f31d6eb7fca')
|
self.assertEqual(md5(subtitles['es']), '69e70cae2d40574fb7316f31d6eb7fca')
|
||||||
|
|
||||||
|
|
||||||
|
class TestFunnyOrDieSubtitles(BaseTestSubtitles):
|
||||||
|
url = 'http://www.funnyordie.com/videos/224829ff6d/judd-apatow-will-direct-your-vine'
|
||||||
|
IE = FunnyOrDieIE
|
||||||
|
|
||||||
|
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']), 'c5593c193eacd353596c11c2d4f9ecc4')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@@ -17,13 +17,22 @@ IGNORED_FILES = [
|
|||||||
'buildserver.py',
|
'buildserver.py',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
IGNORED_DIRS = [
|
||||||
|
'.git',
|
||||||
|
'.tox',
|
||||||
|
]
|
||||||
|
|
||||||
from test.helper import assertRegexpMatches
|
from test.helper import assertRegexpMatches
|
||||||
|
|
||||||
|
|
||||||
class TestUnicodeLiterals(unittest.TestCase):
|
class TestUnicodeLiterals(unittest.TestCase):
|
||||||
def test_all_files(self):
|
def test_all_files(self):
|
||||||
for dirpath, _, filenames in os.walk(rootDir):
|
for dirpath, dirnames, filenames in os.walk(rootDir):
|
||||||
|
for ignore_dir in IGNORED_DIRS:
|
||||||
|
if ignore_dir in dirnames:
|
||||||
|
# If we remove the directory from dirnames os.walk won't
|
||||||
|
# recurse into it
|
||||||
|
dirnames.remove(ignore_dir)
|
||||||
for basename in filenames:
|
for basename in filenames:
|
||||||
if not basename.endswith('.py'):
|
if not basename.endswith('.py'):
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ from youtube_dl.utils import (
|
|||||||
encodeFilename,
|
encodeFilename,
|
||||||
escape_rfc3986,
|
escape_rfc3986,
|
||||||
escape_url,
|
escape_url,
|
||||||
|
ExtractorError,
|
||||||
find_xpath_attr,
|
find_xpath_attr,
|
||||||
fix_xml_ampersands,
|
fix_xml_ampersands,
|
||||||
InAdvancePagedList,
|
InAdvancePagedList,
|
||||||
@@ -38,6 +39,9 @@ from youtube_dl.utils import (
|
|||||||
parse_iso8601,
|
parse_iso8601,
|
||||||
read_batch_urls,
|
read_batch_urls,
|
||||||
sanitize_filename,
|
sanitize_filename,
|
||||||
|
sanitize_path,
|
||||||
|
prepend_extension,
|
||||||
|
replace_extension,
|
||||||
shell_quote,
|
shell_quote,
|
||||||
smuggle_url,
|
smuggle_url,
|
||||||
str_to_int,
|
str_to_int,
|
||||||
@@ -48,12 +52,16 @@ from youtube_dl.utils import (
|
|||||||
unified_strdate,
|
unified_strdate,
|
||||||
unsmuggle_url,
|
unsmuggle_url,
|
||||||
uppercase_escape,
|
uppercase_escape,
|
||||||
|
lowercase_escape,
|
||||||
url_basename,
|
url_basename,
|
||||||
urlencode_postdata,
|
urlencode_postdata,
|
||||||
version_tuple,
|
version_tuple,
|
||||||
xpath_with_ns,
|
xpath_with_ns,
|
||||||
|
xpath_text,
|
||||||
render_table,
|
render_table,
|
||||||
match_str,
|
match_str,
|
||||||
|
parse_dfxp_time_expr,
|
||||||
|
dfxp2srt,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -131,6 +139,58 @@ class TestUtil(unittest.TestCase):
|
|||||||
self.assertEqual(sanitize_filename('_BD_eEpuzXw', is_id=True), '_BD_eEpuzXw')
|
self.assertEqual(sanitize_filename('_BD_eEpuzXw', is_id=True), '_BD_eEpuzXw')
|
||||||
self.assertEqual(sanitize_filename('N0Y__7-UOdI', is_id=True), 'N0Y__7-UOdI')
|
self.assertEqual(sanitize_filename('N0Y__7-UOdI', is_id=True), 'N0Y__7-UOdI')
|
||||||
|
|
||||||
|
def test_sanitize_path(self):
|
||||||
|
if sys.platform != 'win32':
|
||||||
|
return
|
||||||
|
|
||||||
|
self.assertEqual(sanitize_path('abc'), 'abc')
|
||||||
|
self.assertEqual(sanitize_path('abc/def'), 'abc\\def')
|
||||||
|
self.assertEqual(sanitize_path('abc\\def'), 'abc\\def')
|
||||||
|
self.assertEqual(sanitize_path('abc|def'), 'abc#def')
|
||||||
|
self.assertEqual(sanitize_path('<>:"|?*'), '#######')
|
||||||
|
self.assertEqual(sanitize_path('C:/abc/def'), 'C:\\abc\\def')
|
||||||
|
self.assertEqual(sanitize_path('C?:/abc/def'), 'C##\\abc\\def')
|
||||||
|
|
||||||
|
self.assertEqual(sanitize_path('\\\\?\\UNC\\ComputerName\\abc'), '\\\\?\\UNC\\ComputerName\\abc')
|
||||||
|
self.assertEqual(sanitize_path('\\\\?\\UNC/ComputerName/abc'), '\\\\?\\UNC\\ComputerName\\abc')
|
||||||
|
|
||||||
|
self.assertEqual(sanitize_path('\\\\?\\C:\\abc'), '\\\\?\\C:\\abc')
|
||||||
|
self.assertEqual(sanitize_path('\\\\?\\C:/abc'), '\\\\?\\C:\\abc')
|
||||||
|
self.assertEqual(sanitize_path('\\\\?\\C:\\ab?c\\de:f'), '\\\\?\\C:\\ab#c\\de#f')
|
||||||
|
self.assertEqual(sanitize_path('\\\\?\\C:\\abc'), '\\\\?\\C:\\abc')
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sanitize_path('youtube/%(uploader)s/%(autonumber)s-%(title)s-%(upload_date)s.%(ext)s'),
|
||||||
|
'youtube\\%(uploader)s\\%(autonumber)s-%(title)s-%(upload_date)s.%(ext)s')
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sanitize_path('youtube/TheWreckingYard ./00001-Not bad, Especially for Free! (1987 Yamaha 700)-20141116.mp4.part'),
|
||||||
|
'youtube\\TheWreckingYard #\\00001-Not bad, Especially for Free! (1987 Yamaha 700)-20141116.mp4.part')
|
||||||
|
self.assertEqual(sanitize_path('abc/def...'), 'abc\\def..#')
|
||||||
|
self.assertEqual(sanitize_path('abc.../def'), 'abc..#\\def')
|
||||||
|
self.assertEqual(sanitize_path('abc.../def...'), 'abc..#\\def..#')
|
||||||
|
|
||||||
|
self.assertEqual(sanitize_path('../abc'), '..\\abc')
|
||||||
|
self.assertEqual(sanitize_path('../../abc'), '..\\..\\abc')
|
||||||
|
self.assertEqual(sanitize_path('./abc'), 'abc')
|
||||||
|
self.assertEqual(sanitize_path('./../abc'), '..\\abc')
|
||||||
|
|
||||||
|
def test_prepend_extension(self):
|
||||||
|
self.assertEqual(prepend_extension('abc.ext', 'temp'), 'abc.temp.ext')
|
||||||
|
self.assertEqual(prepend_extension('abc.ext', 'temp', 'ext'), 'abc.temp.ext')
|
||||||
|
self.assertEqual(prepend_extension('abc.unexpected_ext', 'temp', 'ext'), 'abc.unexpected_ext.temp')
|
||||||
|
self.assertEqual(prepend_extension('abc', 'temp'), 'abc.temp')
|
||||||
|
self.assertEqual(prepend_extension('.abc', 'temp'), '.abc.temp')
|
||||||
|
self.assertEqual(prepend_extension('.abc.ext', 'temp'), '.abc.temp.ext')
|
||||||
|
|
||||||
|
def test_replace_extension(self):
|
||||||
|
self.assertEqual(replace_extension('abc.ext', 'temp'), 'abc.temp')
|
||||||
|
self.assertEqual(replace_extension('abc.ext', 'temp', 'ext'), 'abc.temp')
|
||||||
|
self.assertEqual(replace_extension('abc.unexpected_ext', 'temp', 'ext'), 'abc.unexpected_ext.temp')
|
||||||
|
self.assertEqual(replace_extension('abc', 'temp'), 'abc.temp')
|
||||||
|
self.assertEqual(replace_extension('.abc', 'temp'), '.abc.temp')
|
||||||
|
self.assertEqual(replace_extension('.abc.ext', 'temp'), '.abc.temp')
|
||||||
|
|
||||||
def test_ordered_set(self):
|
def test_ordered_set(self):
|
||||||
self.assertEqual(orderedSet([1, 1, 2, 3, 4, 4, 5, 6, 7, 3, 5]), [1, 2, 3, 4, 5, 6, 7])
|
self.assertEqual(orderedSet([1, 1, 2, 3, 4, 4, 5, 6, 7, 3, 5]), [1, 2, 3, 4, 5, 6, 7])
|
||||||
self.assertEqual(orderedSet([]), [])
|
self.assertEqual(orderedSet([]), [])
|
||||||
@@ -140,6 +200,8 @@ class TestUtil(unittest.TestCase):
|
|||||||
|
|
||||||
def test_unescape_html(self):
|
def test_unescape_html(self):
|
||||||
self.assertEqual(unescapeHTML('%20;'), '%20;')
|
self.assertEqual(unescapeHTML('%20;'), '%20;')
|
||||||
|
self.assertEqual(unescapeHTML('/'), '/')
|
||||||
|
self.assertEqual(unescapeHTML('/'), '/')
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
unescapeHTML('é'), 'é')
|
unescapeHTML('é'), 'é')
|
||||||
|
|
||||||
@@ -165,6 +227,7 @@ class TestUtil(unittest.TestCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
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')
|
||||||
|
|
||||||
def test_find_xpath_attr(self):
|
def test_find_xpath_attr(self):
|
||||||
testxml = '''<root>
|
testxml = '''<root>
|
||||||
@@ -192,6 +255,17 @@ class TestUtil(unittest.TestCase):
|
|||||||
self.assertEqual(find('media:song/media:author').text, 'The Author')
|
self.assertEqual(find('media:song/media:author').text, 'The Author')
|
||||||
self.assertEqual(find('media:song/url').text, 'http://server.com/download.mp3')
|
self.assertEqual(find('media:song/url').text, 'http://server.com/download.mp3')
|
||||||
|
|
||||||
|
def test_xpath_text(self):
|
||||||
|
testxml = '''<root>
|
||||||
|
<div>
|
||||||
|
<p>Foo</p>
|
||||||
|
</div>
|
||||||
|
</root>'''
|
||||||
|
doc = xml.etree.ElementTree.fromstring(testxml)
|
||||||
|
self.assertEqual(xpath_text(doc, 'div/p'), 'Foo')
|
||||||
|
self.assertTrue(xpath_text(doc, 'div/bar') is None)
|
||||||
|
self.assertRaises(ExtractorError, xpath_text, doc, 'div/bar', fatal=True)
|
||||||
|
|
||||||
def test_smuggle_url(self):
|
def test_smuggle_url(self):
|
||||||
data = {"ö": "ö", "abc": [3]}
|
data = {"ö": "ö", "abc": [3]}
|
||||||
url = 'https://foo.bar/baz?x=y#a'
|
url = 'https://foo.bar/baz?x=y#a'
|
||||||
@@ -324,6 +398,10 @@ class TestUtil(unittest.TestCase):
|
|||||||
self.assertEqual(uppercase_escape('aä'), 'aä')
|
self.assertEqual(uppercase_escape('aä'), 'aä')
|
||||||
self.assertEqual(uppercase_escape('\\U0001d550'), '𝕐')
|
self.assertEqual(uppercase_escape('\\U0001d550'), '𝕐')
|
||||||
|
|
||||||
|
def test_lowercase_escape(self):
|
||||||
|
self.assertEqual(lowercase_escape('aä'), 'aä')
|
||||||
|
self.assertEqual(lowercase_escape('\\u0026'), '&')
|
||||||
|
|
||||||
def test_limit_length(self):
|
def test_limit_length(self):
|
||||||
self.assertEqual(limit_length(None, 12), None)
|
self.assertEqual(limit_length(None, 12), None)
|
||||||
self.assertEqual(limit_length('foo', 12), 'foo')
|
self.assertEqual(limit_length('foo', 12), 'foo')
|
||||||
@@ -397,6 +475,12 @@ class TestUtil(unittest.TestCase):
|
|||||||
self.assertEqual(d['x'], 1)
|
self.assertEqual(d['x'], 1)
|
||||||
self.assertEqual(d['y'], 'a')
|
self.assertEqual(d['y'], 'a')
|
||||||
|
|
||||||
|
on = js_to_json('["abc", "def",]')
|
||||||
|
self.assertEqual(json.loads(on), ['abc', 'def'])
|
||||||
|
|
||||||
|
on = js_to_json('{"abc": "def",}')
|
||||||
|
self.assertEqual(json.loads(on), {'abc': 'def'})
|
||||||
|
|
||||||
def test_clean_html(self):
|
def test_clean_html(self):
|
||||||
self.assertEqual(clean_html('a:\nb'), 'a: b')
|
self.assertEqual(clean_html('a:\nb'), 'a: b')
|
||||||
self.assertEqual(clean_html('a:\n "b"'), 'a: "b"')
|
self.assertEqual(clean_html('a:\n "b"'), 'a: "b"')
|
||||||
@@ -501,6 +585,57 @@ ffmpeg version 2.4.4 Copyright (c) 2000-2014 the FFmpeg ...'''), '2.4.4')
|
|||||||
'like_count > 100 & dislike_count <? 50 & description',
|
'like_count > 100 & dislike_count <? 50 & description',
|
||||||
{'like_count': 190, 'dislike_count': 10}))
|
{'like_count': 190, 'dislike_count': 10}))
|
||||||
|
|
||||||
|
def test_parse_dfxp_time_expr(self):
|
||||||
|
self.assertEqual(parse_dfxp_time_expr(None), 0.0)
|
||||||
|
self.assertEqual(parse_dfxp_time_expr(''), 0.0)
|
||||||
|
self.assertEqual(parse_dfxp_time_expr('0.1'), 0.1)
|
||||||
|
self.assertEqual(parse_dfxp_time_expr('0.1s'), 0.1)
|
||||||
|
self.assertEqual(parse_dfxp_time_expr('00:00:01'), 1.0)
|
||||||
|
self.assertEqual(parse_dfxp_time_expr('00:00:01.100'), 1.1)
|
||||||
|
|
||||||
|
def test_dfxp2srt(self):
|
||||||
|
dfxp_data = '''<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<tt xmlns="http://www.w3.org/ns/ttml" xml:lang="en" xmlns:tts="http://www.w3.org/ns/ttml#parameter">
|
||||||
|
<body>
|
||||||
|
<div xml:lang="en">
|
||||||
|
<p begin="0" end="1">The following line contains Chinese characters and special symbols</p>
|
||||||
|
<p begin="1" end="2">第二行<br/>♪♪</p>
|
||||||
|
<p begin="2" dur="1"><span>Third<br/>Line</span></p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</tt>'''
|
||||||
|
srt_data = '''1
|
||||||
|
00:00:00,000 --> 00:00:01,000
|
||||||
|
The following line contains Chinese characters and special symbols
|
||||||
|
|
||||||
|
2
|
||||||
|
00:00:01,000 --> 00:00:02,000
|
||||||
|
第二行
|
||||||
|
♪♪
|
||||||
|
|
||||||
|
3
|
||||||
|
00:00:02,000 --> 00:00:03,000
|
||||||
|
Third
|
||||||
|
Line
|
||||||
|
|
||||||
|
'''
|
||||||
|
self.assertEqual(dfxp2srt(dfxp_data), srt_data)
|
||||||
|
|
||||||
|
dfxp_data_no_default_namespace = '''<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<tt xml:lang="en" xmlns:tts="http://www.w3.org/ns/ttml#parameter">
|
||||||
|
<body>
|
||||||
|
<div xml:lang="en">
|
||||||
|
<p begin="0" end="1">The first line</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</tt>'''
|
||||||
|
srt_data = '''1
|
||||||
|
00:00:00,000 --> 00:00:01,000
|
||||||
|
The first line
|
||||||
|
|
||||||
|
'''
|
||||||
|
self.assertEqual(dfxp2srt(dfxp_data_no_default_namespace), srt_data)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
9
tox.ini
9
tox.ini
@@ -1,8 +1,13 @@
|
|||||||
[tox]
|
[tox]
|
||||||
envlist = py26,py27,py33
|
envlist = py26,py27,py33,py34
|
||||||
[testenv]
|
[testenv]
|
||||||
deps =
|
deps =
|
||||||
nose
|
nose
|
||||||
coverage
|
coverage
|
||||||
commands = nosetests --verbose {posargs:test} # --with-coverage --cover-package=youtube_dl --cover-html
|
# We need a valid $HOME for test_compat_expanduser
|
||||||
|
passenv = HOME
|
||||||
|
defaultargs = test --exclude test_download.py --exclude test_age_restriction.py
|
||||||
|
--exclude test_subtitles.py --exclude test_write_annotations.py
|
||||||
|
--exclude test_youtube_lists.py
|
||||||
|
commands = nosetests --verbose {posargs:{[testenv]defaultargs}} # --with-coverage --cover-package=youtube_dl --cover-html
|
||||||
# test.test_download:TestDownload.test_NowVideo
|
# test.test_download:TestDownload.test_NowVideo
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ from .utils import (
|
|||||||
ExtractorError,
|
ExtractorError,
|
||||||
format_bytes,
|
format_bytes,
|
||||||
formatSeconds,
|
formatSeconds,
|
||||||
|
HEADRequest,
|
||||||
locked_file,
|
locked_file,
|
||||||
make_HTTPS_handler,
|
make_HTTPS_handler,
|
||||||
MaxDownloadsReached,
|
MaxDownloadsReached,
|
||||||
@@ -61,9 +62,9 @@ from .utils import (
|
|||||||
render_table,
|
render_table,
|
||||||
SameFileError,
|
SameFileError,
|
||||||
sanitize_filename,
|
sanitize_filename,
|
||||||
|
sanitize_path,
|
||||||
std_headers,
|
std_headers,
|
||||||
subtitles_filename,
|
subtitles_filename,
|
||||||
takewhile_inclusive,
|
|
||||||
UnavailableVideoError,
|
UnavailableVideoError,
|
||||||
url_basename,
|
url_basename,
|
||||||
version_tuple,
|
version_tuple,
|
||||||
@@ -71,6 +72,7 @@ from .utils import (
|
|||||||
write_string,
|
write_string,
|
||||||
YoutubeDLHandler,
|
YoutubeDLHandler,
|
||||||
prepend_extension,
|
prepend_extension,
|
||||||
|
replace_extension,
|
||||||
args_to_str,
|
args_to_str,
|
||||||
age_restricted,
|
age_restricted,
|
||||||
)
|
)
|
||||||
@@ -117,7 +119,7 @@ class YoutubeDL(object):
|
|||||||
|
|
||||||
username: Username for authentication purposes.
|
username: Username for authentication purposes.
|
||||||
password: Password for authentication purposes.
|
password: Password for authentication purposes.
|
||||||
videopassword: Password for acces a video.
|
videopassword: Password for accessing a video.
|
||||||
usenetrc: Use netrc for authentication instead.
|
usenetrc: Use netrc for authentication instead.
|
||||||
verbose: Print additional info to stdout.
|
verbose: Print additional info to stdout.
|
||||||
quiet: Do not print messages to stdout.
|
quiet: Do not print messages to stdout.
|
||||||
@@ -134,10 +136,10 @@ class YoutubeDL(object):
|
|||||||
(or video) as a single JSON line.
|
(or video) as a single JSON line.
|
||||||
simulate: Do not download the video files.
|
simulate: Do not download the video files.
|
||||||
format: Video format code. See options.py for more information.
|
format: Video format code. See options.py for more information.
|
||||||
format_limit: Highest quality format to try.
|
|
||||||
outtmpl: Template for output names.
|
outtmpl: Template for output names.
|
||||||
restrictfilenames: Do not allow "&" and spaces in file names
|
restrictfilenames: Do not allow "&" and spaces in file names
|
||||||
ignoreerrors: Do not stop on download errors.
|
ignoreerrors: Do not stop on download errors.
|
||||||
|
force_generic_extractor: Force downloader to use the generic extractor
|
||||||
nooverwrites: Prevent overwriting files.
|
nooverwrites: Prevent overwriting files.
|
||||||
playliststart: Playlist item to start at.
|
playliststart: Playlist item to start at.
|
||||||
playlistend: Playlist item to end at.
|
playlistend: Playlist item to end at.
|
||||||
@@ -260,7 +262,8 @@ class YoutubeDL(object):
|
|||||||
The following options are used by the post processors:
|
The following options are used by the post processors:
|
||||||
prefer_ffmpeg: If True, use ffmpeg instead of avconv if both are available,
|
prefer_ffmpeg: If True, use ffmpeg instead of avconv if both are available,
|
||||||
otherwise prefer avconv.
|
otherwise prefer avconv.
|
||||||
exec_cmd: Arbitrary command to run after downloading
|
postprocessor_args: A list of additional command-line arguments for the
|
||||||
|
postprocessor.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
params = None
|
params = None
|
||||||
@@ -322,8 +325,10 @@ class YoutubeDL(object):
|
|||||||
'Set the LC_ALL environment variable to fix this.')
|
'Set the LC_ALL environment variable to fix this.')
|
||||||
self.params['restrictfilenames'] = True
|
self.params['restrictfilenames'] = True
|
||||||
|
|
||||||
if '%(stitle)s' in self.params.get('outtmpl', ''):
|
if isinstance(params.get('outtmpl'), bytes):
|
||||||
self.report_warning('%(stitle)s is deprecated. Use the %(title)s and the --restrict-filenames flag(which also secures %(uploader)s et al) instead.')
|
self.report_warning(
|
||||||
|
'Parameter outtmpl is bytes, but should be a unicode string. '
|
||||||
|
'Put from __future__ import unicode_literals at the top of your code file or consider switching to Python 3.x.')
|
||||||
|
|
||||||
self._setup_opener()
|
self._setup_opener()
|
||||||
|
|
||||||
@@ -562,7 +567,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 = self.params.get('outtmpl', DEFAULT_OUTTMPL)
|
outtmpl = sanitize_path(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
|
||||||
@@ -624,13 +629,16 @@ class YoutubeDL(object):
|
|||||||
info_dict.setdefault(key, value)
|
info_dict.setdefault(key, value)
|
||||||
|
|
||||||
def extract_info(self, url, download=True, ie_key=None, extra_info={},
|
def extract_info(self, url, download=True, ie_key=None, extra_info={},
|
||||||
process=True):
|
process=True, force_generic_extractor=False):
|
||||||
'''
|
'''
|
||||||
Returns a list with a dictionary for each video we find.
|
Returns a list with a dictionary for each video we find.
|
||||||
If 'download', also downloads the videos.
|
If 'download', also downloads the videos.
|
||||||
extra_info is a dict containing the extra values to add to each result
|
extra_info is a dict containing the extra values to add to each result
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
if not ie_key and force_generic_extractor:
|
||||||
|
ie_key = 'Generic'
|
||||||
|
|
||||||
if ie_key:
|
if ie_key:
|
||||||
ies = [self.get_info_extractor(ie_key)]
|
ies = [self.get_info_extractor(ie_key)]
|
||||||
else:
|
else:
|
||||||
@@ -758,7 +766,9 @@ class YoutubeDL(object):
|
|||||||
if isinstance(ie_entries, list):
|
if isinstance(ie_entries, list):
|
||||||
n_all_entries = len(ie_entries)
|
n_all_entries = len(ie_entries)
|
||||||
if playlistitems:
|
if playlistitems:
|
||||||
entries = [ie_entries[i - 1] for i in playlistitems]
|
entries = [
|
||||||
|
ie_entries[i - 1] for i in playlistitems
|
||||||
|
if -n_all_entries <= i - 1 < n_all_entries]
|
||||||
else:
|
else:
|
||||||
entries = ie_entries[playliststart:playlistend]
|
entries = ie_entries[playliststart:playlistend]
|
||||||
n_entries = len(entries)
|
n_entries = len(entries)
|
||||||
@@ -913,10 +923,17 @@ class YoutubeDL(object):
|
|||||||
if not available_formats:
|
if not available_formats:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if format_spec == 'best' or format_spec is None:
|
if format_spec in ['best', 'worst', None]:
|
||||||
return available_formats[-1]
|
format_idx = 0 if format_spec == 'worst' else -1
|
||||||
elif format_spec == 'worst':
|
audiovideo_formats = [
|
||||||
return available_formats[0]
|
f for f in available_formats
|
||||||
|
if f.get('vcodec') != 'none' and f.get('acodec') != 'none']
|
||||||
|
if audiovideo_formats:
|
||||||
|
return audiovideo_formats[format_idx]
|
||||||
|
# for audio only (soundcloud) or video only (imgur) urls, select the best/worst audio format
|
||||||
|
elif (all(f.get('acodec') != 'none' for f in available_formats) or
|
||||||
|
all(f.get('vcodec') != 'none' for f in available_formats)):
|
||||||
|
return available_formats[format_idx]
|
||||||
elif format_spec == 'bestaudio':
|
elif format_spec == 'bestaudio':
|
||||||
audio_formats = [
|
audio_formats = [
|
||||||
f for f in available_formats
|
f for f in available_formats
|
||||||
@@ -993,7 +1010,7 @@ class YoutubeDL(object):
|
|||||||
t.get('preference'), t.get('width'), t.get('height'),
|
t.get('preference'), t.get('width'), t.get('height'),
|
||||||
t.get('id'), t.get('url')))
|
t.get('id'), t.get('url')))
|
||||||
for i, t in enumerate(thumbnails):
|
for i, t in enumerate(thumbnails):
|
||||||
if 'width' in t and 'height' in t:
|
if t.get('width') and t.get('height'):
|
||||||
t['resolution'] = '%dx%d' % (t['width'], t['height'])
|
t['resolution'] = '%dx%d' % (t['width'], t['height'])
|
||||||
if t.get('id') is None:
|
if t.get('id') is None:
|
||||||
t['id'] = '%d' % i
|
t['id'] = '%d' % i
|
||||||
@@ -1005,13 +1022,13 @@ class YoutubeDL(object):
|
|||||||
info_dict['display_id'] = info_dict['id']
|
info_dict['display_id'] = info_dict['id']
|
||||||
|
|
||||||
if info_dict.get('upload_date') is None and info_dict.get('timestamp') is not None:
|
if info_dict.get('upload_date') is None and info_dict.get('timestamp') is not None:
|
||||||
# Working around negative timestamps in Windows
|
# Working around out-of-range timestamp values (e.g. negative ones on Windows,
|
||||||
# (see http://bugs.python.org/issue1646728)
|
# see http://bugs.python.org/issue1646728)
|
||||||
if info_dict['timestamp'] < 0 and os.name == 'nt':
|
try:
|
||||||
info_dict['timestamp'] = 0
|
upload_date = datetime.datetime.utcfromtimestamp(info_dict['timestamp'])
|
||||||
upload_date = datetime.datetime.utcfromtimestamp(
|
|
||||||
info_dict['timestamp'])
|
|
||||||
info_dict['upload_date'] = upload_date.strftime('%Y%m%d')
|
info_dict['upload_date'] = upload_date.strftime('%Y%m%d')
|
||||||
|
except (ValueError, OverflowError, OSError):
|
||||||
|
pass
|
||||||
|
|
||||||
if self.params.get('listsubtitles', False):
|
if self.params.get('listsubtitles', False):
|
||||||
if 'automatic_captions' in info_dict:
|
if 'automatic_captions' in info_dict:
|
||||||
@@ -1022,12 +1039,6 @@ class YoutubeDL(object):
|
|||||||
info_dict['id'], info_dict.get('subtitles'),
|
info_dict['id'], info_dict.get('subtitles'),
|
||||||
info_dict.get('automatic_captions'))
|
info_dict.get('automatic_captions'))
|
||||||
|
|
||||||
# This extractors handle format selection themselves
|
|
||||||
if info_dict['extractor'] in ['Youku']:
|
|
||||||
if download:
|
|
||||||
self.process_info(info_dict)
|
|
||||||
return info_dict
|
|
||||||
|
|
||||||
# We now pick which formats have to be downloaded
|
# We now pick which formats have to be downloaded
|
||||||
if info_dict.get('formats') is None:
|
if info_dict.get('formats') is None:
|
||||||
# There's only one format available
|
# There's only one format available
|
||||||
@@ -1038,6 +1049,8 @@ class YoutubeDL(object):
|
|||||||
if not formats:
|
if not formats:
|
||||||
raise ExtractorError('No video formats found!')
|
raise ExtractorError('No video formats found!')
|
||||||
|
|
||||||
|
formats_dict = {}
|
||||||
|
|
||||||
# We check that all the formats have the format and format_id fields
|
# We check that all the formats have the format and format_id fields
|
||||||
for i, format in enumerate(formats):
|
for i, format in enumerate(formats):
|
||||||
if 'url' not in format:
|
if 'url' not in format:
|
||||||
@@ -1045,6 +1058,18 @@ class YoutubeDL(object):
|
|||||||
|
|
||||||
if format.get('format_id') is None:
|
if format.get('format_id') is None:
|
||||||
format['format_id'] = compat_str(i)
|
format['format_id'] = compat_str(i)
|
||||||
|
format_id = format['format_id']
|
||||||
|
if format_id not in formats_dict:
|
||||||
|
formats_dict[format_id] = []
|
||||||
|
formats_dict[format_id].append(format)
|
||||||
|
|
||||||
|
# Make sure all formats have unique format_id
|
||||||
|
for format_id, ambiguous_formats in formats_dict.items():
|
||||||
|
if len(ambiguous_formats) > 1:
|
||||||
|
for i, format in enumerate(ambiguous_formats):
|
||||||
|
format['format_id'] = '%s-%d' % (format_id, i)
|
||||||
|
|
||||||
|
for i, format in enumerate(formats):
|
||||||
if format.get('format') is None:
|
if format.get('format') is None:
|
||||||
format['format'] = '{id} - {res}{note}'.format(
|
format['format'] = '{id} - {res}{note}'.format(
|
||||||
id=format['format_id'],
|
id=format['format_id'],
|
||||||
@@ -1060,12 +1085,6 @@ class YoutubeDL(object):
|
|||||||
full_format_info.update(format)
|
full_format_info.update(format)
|
||||||
format['http_headers'] = self._calc_headers(full_format_info)
|
format['http_headers'] = self._calc_headers(full_format_info)
|
||||||
|
|
||||||
format_limit = self.params.get('format_limit', None)
|
|
||||||
if format_limit:
|
|
||||||
formats = list(takewhile_inclusive(
|
|
||||||
lambda f: f['format_id'] != format_limit, formats
|
|
||||||
))
|
|
||||||
|
|
||||||
# TODO Central sorting goes here
|
# TODO Central sorting goes here
|
||||||
|
|
||||||
if formats[0] is not info_dict:
|
if formats[0] is not info_dict:
|
||||||
@@ -1083,10 +1102,16 @@ class YoutubeDL(object):
|
|||||||
|
|
||||||
req_format = self.params.get('format')
|
req_format = self.params.get('format')
|
||||||
if req_format is None:
|
if req_format is None:
|
||||||
req_format = 'best'
|
req_format_list = []
|
||||||
|
if (self.params.get('outtmpl', DEFAULT_OUTTMPL) != '-' and
|
||||||
|
info_dict['extractor'] in ['youtube', 'ted']):
|
||||||
|
merger = FFmpegMergerPP(self)
|
||||||
|
if merger.available and merger.can_merge():
|
||||||
|
req_format_list.append('bestvideo+bestaudio')
|
||||||
|
req_format_list.append('best')
|
||||||
|
req_format = '/'.join(req_format_list)
|
||||||
formats_to_download = []
|
formats_to_download = []
|
||||||
# The -1 is for supporting YoutubeIE
|
if req_format == 'all':
|
||||||
if req_format in ('-1', 'all'):
|
|
||||||
formats_to_download = formats
|
formats_to_download = formats
|
||||||
else:
|
else:
|
||||||
for rfstr in req_format.split(','):
|
for rfstr in req_format.split(','):
|
||||||
@@ -1213,9 +1238,6 @@ class YoutubeDL(object):
|
|||||||
if len(info_dict['title']) > 200:
|
if len(info_dict['title']) > 200:
|
||||||
info_dict['title'] = info_dict['title'][:197] + '...'
|
info_dict['title'] = info_dict['title'][:197] + '...'
|
||||||
|
|
||||||
# Keep for backwards compatibility
|
|
||||||
info_dict['stitle'] = info_dict['title']
|
|
||||||
|
|
||||||
if 'format' not in info_dict:
|
if 'format' not in info_dict:
|
||||||
info_dict['format'] = info_dict['ext']
|
info_dict['format'] = info_dict['ext']
|
||||||
|
|
||||||
@@ -1261,7 +1283,7 @@ class YoutubeDL(object):
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
dn = os.path.dirname(encodeFilename(filename))
|
dn = os.path.dirname(sanitize_path(encodeFilename(filename)))
|
||||||
if dn and not os.path.exists(dn):
|
if dn and not os.path.exists(dn):
|
||||||
os.makedirs(dn)
|
os.makedirs(dn)
|
||||||
except (OSError, IOError) as err:
|
except (OSError, IOError) as err:
|
||||||
@@ -1269,7 +1291,7 @@ class YoutubeDL(object):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if self.params.get('writedescription', False):
|
if self.params.get('writedescription', False):
|
||||||
descfn = filename + '.description'
|
descfn = replace_extension(filename, 'description', info_dict.get('ext'))
|
||||||
if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(descfn)):
|
if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(descfn)):
|
||||||
self.to_screen('[info] Video description is already present')
|
self.to_screen('[info] Video description is already present')
|
||||||
elif info_dict.get('description') is None:
|
elif info_dict.get('description') is None:
|
||||||
@@ -1284,7 +1306,7 @@ class YoutubeDL(object):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if self.params.get('writeannotations', False):
|
if self.params.get('writeannotations', False):
|
||||||
annofn = filename + '.annotations.xml'
|
annofn = replace_extension(filename, 'annotations.xml', info_dict.get('ext'))
|
||||||
if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(annofn)):
|
if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(annofn)):
|
||||||
self.to_screen('[info] Video annotations are already present')
|
self.to_screen('[info] Video annotations are already present')
|
||||||
else:
|
else:
|
||||||
@@ -1331,13 +1353,13 @@ class YoutubeDL(object):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if self.params.get('writeinfojson', False):
|
if self.params.get('writeinfojson', False):
|
||||||
infofn = os.path.splitext(filename)[0] + '.info.json'
|
infofn = replace_extension(filename, 'info.json', info_dict.get('ext'))
|
||||||
if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(infofn)):
|
if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(infofn)):
|
||||||
self.to_screen('[info] Video description metadata is already present')
|
self.to_screen('[info] Video description metadata is already present')
|
||||||
else:
|
else:
|
||||||
self.to_screen('[info] Writing video description metadata as JSON to: ' + infofn)
|
self.to_screen('[info] Writing video description metadata as JSON to: ' + infofn)
|
||||||
try:
|
try:
|
||||||
write_json_file(info_dict, infofn)
|
write_json_file(self.filter_requested_info(info_dict), infofn)
|
||||||
except (OSError, IOError):
|
except (OSError, IOError):
|
||||||
self.report_error('Cannot write metadata to JSON file ' + infofn)
|
self.report_error('Cannot write metadata to JSON file ' + infofn)
|
||||||
return
|
return
|
||||||
@@ -1357,19 +1379,52 @@ class YoutubeDL(object):
|
|||||||
if info_dict.get('requested_formats') is not None:
|
if info_dict.get('requested_formats') is not None:
|
||||||
downloaded = []
|
downloaded = []
|
||||||
success = True
|
success = True
|
||||||
merger = FFmpegMergerPP(self, not self.params.get('keepvideo'))
|
merger = FFmpegMergerPP(self)
|
||||||
if not merger.available:
|
if not merger.available:
|
||||||
postprocessors = []
|
postprocessors = []
|
||||||
self.report_warning('You have requested multiple '
|
self.report_warning('You have requested multiple '
|
||||||
'formats but ffmpeg or avconv are not installed.'
|
'formats but ffmpeg or avconv are not installed.'
|
||||||
' The formats won\'t be merged')
|
' The formats won\'t be merged.')
|
||||||
else:
|
else:
|
||||||
postprocessors = [merger]
|
postprocessors = [merger]
|
||||||
for f in info_dict['requested_formats']:
|
|
||||||
|
def compatible_formats(formats):
|
||||||
|
video, audio = formats
|
||||||
|
# Check extension
|
||||||
|
video_ext, audio_ext = audio.get('ext'), video.get('ext')
|
||||||
|
if video_ext and audio_ext:
|
||||||
|
COMPATIBLE_EXTS = (
|
||||||
|
('mp3', 'mp4', 'm4a', 'm4p', 'm4b', 'm4r', 'm4v'),
|
||||||
|
('webm')
|
||||||
|
)
|
||||||
|
for exts in COMPATIBLE_EXTS:
|
||||||
|
if video_ext in exts and audio_ext in exts:
|
||||||
|
return True
|
||||||
|
# TODO: Check acodec/vcodec
|
||||||
|
return False
|
||||||
|
|
||||||
|
filename_real_ext = os.path.splitext(filename)[1][1:]
|
||||||
|
filename_wo_ext = (
|
||||||
|
os.path.splitext(filename)[0]
|
||||||
|
if filename_real_ext == info_dict['ext']
|
||||||
|
else filename)
|
||||||
|
requested_formats = info_dict['requested_formats']
|
||||||
|
if self.params.get('merge_output_format') is None and not compatible_formats(requested_formats):
|
||||||
|
info_dict['ext'] = 'mkv'
|
||||||
|
self.report_warning(
|
||||||
|
'Requested formats are incompatible for merge and will be merged into mkv.')
|
||||||
|
# Ensure filename always has a correct extension for successful merge
|
||||||
|
filename = '%s.%s' % (filename_wo_ext, info_dict['ext'])
|
||||||
|
if os.path.exists(encodeFilename(filename)):
|
||||||
|
self.to_screen(
|
||||||
|
'[download] %s has already been downloaded and '
|
||||||
|
'merged' % filename)
|
||||||
|
else:
|
||||||
|
for f in requested_formats:
|
||||||
new_info = dict(info_dict)
|
new_info = dict(info_dict)
|
||||||
new_info.update(f)
|
new_info.update(f)
|
||||||
fname = self.prepare_filename(new_info)
|
fname = self.prepare_filename(new_info)
|
||||||
fname = prepend_extension(fname, 'f%s' % f['format_id'])
|
fname = prepend_extension(fname, 'f%s' % f['format_id'], new_info['ext'])
|
||||||
downloaded.append(fname)
|
downloaded.append(fname)
|
||||||
partial_success = dl(fname, new_info)
|
partial_success = dl(fname, new_info)
|
||||||
success = success and partial_success
|
success = success and partial_success
|
||||||
@@ -1444,7 +1499,8 @@ class YoutubeDL(object):
|
|||||||
for url in url_list:
|
for url in url_list:
|
||||||
try:
|
try:
|
||||||
# It also downloads the videos
|
# It also downloads the videos
|
||||||
res = self.extract_info(url)
|
res = self.extract_info(
|
||||||
|
url, force_generic_extractor=self.params.get('force_generic_extractor', False))
|
||||||
except UnavailableVideoError:
|
except UnavailableVideoError:
|
||||||
self.report_error('unable to download video')
|
self.report_error('unable to download video')
|
||||||
except MaxDownloadsReached:
|
except MaxDownloadsReached:
|
||||||
@@ -1461,7 +1517,7 @@ class YoutubeDL(object):
|
|||||||
[info_filename], mode='r',
|
[info_filename], mode='r',
|
||||||
openhook=fileinput.hook_encoded('utf-8'))) as f:
|
openhook=fileinput.hook_encoded('utf-8'))) as f:
|
||||||
# FileInput doesn't have a read method, we can't call json.load
|
# FileInput doesn't have a read method, we can't call json.load
|
||||||
info = json.loads('\n'.join(f))
|
info = self.filter_requested_info(json.loads('\n'.join(f)))
|
||||||
try:
|
try:
|
||||||
self.process_ie_result(info, download=True)
|
self.process_ie_result(info, download=True)
|
||||||
except DownloadError:
|
except DownloadError:
|
||||||
@@ -1473,6 +1529,12 @@ class YoutubeDL(object):
|
|||||||
raise
|
raise
|
||||||
return self._download_retcode
|
return self._download_retcode
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def filter_requested_info(info_dict):
|
||||||
|
return dict(
|
||||||
|
(k, v) for k, v in info_dict.items()
|
||||||
|
if k not in ['requested_formats', 'requested_subtitles'])
|
||||||
|
|
||||||
def post_process(self, filename, ie_info):
|
def post_process(self, filename, ie_info):
|
||||||
"""Run all the postprocessors on the given file."""
|
"""Run all the postprocessors on the given file."""
|
||||||
info = dict(ie_info)
|
info = dict(ie_info)
|
||||||
@@ -1482,24 +1544,18 @@ class YoutubeDL(object):
|
|||||||
pps_chain.extend(ie_info['__postprocessors'])
|
pps_chain.extend(ie_info['__postprocessors'])
|
||||||
pps_chain.extend(self._pps)
|
pps_chain.extend(self._pps)
|
||||||
for pp in pps_chain:
|
for pp in pps_chain:
|
||||||
keep_video = None
|
files_to_delete = []
|
||||||
old_filename = info['filepath']
|
|
||||||
try:
|
try:
|
||||||
keep_video_wish, info = pp.run(info)
|
files_to_delete, info = pp.run(info)
|
||||||
if keep_video_wish is not None:
|
|
||||||
if keep_video_wish:
|
|
||||||
keep_video = keep_video_wish
|
|
||||||
elif keep_video is None:
|
|
||||||
# No clear decision yet, let IE decide
|
|
||||||
keep_video = keep_video_wish
|
|
||||||
except PostProcessingError as e:
|
except PostProcessingError as e:
|
||||||
self.report_error(e.msg)
|
self.report_error(e.msg)
|
||||||
if keep_video is False and not self.params.get('keepvideo', False):
|
if files_to_delete and not self.params.get('keepvideo', False):
|
||||||
try:
|
for old_filename in files_to_delete:
|
||||||
self.to_screen('Deleting original file %s (pass -k to keep)' % old_filename)
|
self.to_screen('Deleting original file %s (pass -k to keep)' % old_filename)
|
||||||
|
try:
|
||||||
os.remove(encodeFilename(old_filename))
|
os.remove(encodeFilename(old_filename))
|
||||||
except (IOError, OSError):
|
except (IOError, OSError):
|
||||||
self.report_warning('Unable to remove downloaded video file')
|
self.report_warning('Unable to remove downloaded original file')
|
||||||
|
|
||||||
def _make_archive_id(self, info_dict):
|
def _make_archive_id(self, info_dict):
|
||||||
# Future-proof against any change in case
|
# Future-proof against any change in case
|
||||||
@@ -1667,7 +1723,8 @@ class YoutubeDL(object):
|
|||||||
if req_is_string:
|
if req_is_string:
|
||||||
req = url_escaped
|
req = url_escaped
|
||||||
else:
|
else:
|
||||||
req = compat_urllib_request.Request(
|
req_type = HEADRequest if req.get_method() == 'HEAD' else compat_urllib_request.Request
|
||||||
|
req = req_type(
|
||||||
url_escaped, data=req.data, headers=req.headers,
|
url_escaped, data=req.data, headers=req.headers,
|
||||||
origin_req_host=req.origin_req_host, unverifiable=req.unverifiable)
|
origin_req_host=req.origin_req_host, unverifiable=req.unverifiable)
|
||||||
|
|
||||||
@@ -1702,10 +1759,10 @@ class YoutubeDL(object):
|
|||||||
out = out.decode().strip()
|
out = out.decode().strip()
|
||||||
if re.match('[0-9a-f]+', out):
|
if re.match('[0-9a-f]+', out):
|
||||||
self._write_string('[debug] Git HEAD: ' + out + '\n')
|
self._write_string('[debug] Git HEAD: ' + out + '\n')
|
||||||
except:
|
except Exception:
|
||||||
try:
|
try:
|
||||||
sys.exc_clear()
|
sys.exc_clear()
|
||||||
except:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
self._write_string('[debug] Python version %s - %s\n' % (
|
self._write_string('[debug] Python version %s - %s\n' % (
|
||||||
platform.python_version(), platform_name()))
|
platform.python_version(), platform_name()))
|
||||||
@@ -1813,7 +1870,7 @@ class YoutubeDL(object):
|
|||||||
thumb_ext = determine_ext(t['url'], 'jpg')
|
thumb_ext = determine_ext(t['url'], 'jpg')
|
||||||
suffix = '_%s' % t['id'] if len(thumbnails) > 1 else ''
|
suffix = '_%s' % t['id'] if len(thumbnails) > 1 else ''
|
||||||
thumb_display_id = '%s ' % t['id'] if len(thumbnails) > 1 else ''
|
thumb_display_id = '%s ' % t['id'] if len(thumbnails) > 1 else ''
|
||||||
thumb_filename = os.path.splitext(filename)[0] + suffix + '.' + thumb_ext
|
t['filename'] = thumb_filename = os.path.splitext(filename)[0] + suffix + '.' + thumb_ext
|
||||||
|
|
||||||
if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(thumb_filename)):
|
if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(thumb_filename)):
|
||||||
self.to_screen('[%s] %s: Thumbnail %sis already present' %
|
self.to_screen('[%s] %s: Thumbnail %sis already present' %
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ def _real_main(argv=None):
|
|||||||
if not opts.audioquality.isdigit():
|
if not opts.audioquality.isdigit():
|
||||||
parser.error('invalid audio quality specified')
|
parser.error('invalid audio quality specified')
|
||||||
if opts.recodevideo is not None:
|
if opts.recodevideo is not None:
|
||||||
if opts.recodevideo not in ['mp4', 'flv', 'webm', 'ogg', 'mkv']:
|
if opts.recodevideo not in ['mp4', 'flv', 'webm', 'ogg', 'mkv', 'avi']:
|
||||||
parser.error('invalid video recode format specified')
|
parser.error('invalid video recode format specified')
|
||||||
if opts.convertsubtitles is not None:
|
if opts.convertsubtitles is not None:
|
||||||
if opts.convertsubtitles not in ['srt', 'vtt', 'ass']:
|
if opts.convertsubtitles not in ['srt', 'vtt', 'ass']:
|
||||||
@@ -189,10 +189,6 @@ def _real_main(argv=None):
|
|||||||
if opts.allsubtitles and not opts.writeautomaticsub:
|
if opts.allsubtitles and not opts.writeautomaticsub:
|
||||||
opts.writesubtitles = True
|
opts.writesubtitles = True
|
||||||
|
|
||||||
if sys.version_info < (3,):
|
|
||||||
# In Python 2, sys.argv is a bytestring (also note http://bugs.python.org/issue2128 for Windows systems)
|
|
||||||
if opts.outtmpl is not None:
|
|
||||||
opts.outtmpl = opts.outtmpl.decode(preferredencoding())
|
|
||||||
outtmpl = ((opts.outtmpl is not None and opts.outtmpl) or
|
outtmpl = ((opts.outtmpl is not None and opts.outtmpl) or
|
||||||
(opts.format == '-1' and opts.usetitle and '%(title)s-%(id)s-%(format)s.%(ext)s') or
|
(opts.format == '-1' and opts.usetitle and '%(title)s-%(id)s-%(format)s.%(ext)s') or
|
||||||
(opts.format == '-1' and '%(id)s-%(format)s.%(ext)s') or
|
(opts.format == '-1' and '%(id)s-%(format)s.%(ext)s') or
|
||||||
@@ -213,6 +209,11 @@ def _real_main(argv=None):
|
|||||||
# PostProcessors
|
# PostProcessors
|
||||||
postprocessors = []
|
postprocessors = []
|
||||||
# Add the metadata pp first, the other pps will copy it
|
# Add the metadata pp first, the other pps will copy it
|
||||||
|
if opts.metafromtitle:
|
||||||
|
postprocessors.append({
|
||||||
|
'key': 'MetadataFromTitle',
|
||||||
|
'titleformat': opts.metafromtitle
|
||||||
|
})
|
||||||
if opts.addmetadata:
|
if opts.addmetadata:
|
||||||
postprocessors.append({'key': 'FFmpegMetadata'})
|
postprocessors.append({'key': 'FFmpegMetadata'})
|
||||||
if opts.extractaudio:
|
if opts.extractaudio:
|
||||||
@@ -239,15 +240,18 @@ def _real_main(argv=None):
|
|||||||
if opts.xattrs:
|
if opts.xattrs:
|
||||||
postprocessors.append({'key': 'XAttrMetadata'})
|
postprocessors.append({'key': 'XAttrMetadata'})
|
||||||
if opts.embedthumbnail:
|
if opts.embedthumbnail:
|
||||||
if not opts.addmetadata:
|
already_have_thumbnail = opts.writethumbnail or opts.write_all_thumbnails
|
||||||
postprocessors.append({'key': 'FFmpegAudioFix'})
|
postprocessors.append({
|
||||||
postprocessors.append({'key': 'AtomicParsley'})
|
'key': 'EmbedThumbnail',
|
||||||
|
'already_have_thumbnail': already_have_thumbnail
|
||||||
|
})
|
||||||
|
if not already_have_thumbnail:
|
||||||
|
opts.writethumbnail = True
|
||||||
# Please keep ExecAfterDownload towards the bottom as it allows the user to modify the final file in any way.
|
# Please keep ExecAfterDownload towards the bottom as it allows the user to modify the final file in any way.
|
||||||
# So if the user is able to remove the file before your postprocessor runs it might cause a few problems.
|
# So if the user is able to remove the file before your postprocessor runs it might cause a few problems.
|
||||||
if opts.exec_cmd:
|
if opts.exec_cmd:
|
||||||
postprocessors.append({
|
postprocessors.append({
|
||||||
'key': 'ExecAfterDownload',
|
'key': 'ExecAfterDownload',
|
||||||
'verboseOutput': opts.verbose,
|
|
||||||
'exec_cmd': opts.exec_cmd,
|
'exec_cmd': opts.exec_cmd,
|
||||||
})
|
})
|
||||||
if opts.xattr_set_filesize:
|
if opts.xattr_set_filesize:
|
||||||
@@ -259,6 +263,9 @@ def _real_main(argv=None):
|
|||||||
external_downloader_args = None
|
external_downloader_args = None
|
||||||
if opts.external_downloader_args:
|
if opts.external_downloader_args:
|
||||||
external_downloader_args = shlex.split(opts.external_downloader_args)
|
external_downloader_args = shlex.split(opts.external_downloader_args)
|
||||||
|
postprocessor_args = None
|
||||||
|
if opts.postprocessor_args:
|
||||||
|
postprocessor_args = shlex.split(opts.postprocessor_args)
|
||||||
match_filter = (
|
match_filter = (
|
||||||
None if opts.match_filter is None
|
None if opts.match_filter is None
|
||||||
else match_filter_func(opts.match_filter))
|
else match_filter_func(opts.match_filter))
|
||||||
@@ -284,12 +291,12 @@ def _real_main(argv=None):
|
|||||||
'simulate': opts.simulate or any_getting,
|
'simulate': opts.simulate or any_getting,
|
||||||
'skip_download': opts.skip_download,
|
'skip_download': opts.skip_download,
|
||||||
'format': opts.format,
|
'format': opts.format,
|
||||||
'format_limit': opts.format_limit,
|
|
||||||
'listformats': opts.listformats,
|
'listformats': opts.listformats,
|
||||||
'outtmpl': outtmpl,
|
'outtmpl': outtmpl,
|
||||||
'autonumber_size': opts.autonumber_size,
|
'autonumber_size': opts.autonumber_size,
|
||||||
'restrictfilenames': opts.restrictfilenames,
|
'restrictfilenames': opts.restrictfilenames,
|
||||||
'ignoreerrors': opts.ignoreerrors,
|
'ignoreerrors': opts.ignoreerrors,
|
||||||
|
'force_generic_extractor': opts.force_generic_extractor,
|
||||||
'ratelimit': opts.ratelimit,
|
'ratelimit': opts.ratelimit,
|
||||||
'nooverwrites': opts.nooverwrites,
|
'nooverwrites': opts.nooverwrites,
|
||||||
'retries': opts_retries,
|
'retries': opts_retries,
|
||||||
@@ -347,7 +354,6 @@ def _real_main(argv=None):
|
|||||||
'default_search': opts.default_search,
|
'default_search': opts.default_search,
|
||||||
'youtube_include_dash_manifest': opts.youtube_include_dash_manifest,
|
'youtube_include_dash_manifest': opts.youtube_include_dash_manifest,
|
||||||
'encoding': opts.encoding,
|
'encoding': opts.encoding,
|
||||||
'exec_cmd': opts.exec_cmd,
|
|
||||||
'extract_flat': opts.extract_flat,
|
'extract_flat': opts.extract_flat,
|
||||||
'merge_output_format': opts.merge_output_format,
|
'merge_output_format': opts.merge_output_format,
|
||||||
'postprocessors': postprocessors,
|
'postprocessors': postprocessors,
|
||||||
@@ -364,6 +370,7 @@ def _real_main(argv=None):
|
|||||||
'ffmpeg_location': opts.ffmpeg_location,
|
'ffmpeg_location': opts.ffmpeg_location,
|
||||||
'hls_prefer_native': opts.hls_prefer_native,
|
'hls_prefer_native': opts.hls_prefer_native,
|
||||||
'external_downloader_args': external_downloader_args,
|
'external_downloader_args': external_downloader_args,
|
||||||
|
'postprocessor_args': postprocessor_args,
|
||||||
'cn_verification_proxy': opts.cn_verification_proxy,
|
'cn_verification_proxy': opts.cn_verification_proxy,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ def aes_decrypt_text(data, password, key_size_bytes):
|
|||||||
"""
|
"""
|
||||||
NONCE_LENGTH_BYTES = 8
|
NONCE_LENGTH_BYTES = 8
|
||||||
|
|
||||||
data = bytes_to_intlist(base64.b64decode(data))
|
data = bytes_to_intlist(base64.b64decode(data.encode('utf-8')))
|
||||||
password = bytes_to_intlist(password.encode('utf-8'))
|
password = bytes_to_intlist(password.encode('utf-8'))
|
||||||
|
|
||||||
key = password[:key_size_bytes] + [0] * (key_size_bytes - len(password))
|
key = password[:key_size_bytes] + [0] * (key_size_bytes - len(password))
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import shutil
|
|||||||
import socket
|
import socket
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
import itertools
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -46,11 +47,6 @@ try:
|
|||||||
except ImportError: # Python 2
|
except ImportError: # Python 2
|
||||||
import htmlentitydefs as compat_html_entities
|
import htmlentitydefs as compat_html_entities
|
||||||
|
|
||||||
try:
|
|
||||||
import html.parser as compat_html_parser
|
|
||||||
except ImportError: # Python 2
|
|
||||||
import HTMLParser as compat_html_parser
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import http.client as compat_http_client
|
import http.client as compat_http_client
|
||||||
except ImportError: # Python 2
|
except ImportError: # Python 2
|
||||||
@@ -79,42 +75,73 @@ except ImportError:
|
|||||||
import BaseHTTPServer as compat_http_server
|
import BaseHTTPServer as compat_http_server
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
from urllib.parse import unquote_to_bytes as compat_urllib_parse_unquote_to_bytes
|
||||||
from urllib.parse import unquote as compat_urllib_parse_unquote
|
from urllib.parse import unquote as compat_urllib_parse_unquote
|
||||||
except ImportError:
|
from urllib.parse import unquote_plus as compat_urllib_parse_unquote_plus
|
||||||
def compat_urllib_parse_unquote(string, encoding='utf-8', errors='replace'):
|
except ImportError: # Python 2
|
||||||
if string == '':
|
_asciire = re.compile('([\x00-\x7f]+)') if sys.version_info < (2, 7) else compat_urllib_parse._asciire
|
||||||
|
|
||||||
|
# HACK: The following are the correct unquote_to_bytes, unquote and unquote_plus
|
||||||
|
# implementations from cpython 3.4.3's stdlib. Python 2's version
|
||||||
|
# is apparently broken (see https://github.com/rg3/youtube-dl/pull/6244)
|
||||||
|
|
||||||
|
def compat_urllib_parse_unquote_to_bytes(string):
|
||||||
|
"""unquote_to_bytes('abc%20def') -> b'abc def'."""
|
||||||
|
# Note: strings are encoded as UTF-8. This is only an issue if it contains
|
||||||
|
# unescaped non-ASCII characters, which URIs should not.
|
||||||
|
if not string:
|
||||||
|
# Is it a string-like object?
|
||||||
|
string.split
|
||||||
|
return b''
|
||||||
|
if isinstance(string, unicode):
|
||||||
|
string = string.encode('utf-8')
|
||||||
|
bits = string.split(b'%')
|
||||||
|
if len(bits) == 1:
|
||||||
return string
|
return string
|
||||||
res = string.split('%')
|
res = [bits[0]]
|
||||||
if len(res) == 1:
|
append = res.append
|
||||||
|
for item in bits[1:]:
|
||||||
|
try:
|
||||||
|
append(compat_urllib_parse._hextochr[item[:2]])
|
||||||
|
append(item[2:])
|
||||||
|
except KeyError:
|
||||||
|
append(b'%')
|
||||||
|
append(item)
|
||||||
|
return b''.join(res)
|
||||||
|
|
||||||
|
def compat_urllib_parse_unquote(string, encoding='utf-8', errors='replace'):
|
||||||
|
"""Replace %xx escapes by their single-character equivalent. The optional
|
||||||
|
encoding and errors parameters specify how to decode percent-encoded
|
||||||
|
sequences into Unicode characters, as accepted by the bytes.decode()
|
||||||
|
method.
|
||||||
|
By default, percent-encoded sequences are decoded with UTF-8, and invalid
|
||||||
|
sequences are replaced by a placeholder character.
|
||||||
|
|
||||||
|
unquote('abc%20def') -> 'abc def'.
|
||||||
|
"""
|
||||||
|
if '%' not in string:
|
||||||
|
string.split
|
||||||
return string
|
return string
|
||||||
if encoding is None:
|
if encoding is None:
|
||||||
encoding = 'utf-8'
|
encoding = 'utf-8'
|
||||||
if errors is None:
|
if errors is None:
|
||||||
errors = 'replace'
|
errors = 'replace'
|
||||||
# pct_sequence: contiguous sequence of percent-encoded bytes, decoded
|
bits = _asciire.split(string)
|
||||||
pct_sequence = b''
|
res = [bits[0]]
|
||||||
string = res[0]
|
append = res.append
|
||||||
for item in res[1:]:
|
for i in range(1, len(bits), 2):
|
||||||
try:
|
append(compat_urllib_parse_unquote_to_bytes(bits[i]).decode(encoding, errors))
|
||||||
if not item:
|
append(bits[i + 1])
|
||||||
raise ValueError
|
return ''.join(res)
|
||||||
pct_sequence += item[:2].decode('hex')
|
|
||||||
rest = item[2:]
|
def compat_urllib_parse_unquote_plus(string, encoding='utf-8', errors='replace'):
|
||||||
if not rest:
|
"""Like unquote(), but also replace plus signs by spaces, as required for
|
||||||
# This segment was just a single percent-encoded character.
|
unquoting HTML form values.
|
||||||
# May be part of a sequence of code units, so delay decoding.
|
|
||||||
# (Stored in pct_sequence).
|
unquote_plus('%7e/abc+def') -> '~/abc def'
|
||||||
continue
|
"""
|
||||||
except ValueError:
|
string = string.replace('+', ' ')
|
||||||
rest = '%' + item
|
return compat_urllib_parse_unquote(string, encoding, errors)
|
||||||
# Encountered non-percent-encoded characters. Flush the current
|
|
||||||
# pct_sequence.
|
|
||||||
string += pct_sequence.decode(encoding, errors) + rest
|
|
||||||
pct_sequence = b''
|
|
||||||
if pct_sequence:
|
|
||||||
# Flush the final pct_sequence
|
|
||||||
string += pct_sequence.decode(encoding, errors)
|
|
||||||
return string
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
compat_str = unicode # Python 2
|
compat_str = unicode # Python 2
|
||||||
@@ -389,10 +416,19 @@ else:
|
|||||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
out, err = sp.communicate()
|
out, err = sp.communicate()
|
||||||
lines, columns = map(int, out.split())
|
lines, columns = map(int, out.split())
|
||||||
except:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return _terminal_size(columns, lines)
|
return _terminal_size(columns, lines)
|
||||||
|
|
||||||
|
try:
|
||||||
|
itertools.count(start=0, step=1)
|
||||||
|
compat_itertools_count = itertools.count
|
||||||
|
except TypeError: # Python 2.6
|
||||||
|
def compat_itertools_count(start=0, step=1):
|
||||||
|
n = start
|
||||||
|
while True:
|
||||||
|
yield n
|
||||||
|
n += step
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'compat_HTTPError',
|
'compat_HTTPError',
|
||||||
@@ -404,9 +440,9 @@ __all__ = [
|
|||||||
'compat_getenv',
|
'compat_getenv',
|
||||||
'compat_getpass',
|
'compat_getpass',
|
||||||
'compat_html_entities',
|
'compat_html_entities',
|
||||||
'compat_html_parser',
|
|
||||||
'compat_http_client',
|
'compat_http_client',
|
||||||
'compat_http_server',
|
'compat_http_server',
|
||||||
|
'compat_itertools_count',
|
||||||
'compat_kwargs',
|
'compat_kwargs',
|
||||||
'compat_ord',
|
'compat_ord',
|
||||||
'compat_parse_qs',
|
'compat_parse_qs',
|
||||||
@@ -417,6 +453,8 @@ __all__ = [
|
|||||||
'compat_urllib_error',
|
'compat_urllib_error',
|
||||||
'compat_urllib_parse',
|
'compat_urllib_parse',
|
||||||
'compat_urllib_parse_unquote',
|
'compat_urllib_parse_unquote',
|
||||||
|
'compat_urllib_parse_unquote_plus',
|
||||||
|
'compat_urllib_parse_unquote_to_bytes',
|
||||||
'compat_urllib_parse_urlparse',
|
'compat_urllib_parse_urlparse',
|
||||||
'compat_urllib_request',
|
'compat_urllib_request',
|
||||||
'compat_urlparse',
|
'compat_urlparse',
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from .f4m import F4mFD
|
|||||||
from .hls import HlsFD
|
from .hls import HlsFD
|
||||||
from .hls import NativeHlsFD
|
from .hls import NativeHlsFD
|
||||||
from .http import HttpFD
|
from .http import HttpFD
|
||||||
from .mplayer import MplayerFD
|
from .rtsp import RtspFD
|
||||||
from .rtmp import RtmpFD
|
from .rtmp import RtmpFD
|
||||||
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
@@ -17,8 +17,8 @@ PROTOCOL_MAP = {
|
|||||||
'rtmp': RtmpFD,
|
'rtmp': RtmpFD,
|
||||||
'm3u8_native': NativeHlsFD,
|
'm3u8_native': NativeHlsFD,
|
||||||
'm3u8': HlsFD,
|
'm3u8': HlsFD,
|
||||||
'mms': MplayerFD,
|
'mms': RtspFD,
|
||||||
'rtsp': MplayerFD,
|
'rtsp': RtspFD,
|
||||||
'f4m': F4mFD,
|
'f4m': F4mFD,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import time
|
|||||||
from ..compat import compat_str
|
from ..compat import compat_str
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
encodeFilename,
|
encodeFilename,
|
||||||
|
decodeArgument,
|
||||||
format_bytes,
|
format_bytes,
|
||||||
timeconvert,
|
timeconvert,
|
||||||
)
|
)
|
||||||
@@ -204,7 +205,7 @@ class FileDownloader(object):
|
|||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
os.utime(filename, (time.time(), filetime))
|
os.utime(filename, (time.time(), filetime))
|
||||||
except:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return filetime
|
return filetime
|
||||||
|
|
||||||
@@ -318,7 +319,7 @@ class FileDownloader(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
continuedl_and_exists = (
|
continuedl_and_exists = (
|
||||||
self.params.get('continuedl', False) and
|
self.params.get('continuedl', True) and
|
||||||
os.path.isfile(encodeFilename(filename)) and
|
os.path.isfile(encodeFilename(filename)) and
|
||||||
not self.params.get('nopart', False)
|
not self.params.get('nopart', False)
|
||||||
)
|
)
|
||||||
@@ -353,19 +354,15 @@ class FileDownloader(object):
|
|||||||
# this interface
|
# this interface
|
||||||
self._progress_hooks.append(ph)
|
self._progress_hooks.append(ph)
|
||||||
|
|
||||||
def _debug_cmd(self, args, subprocess_encoding, exe=None):
|
def _debug_cmd(self, args, exe=None):
|
||||||
if not self.params.get('verbose', False):
|
if not self.params.get('verbose', False):
|
||||||
return
|
return
|
||||||
|
|
||||||
if exe is None:
|
str_args = [decodeArgument(a) for a in args]
|
||||||
exe = os.path.basename(args[0])
|
|
||||||
|
if exe is None:
|
||||||
|
exe = os.path.basename(str_args[0])
|
||||||
|
|
||||||
if subprocess_encoding:
|
|
||||||
str_args = [
|
|
||||||
a.decode(subprocess_encoding) if isinstance(a, bytes) else a
|
|
||||||
for a in args]
|
|
||||||
else:
|
|
||||||
str_args = args
|
|
||||||
try:
|
try:
|
||||||
import pipes
|
import pipes
|
||||||
shell_quote = lambda args: ' '.join(map(pipes.quote, str_args))
|
shell_quote = lambda args: ' '.join(map(pipes.quote, str_args))
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import os.path
|
import os.path
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
|
||||||
|
|
||||||
from .common import FileDownloader
|
from .common import FileDownloader
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
encodeFilename,
|
encodeFilename,
|
||||||
|
encodeArgument,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -60,17 +60,9 @@ class ExternalFD(FileDownloader):
|
|||||||
|
|
||||||
def _call_downloader(self, tmpfilename, info_dict):
|
def _call_downloader(self, tmpfilename, info_dict):
|
||||||
""" Either overwrite this or implement _make_cmd """
|
""" Either overwrite this or implement _make_cmd """
|
||||||
cmd = self._make_cmd(tmpfilename, info_dict)
|
cmd = [encodeArgument(a) for a in self._make_cmd(tmpfilename, info_dict)]
|
||||||
|
|
||||||
if sys.platform == 'win32' and sys.version_info < (3, 0):
|
self._debug_cmd(cmd)
|
||||||
# Windows subprocess module does not actually support Unicode
|
|
||||||
# on Python 2.x
|
|
||||||
# See http://stackoverflow.com/a/9951851/35070
|
|
||||||
subprocess_encoding = sys.getfilesystemencoding()
|
|
||||||
cmd = [a.encode(subprocess_encoding, 'ignore') for a in cmd]
|
|
||||||
else:
|
|
||||||
subprocess_encoding = None
|
|
||||||
self._debug_cmd(cmd, subprocess_encoding)
|
|
||||||
|
|
||||||
p = subprocess.Popen(
|
p = subprocess.Popen(
|
||||||
cmd, stderr=subprocess.PIPE)
|
cmd, stderr=subprocess.PIPE)
|
||||||
@@ -117,6 +109,14 @@ class Aria2cFD(ExternalFD):
|
|||||||
cmd += ['--', info_dict['url']]
|
cmd += ['--', info_dict['url']]
|
||||||
return cmd
|
return cmd
|
||||||
|
|
||||||
|
|
||||||
|
class HttpieFD(ExternalFD):
|
||||||
|
def _make_cmd(self, tmpfilename, info_dict):
|
||||||
|
cmd = ['http', '--download', '--output', tmpfilename, info_dict['url']]
|
||||||
|
for key, val in info_dict['http_headers'].items():
|
||||||
|
cmd += ['%s:%s' % (key, val)]
|
||||||
|
return cmd
|
||||||
|
|
||||||
_BY_NAME = dict(
|
_BY_NAME = dict(
|
||||||
(klass.get_basename(), klass)
|
(klass.get_basename(), klass)
|
||||||
for name, klass in globals().items()
|
for name, klass in globals().items()
|
||||||
@@ -131,5 +131,6 @@ def list_external_downloaders():
|
|||||||
def get_external_downloader(external_downloader):
|
def get_external_downloader(external_downloader):
|
||||||
""" Given the name of the executable, see whether we support the given
|
""" Given the name of the executable, see whether we support the given
|
||||||
downloader . """
|
downloader . """
|
||||||
bn = os.path.basename(external_downloader)
|
# Drop .exe extension on Windows
|
||||||
|
bn = os.path.splitext(os.path.basename(external_downloader))[0]
|
||||||
return _BY_NAME[bn]
|
return _BY_NAME[bn]
|
||||||
|
|||||||
@@ -281,7 +281,7 @@ class F4mFD(FileDownloader):
|
|||||||
boot_info = self._get_bootstrap_from_url(bootstrap_url)
|
boot_info = self._get_bootstrap_from_url(bootstrap_url)
|
||||||
else:
|
else:
|
||||||
bootstrap_url = None
|
bootstrap_url = None
|
||||||
bootstrap = base64.b64decode(node.text)
|
bootstrap = base64.b64decode(node.text.encode('ascii'))
|
||||||
boot_info = read_bootstrap_info(bootstrap)
|
boot_info = read_bootstrap_info(bootstrap)
|
||||||
return (boot_info, bootstrap_url)
|
return (boot_info, bootstrap_url)
|
||||||
|
|
||||||
@@ -308,7 +308,7 @@ class F4mFD(FileDownloader):
|
|||||||
live = boot_info['live']
|
live = boot_info['live']
|
||||||
metadata_node = media.find(_add_ns('metadata'))
|
metadata_node = media.find(_add_ns('metadata'))
|
||||||
if metadata_node is not None:
|
if metadata_node is not None:
|
||||||
metadata = base64.b64decode(metadata_node.text)
|
metadata = base64.b64decode(metadata_node.text.encode('ascii'))
|
||||||
else:
|
else:
|
||||||
metadata = None
|
metadata = None
|
||||||
|
|
||||||
@@ -389,6 +389,8 @@ class F4mFD(FileDownloader):
|
|||||||
url = base_url + name
|
url = base_url + name
|
||||||
if akamai_pv:
|
if akamai_pv:
|
||||||
url += '?' + akamai_pv.strip(';')
|
url += '?' + akamai_pv.strip(';')
|
||||||
|
if info_dict.get('extra_param_to_segment_url'):
|
||||||
|
url += info_dict.get('extra_param_to_segment_url')
|
||||||
frag_filename = '%s-%s' % (tmpfilename, name)
|
frag_filename = '%s-%s' % (tmpfilename, name)
|
||||||
try:
|
try:
|
||||||
success = http_dl.download(frag_filename, {'url': url})
|
success = http_dl.download(frag_filename, {'url': url})
|
||||||
|
|||||||
@@ -28,13 +28,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)
|
||||||
data = info_dict.get('http_post_data')
|
basic_request = compat_urllib_request.Request(url, None, headers)
|
||||||
http_method = info_dict.get('http_method')
|
request = compat_urllib_request.Request(url, None, headers)
|
||||||
basic_request = compat_urllib_request.Request(url, data, headers)
|
|
||||||
request = compat_urllib_request.Request(url, data, headers)
|
|
||||||
if http_method is not None:
|
|
||||||
basic_request.get_method = lambda: http_method
|
|
||||||
request.get_method = lambda: http_method
|
|
||||||
|
|
||||||
is_test = self.params.get('test', False)
|
is_test = self.params.get('test', False)
|
||||||
|
|
||||||
@@ -49,7 +44,7 @@ class HttpFD(FileDownloader):
|
|||||||
|
|
||||||
open_mode = 'wb'
|
open_mode = 'wb'
|
||||||
if resume_len != 0:
|
if resume_len != 0:
|
||||||
if self.params.get('continuedl', False):
|
if self.params.get('continuedl', True):
|
||||||
self.report_resuming_byte(resume_len)
|
self.report_resuming_byte(resume_len)
|
||||||
request.add_header('Range', 'bytes=%d-' % resume_len)
|
request.add_header('Range', 'bytes=%d-' % resume_len)
|
||||||
open_mode = 'ab'
|
open_mode = 'ab'
|
||||||
@@ -92,6 +87,8 @@ class HttpFD(FileDownloader):
|
|||||||
self._hook_progress({
|
self._hook_progress({
|
||||||
'filename': filename,
|
'filename': filename,
|
||||||
'status': 'finished',
|
'status': 'finished',
|
||||||
|
'downloaded_bytes': resume_len,
|
||||||
|
'total_bytes': resume_len,
|
||||||
})
|
})
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
@@ -218,12 +215,6 @@ class HttpFD(FileDownloader):
|
|||||||
if tmpfilename != '-':
|
if tmpfilename != '-':
|
||||||
stream.close()
|
stream.close()
|
||||||
|
|
||||||
self._hook_progress({
|
|
||||||
'downloaded_bytes': byte_counter,
|
|
||||||
'total_bytes': data_len,
|
|
||||||
'tmpfilename': tmpfilename,
|
|
||||||
'status': 'error',
|
|
||||||
})
|
|
||||||
if data_len is not None and byte_counter != data_len:
|
if data_len is not None and byte_counter != data_len:
|
||||||
raise ContentTooShortError(byte_counter, int(data_len))
|
raise ContentTooShortError(byte_counter, int(data_len))
|
||||||
self.try_rename(tmpfilename, filename)
|
self.try_rename(tmpfilename, filename)
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ from __future__ import unicode_literals
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from .common import FileDownloader
|
from .common import FileDownloader
|
||||||
@@ -11,6 +10,7 @@ from ..compat import compat_str
|
|||||||
from ..utils import (
|
from ..utils import (
|
||||||
check_executable,
|
check_executable,
|
||||||
encodeFilename,
|
encodeFilename,
|
||||||
|
encodeArgument,
|
||||||
get_exe_version,
|
get_exe_version,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -105,7 +105,7 @@ class RtmpFD(FileDownloader):
|
|||||||
protocol = info_dict.get('rtmp_protocol', None)
|
protocol = info_dict.get('rtmp_protocol', None)
|
||||||
real_time = info_dict.get('rtmp_real_time', False)
|
real_time = info_dict.get('rtmp_real_time', False)
|
||||||
no_resume = info_dict.get('no_resume', False)
|
no_resume = info_dict.get('no_resume', False)
|
||||||
continue_dl = info_dict.get('continuedl', False)
|
continue_dl = info_dict.get('continuedl', True)
|
||||||
|
|
||||||
self.report_destination(filename)
|
self.report_destination(filename)
|
||||||
tmpfilename = self.temp_name(filename)
|
tmpfilename = self.temp_name(filename)
|
||||||
@@ -121,7 +121,7 @@ class RtmpFD(FileDownloader):
|
|||||||
# 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,
|
||||||
'-o', encodeFilename(tmpfilename, True)]
|
'-o', tmpfilename]
|
||||||
if player_url is not None:
|
if player_url is not None:
|
||||||
basic_args += ['--swfVfy', player_url]
|
basic_args += ['--swfVfy', player_url]
|
||||||
if page_url is not None:
|
if page_url is not None:
|
||||||
@@ -131,7 +131,7 @@ class RtmpFD(FileDownloader):
|
|||||||
if play_path is not None:
|
if play_path is not None:
|
||||||
basic_args += ['--playpath', play_path]
|
basic_args += ['--playpath', play_path]
|
||||||
if tc_url is not None:
|
if tc_url is not None:
|
||||||
basic_args += ['--tcUrl', url]
|
basic_args += ['--tcUrl', tc_url]
|
||||||
if test:
|
if test:
|
||||||
basic_args += ['--stop', '1']
|
basic_args += ['--stop', '1']
|
||||||
if flash_version is not None:
|
if flash_version is not None:
|
||||||
@@ -154,16 +154,9 @@ class RtmpFD(FileDownloader):
|
|||||||
if not live and continue_dl:
|
if not live and continue_dl:
|
||||||
args += ['--skip', '1']
|
args += ['--skip', '1']
|
||||||
|
|
||||||
if sys.platform == 'win32' and sys.version_info < (3, 0):
|
args = [encodeArgument(a) for a in args]
|
||||||
# Windows subprocess module does not actually support Unicode
|
|
||||||
# on Python 2.x
|
|
||||||
# See http://stackoverflow.com/a/9951851/35070
|
|
||||||
subprocess_encoding = sys.getfilesystemencoding()
|
|
||||||
args = [a.encode(subprocess_encoding, 'ignore') for a in args]
|
|
||||||
else:
|
|
||||||
subprocess_encoding = None
|
|
||||||
|
|
||||||
self._debug_cmd(args, subprocess_encoding, exe='rtmpdump')
|
self._debug_cmd(args, exe='rtmpdump')
|
||||||
|
|
||||||
RD_SUCCESS = 0
|
RD_SUCCESS = 0
|
||||||
RD_FAILED = 1
|
RD_FAILED = 1
|
||||||
@@ -180,7 +173,11 @@ class RtmpFD(FileDownloader):
|
|||||||
prevsize = os.path.getsize(encodeFilename(tmpfilename))
|
prevsize = os.path.getsize(encodeFilename(tmpfilename))
|
||||||
self.to_screen('[rtmpdump] %s bytes' % prevsize)
|
self.to_screen('[rtmpdump] %s bytes' % prevsize)
|
||||||
time.sleep(5.0) # This seems to be needed
|
time.sleep(5.0) # This seems to be needed
|
||||||
retval = run_rtmpdump(basic_args + ['-e'] + [[], ['-k', '1']][retval == RD_FAILED])
|
args = basic_args + ['--resume']
|
||||||
|
if retval == RD_FAILED:
|
||||||
|
args += ['--skip', '1']
|
||||||
|
args = [encodeArgument(a) for a in args]
|
||||||
|
retval = run_rtmpdump(args)
|
||||||
cursize = os.path.getsize(encodeFilename(tmpfilename))
|
cursize = os.path.getsize(encodeFilename(tmpfilename))
|
||||||
if prevsize == cursize and retval == RD_FAILED:
|
if prevsize == cursize and retval == RD_FAILED:
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -10,21 +10,23 @@ from ..utils import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class MplayerFD(FileDownloader):
|
class RtspFD(FileDownloader):
|
||||||
def real_download(self, filename, info_dict):
|
def real_download(self, filename, info_dict):
|
||||||
url = info_dict['url']
|
url = info_dict['url']
|
||||||
self.report_destination(filename)
|
self.report_destination(filename)
|
||||||
tmpfilename = self.temp_name(filename)
|
tmpfilename = self.temp_name(filename)
|
||||||
|
|
||||||
|
if check_executable('mplayer', ['-h']):
|
||||||
args = [
|
args = [
|
||||||
'mplayer', '-really-quiet', '-vo', 'null', '-vc', 'dummy',
|
'mplayer', '-really-quiet', '-vo', 'null', '-vc', 'dummy',
|
||||||
'-dumpstream', '-dumpfile', tmpfilename, url]
|
'-dumpstream', '-dumpfile', tmpfilename, url]
|
||||||
# Check for mplayer first
|
elif check_executable('mpv', ['-h']):
|
||||||
if not check_executable('mplayer', ['-h']):
|
args = [
|
||||||
self.report_error('MMS or RTSP download detected but "%s" could not be run' % args[0])
|
'mpv', '-really-quiet', '--vo=null', '--stream-dump=' + tmpfilename, url]
|
||||||
|
else:
|
||||||
|
self.report_error('MMS or RTSP download detected but neither "mplayer" nor "mpv" could be run. Please install any.')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Download using mplayer.
|
|
||||||
retval = subprocess.call(args)
|
retval = subprocess.call(args)
|
||||||
if retval == 0:
|
if retval == 0:
|
||||||
fsize = os.path.getsize(encodeFilename(tmpfilename))
|
fsize = os.path.getsize(encodeFilename(tmpfilename))
|
||||||
@@ -39,5 +41,5 @@ class MplayerFD(FileDownloader):
|
|||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
self.to_stderr('\n')
|
self.to_stderr('\n')
|
||||||
self.report_error('mplayer exited with code %d' % retval)
|
self.report_error('%s exited with code %d' % (args[0], retval))
|
||||||
return False
|
return False
|
||||||
@@ -4,7 +4,10 @@ from .abc import ABCIE
|
|||||||
from .abc7news import Abc7NewsIE
|
from .abc7news import Abc7NewsIE
|
||||||
from .academicearth import AcademicEarthCourseIE
|
from .academicearth import AcademicEarthCourseIE
|
||||||
from .addanime import AddAnimeIE
|
from .addanime import AddAnimeIE
|
||||||
from .adobetv import AdobeTVIE
|
from .adobetv import (
|
||||||
|
AdobeTVIE,
|
||||||
|
AdobeTVVideoIE,
|
||||||
|
)
|
||||||
from .adultswim import AdultSwimIE
|
from .adultswim import AdultSwimIE
|
||||||
from .aftenposten import AftenpostenIE
|
from .aftenposten import AftenpostenIE
|
||||||
from .aftonbladet import AftonbladetIE
|
from .aftonbladet import AftonbladetIE
|
||||||
@@ -32,11 +35,13 @@ from .atresplayer import AtresPlayerIE
|
|||||||
from .atttechchannel import ATTTechChannelIE
|
from .atttechchannel import ATTTechChannelIE
|
||||||
from .audiomack import AudiomackIE, AudiomackAlbumIE
|
from .audiomack import AudiomackIE, AudiomackAlbumIE
|
||||||
from .azubu import AzubuIE
|
from .azubu import AzubuIE
|
||||||
|
from .baidu import BaiduVideoIE
|
||||||
from .bambuser import BambuserIE, BambuserChannelIE
|
from .bambuser import BambuserIE, BambuserChannelIE
|
||||||
from .bandcamp import BandcampIE, BandcampAlbumIE
|
from .bandcamp import BandcampIE, BandcampAlbumIE
|
||||||
from .bbccouk import BBCCoUkIE
|
from .bbccouk import BBCCoUkIE
|
||||||
from .beeg import BeegIE
|
from .beeg import BeegIE
|
||||||
from .behindkink import BehindKinkIE
|
from .behindkink import BehindKinkIE
|
||||||
|
from .beatportpro import BeatportProIE
|
||||||
from .bet import BetIE
|
from .bet import BetIE
|
||||||
from .bild import BildIE
|
from .bild import BildIE
|
||||||
from .bilibili import BiliBiliIE
|
from .bilibili import BiliBiliIE
|
||||||
@@ -69,6 +74,7 @@ from .chirbit import (
|
|||||||
ChirbitProfileIE,
|
ChirbitProfileIE,
|
||||||
)
|
)
|
||||||
from .cinchcast import CinchcastIE
|
from .cinchcast import CinchcastIE
|
||||||
|
from .cinemassacre import CinemassacreIE
|
||||||
from .clipfish import ClipfishIE
|
from .clipfish import ClipfishIE
|
||||||
from .cliphunter import CliphunterIE
|
from .cliphunter import CliphunterIE
|
||||||
from .clipsyndicate import ClipsyndicateIE
|
from .clipsyndicate import ClipsyndicateIE
|
||||||
@@ -89,6 +95,7 @@ from .commonmistakes import CommonMistakesIE, UnicodeBOMIE
|
|||||||
from .condenast import CondeNastIE
|
from .condenast import CondeNastIE
|
||||||
from .cracked import CrackedIE
|
from .cracked import CrackedIE
|
||||||
from .criterion import CriterionIE
|
from .criterion import CriterionIE
|
||||||
|
from .crooksandliars import CrooksAndLiarsIE
|
||||||
from .crunchyroll import (
|
from .crunchyroll import (
|
||||||
CrunchyrollIE,
|
CrunchyrollIE,
|
||||||
CrunchyrollShowPlaylistIE
|
CrunchyrollShowPlaylistIE
|
||||||
@@ -99,23 +106,32 @@ from .dailymotion import (
|
|||||||
DailymotionIE,
|
DailymotionIE,
|
||||||
DailymotionPlaylistIE,
|
DailymotionPlaylistIE,
|
||||||
DailymotionUserIE,
|
DailymotionUserIE,
|
||||||
|
DailymotionCloudIE,
|
||||||
)
|
)
|
||||||
from .daum import DaumIE
|
from .daum import DaumIE
|
||||||
from .dbtv import DBTVIE
|
from .dbtv import DBTVIE
|
||||||
from .dctp import DctpTvIE
|
from .dctp import DctpTvIE
|
||||||
from .deezer import DeezerPlaylistIE
|
from .deezer import DeezerPlaylistIE
|
||||||
from .dfb import DFBIE
|
from .dfb import DFBIE
|
||||||
|
from .dhm import DHMIE
|
||||||
from .dotsub import DotsubIE
|
from .dotsub import DotsubIE
|
||||||
|
from .douyutv import DouyuTVIE
|
||||||
|
from .dramafever import (
|
||||||
|
DramaFeverIE,
|
||||||
|
DramaFeverSeriesIE,
|
||||||
|
)
|
||||||
from .dreisat import DreiSatIE
|
from .dreisat import DreiSatIE
|
||||||
from .drbonanza import DRBonanzaIE
|
from .drbonanza import DRBonanzaIE
|
||||||
from .drtuber import DrTuberIE
|
from .drtuber import DrTuberIE
|
||||||
from .drtv import DRTVIE
|
from .drtv import DRTVIE
|
||||||
from .dvtv import DVTVIE
|
from .dvtv import DVTVIE
|
||||||
from .dump import DumpIE
|
from .dump import DumpIE
|
||||||
|
from .dumpert import DumpertIE
|
||||||
from .defense import DefenseGouvFrIE
|
from .defense import DefenseGouvFrIE
|
||||||
from .discovery import DiscoveryIE
|
from .discovery import DiscoveryIE
|
||||||
from .divxstage import DivxStageIE
|
from .divxstage import DivxStageIE
|
||||||
from .dropbox import DropboxIE
|
from .dropbox import DropboxIE
|
||||||
|
from .eagleplatform import EaglePlatformIE
|
||||||
from .ebaumsworld import EbaumsWorldIE
|
from .ebaumsworld import EbaumsWorldIE
|
||||||
from .echomsk import EchoMskIE
|
from .echomsk import EchoMskIE
|
||||||
from .ehow import EHowIE
|
from .ehow import EHowIE
|
||||||
@@ -128,11 +144,11 @@ from .ellentv import (
|
|||||||
)
|
)
|
||||||
from .elpais import ElPaisIE
|
from .elpais import ElPaisIE
|
||||||
from .embedly import EmbedlyIE
|
from .embedly import EmbedlyIE
|
||||||
from .empflix import EMPFlixIE
|
|
||||||
from .engadget import EngadgetIE
|
from .engadget import EngadgetIE
|
||||||
from .eporner import EpornerIE
|
from .eporner import EpornerIE
|
||||||
from .eroprofile import EroProfileIE
|
from .eroprofile import EroProfileIE
|
||||||
from .escapist import EscapistIE
|
from .escapist import EscapistIE
|
||||||
|
from .espn import ESPNIE
|
||||||
from .everyonesmixtape import EveryonesMixtapeIE
|
from .everyonesmixtape import EveryonesMixtapeIE
|
||||||
from .exfm import ExfmIE
|
from .exfm import ExfmIE
|
||||||
from .expotv import ExpoTVIE
|
from .expotv import ExpoTVIE
|
||||||
@@ -140,19 +156,21 @@ from .extremetube import ExtremeTubeIE
|
|||||||
from .facebook import FacebookIE
|
from .facebook import FacebookIE
|
||||||
from .faz import FazIE
|
from .faz import FazIE
|
||||||
from .fc2 import FC2IE
|
from .fc2 import FC2IE
|
||||||
from .firedrive import FiredriveIE
|
|
||||||
from .firstpost import FirstpostIE
|
from .firstpost import FirstpostIE
|
||||||
from .firsttv import FirstTVIE
|
from .firsttv import FirstTVIE
|
||||||
from .fivemin import FiveMinIE
|
from .fivemin import FiveMinIE
|
||||||
|
from .fivetv import FiveTVIE
|
||||||
from .fktv import (
|
from .fktv import (
|
||||||
FKTVIE,
|
FKTVIE,
|
||||||
FKTVPosteckeIE,
|
FKTVPosteckeIE,
|
||||||
)
|
)
|
||||||
from .flickr import FlickrIE
|
from .flickr import FlickrIE
|
||||||
from .folketinget import FolketingetIE
|
from .folketinget import FolketingetIE
|
||||||
|
from .footyroom import FootyRoomIE
|
||||||
from .fourtube import FourTubeIE
|
from .fourtube import FourTubeIE
|
||||||
from .foxgay import FoxgayIE
|
from .foxgay import FoxgayIE
|
||||||
from .foxnews import FoxNewsIE
|
from .foxnews import FoxNewsIE
|
||||||
|
from .foxsports import FoxSportsIE
|
||||||
from .franceculture import FranceCultureIE
|
from .franceculture import FranceCultureIE
|
||||||
from .franceinter import FranceInterIE
|
from .franceinter import FranceInterIE
|
||||||
from .francetv import (
|
from .francetv import (
|
||||||
@@ -171,11 +189,14 @@ from .gameone import (
|
|||||||
GameOneIE,
|
GameOneIE,
|
||||||
GameOnePlaylistIE,
|
GameOnePlaylistIE,
|
||||||
)
|
)
|
||||||
|
from .gamersyde import GamersydeIE
|
||||||
from .gamespot import GameSpotIE
|
from .gamespot import GameSpotIE
|
||||||
from .gamestar import GameStarIE
|
from .gamestar import GameStarIE
|
||||||
from .gametrailers import GametrailersIE
|
from .gametrailers import GametrailersIE
|
||||||
|
from .gazeta import GazetaIE
|
||||||
from .gdcvault import GDCVaultIE
|
from .gdcvault import GDCVaultIE
|
||||||
from .generic import GenericIE
|
from .generic import GenericIE
|
||||||
|
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
|
||||||
@@ -187,7 +208,6 @@ from .googleplus import GooglePlusIE
|
|||||||
from .googlesearch import GoogleSearchIE
|
from .googlesearch import GoogleSearchIE
|
||||||
from .gorillavid import GorillaVidIE
|
from .gorillavid import GorillaVidIE
|
||||||
from .goshgay import GoshgayIE
|
from .goshgay import GoshgayIE
|
||||||
from .grooveshark import GroovesharkIE
|
|
||||||
from .groupon import GrouponIE
|
from .groupon import GrouponIE
|
||||||
from .hark import HarkIE
|
from .hark import HarkIE
|
||||||
from .hearthisat import HearThisAtIE
|
from .hearthisat import HearThisAtIE
|
||||||
@@ -217,6 +237,7 @@ from .infoq import InfoQIE
|
|||||||
from .instagram import InstagramIE, InstagramUserIE
|
from .instagram import InstagramIE, InstagramUserIE
|
||||||
from .internetvideoarchive import InternetVideoArchiveIE
|
from .internetvideoarchive import InternetVideoArchiveIE
|
||||||
from .iprima import IPrimaIE
|
from .iprima import IPrimaIE
|
||||||
|
from .iqiyi import IqiyiIE
|
||||||
from .ivi import (
|
from .ivi import (
|
||||||
IviIE,
|
IviIE,
|
||||||
IviCompilationIE
|
IviCompilationIE
|
||||||
@@ -228,8 +249,10 @@ from .jove import JoveIE
|
|||||||
from .jukebox import JukeboxIE
|
from .jukebox import JukeboxIE
|
||||||
from .jpopsukitv import JpopsukiIE
|
from .jpopsukitv import JpopsukiIE
|
||||||
from .kaltura import KalturaIE
|
from .kaltura import KalturaIE
|
||||||
|
from .kanalplay import KanalPlayIE
|
||||||
from .kankan import KankanIE
|
from .kankan import KankanIE
|
||||||
from .karaoketv import KaraoketvIE
|
from .karaoketv import KaraoketvIE
|
||||||
|
from .karrierevideos import KarriereVideosIE
|
||||||
from .keezmovies import KeezMoviesIE
|
from .keezmovies import KeezMoviesIE
|
||||||
from .khanacademy import KhanAcademyIE
|
from .khanacademy import KhanAcademyIE
|
||||||
from .kickstarter import KickStarterIE
|
from .kickstarter import KickStarterIE
|
||||||
@@ -237,6 +260,14 @@ from .keek import KeekIE
|
|||||||
from .kontrtube import KontrTubeIE
|
from .kontrtube import KontrTubeIE
|
||||||
from .krasview import KrasViewIE
|
from .krasview import KrasViewIE
|
||||||
from .ku6 import Ku6IE
|
from .ku6 import Ku6IE
|
||||||
|
from .kuwo import (
|
||||||
|
KuwoIE,
|
||||||
|
KuwoAlbumIE,
|
||||||
|
KuwoChartIE,
|
||||||
|
KuwoSingerIE,
|
||||||
|
KuwoCategoryIE,
|
||||||
|
KuwoMvIE,
|
||||||
|
)
|
||||||
from .la7 import LA7IE
|
from .la7 import LA7IE
|
||||||
from .laola1tv import Laola1TvIE
|
from .laola1tv import Laola1TvIE
|
||||||
from .letv import (
|
from .letv import (
|
||||||
@@ -244,7 +275,11 @@ from .letv import (
|
|||||||
LetvTvIE,
|
LetvTvIE,
|
||||||
LetvPlaylistIE
|
LetvPlaylistIE
|
||||||
)
|
)
|
||||||
from .lifenews import LifeNewsIE
|
from .libsyn import LibsynIE
|
||||||
|
from .lifenews import (
|
||||||
|
LifeNewsIE,
|
||||||
|
LifeEmbedIE,
|
||||||
|
)
|
||||||
from .liveleak import LiveLeakIE
|
from .liveleak import LiveLeakIE
|
||||||
from .livestream import (
|
from .livestream import (
|
||||||
LivestreamIE,
|
LivestreamIE,
|
||||||
@@ -262,11 +297,13 @@ from .macgamestore import MacGameStoreIE
|
|||||||
from .mailru import MailRuIE
|
from .mailru import MailRuIE
|
||||||
from .malemotion import MalemotionIE
|
from .malemotion import MalemotionIE
|
||||||
from .mdr import MDRIE
|
from .mdr import MDRIE
|
||||||
|
from .megavideoz import MegaVideozIE
|
||||||
from .metacafe import MetacafeIE
|
from .metacafe import MetacafeIE
|
||||||
from .metacritic import MetacriticIE
|
from .metacritic import MetacriticIE
|
||||||
from .mgoon import MgoonIE
|
from .mgoon import MgoonIE
|
||||||
from .minhateca import MinhatecaIE
|
from .minhateca import MinhatecaIE
|
||||||
from .ministrygrid import MinistryGridIE
|
from .ministrygrid import MinistryGridIE
|
||||||
|
from .miomio import MioMioIE
|
||||||
from .mit import TechTVMITIE, MITIE, OCWMITIE
|
from .mit import TechTVMITIE, MITIE, OCWMITIE
|
||||||
from .mitele import MiTeleIE
|
from .mitele import MiTeleIE
|
||||||
from .mixcloud import MixcloudIE
|
from .mixcloud import MixcloudIE
|
||||||
@@ -294,6 +331,7 @@ from .musicvault import MusicVaultIE
|
|||||||
from .muzu import MuzuTVIE
|
from .muzu import MuzuTVIE
|
||||||
from .myspace import MySpaceIE, MySpaceAlbumIE
|
from .myspace import MySpaceIE, MySpaceAlbumIE
|
||||||
from .myspass import MySpassIE
|
from .myspass import MySpassIE
|
||||||
|
from .myvi import MyviIE
|
||||||
from .myvideo import MyVideoIE
|
from .myvideo import MyVideoIE
|
||||||
from .myvidster import MyVidsterIE
|
from .myvidster import MyVidsterIE
|
||||||
from .nationalgeographic import NationalGeographicIE
|
from .nationalgeographic import NationalGeographicIE
|
||||||
@@ -302,19 +340,32 @@ from .nba import NBAIE
|
|||||||
from .nbc import (
|
from .nbc import (
|
||||||
NBCIE,
|
NBCIE,
|
||||||
NBCNewsIE,
|
NBCNewsIE,
|
||||||
|
NBCSportsIE,
|
||||||
|
NBCSportsVPlayerIE,
|
||||||
|
)
|
||||||
|
from .ndr import (
|
||||||
|
NDRIE,
|
||||||
|
NJoyIE,
|
||||||
)
|
)
|
||||||
from .ndr import NDRIE
|
|
||||||
from .ndtv import NDTVIE
|
from .ndtv import NDTVIE
|
||||||
from .netzkino import NetzkinoIE
|
from .netzkino import NetzkinoIE
|
||||||
from .nerdcubed import NerdCubedFeedIE
|
from .nerdcubed import NerdCubedFeedIE
|
||||||
from .nerdist import NerdistIE
|
from .nerdist import NerdistIE
|
||||||
|
from .neteasemusic import (
|
||||||
|
NetEaseMusicIE,
|
||||||
|
NetEaseMusicAlbumIE,
|
||||||
|
NetEaseMusicSingerIE,
|
||||||
|
NetEaseMusicListIE,
|
||||||
|
NetEaseMusicMvIE,
|
||||||
|
NetEaseMusicProgramIE,
|
||||||
|
NetEaseMusicDjRadioIE,
|
||||||
|
)
|
||||||
from .newgrounds import NewgroundsIE
|
from .newgrounds import NewgroundsIE
|
||||||
from .newstube import NewstubeIE
|
from .newstube import NewstubeIE
|
||||||
from .nextmedia import (
|
from .nextmedia import (
|
||||||
NextMediaIE,
|
NextMediaIE,
|
||||||
NextMediaActionNewsIE,
|
NextMediaActionNewsIE,
|
||||||
AppleDailyRealtimeNewsIE,
|
AppleDailyIE,
|
||||||
AppleDailyAnimationNewsIE
|
|
||||||
)
|
)
|
||||||
from .nfb import NFBIE
|
from .nfb import NFBIE
|
||||||
from .nfl import NFLIE
|
from .nfl import NFLIE
|
||||||
@@ -328,61 +379,89 @@ from .ninegag import NineGagIE
|
|||||||
from .noco import NocoIE
|
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 .novamov import NovaMovIE
|
from .novamov import NovaMovIE
|
||||||
from .nowness import NownessIE
|
from .nowness import NownessIE
|
||||||
|
from .nowtv import NowTVIE
|
||||||
from .nowvideo import NowVideoIE
|
from .nowvideo import NowVideoIE
|
||||||
from .npo import (
|
from .npo import (
|
||||||
NPOIE,
|
NPOIE,
|
||||||
NPOLiveIE,
|
NPOLiveIE,
|
||||||
NPORadioIE,
|
NPORadioIE,
|
||||||
NPORadioFragmentIE,
|
NPORadioFragmentIE,
|
||||||
TegenlichtVproIE,
|
VPROIE,
|
||||||
|
WNLIE
|
||||||
)
|
)
|
||||||
from .nrk import (
|
from .nrk import (
|
||||||
NRKIE,
|
NRKIE,
|
||||||
|
NRKPlaylistIE,
|
||||||
NRKTVIE,
|
NRKTVIE,
|
||||||
)
|
)
|
||||||
from .ntvde import NTVDeIE
|
from .ntvde import NTVDeIE
|
||||||
from .ntvru import NTVRuIE
|
from .ntvru import NTVRuIE
|
||||||
from .nytimes import NYTimesIE
|
from .nytimes import (
|
||||||
|
NYTimesIE,
|
||||||
|
NYTimesArticleIE,
|
||||||
|
)
|
||||||
from .nuvid import NuvidIE
|
from .nuvid import NuvidIE
|
||||||
from .odnoklassniki import OdnoklassnikiIE
|
from .odnoklassniki import OdnoklassnikiIE
|
||||||
from .oktoberfesttv import OktoberfestTVIE
|
from .oktoberfesttv import OktoberfestTVIE
|
||||||
from .ooyala import OoyalaIE
|
from .onionstudios import OnionStudiosIE
|
||||||
|
from .ooyala import (
|
||||||
|
OoyalaIE,
|
||||||
|
OoyalaExternalIE,
|
||||||
|
)
|
||||||
from .openfilm import OpenFilmIE
|
from .openfilm import OpenFilmIE
|
||||||
from .orf import (
|
from .orf import (
|
||||||
ORFTVthekIE,
|
ORFTVthekIE,
|
||||||
ORFOE1IE,
|
ORFOE1IE,
|
||||||
ORFFM4IE,
|
ORFFM4IE,
|
||||||
|
ORFIPTVIE,
|
||||||
)
|
)
|
||||||
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 .philharmoniedeparis import PhilharmonieDeParisIE
|
||||||
from .phoenix import PhoenixIE
|
from .phoenix import PhoenixIE
|
||||||
from .photobucket import PhotobucketIE
|
from .photobucket import PhotobucketIE
|
||||||
|
from .pinkbike import PinkbikeIE
|
||||||
from .planetaplay import PlanetaPlayIE
|
from .planetaplay import PlanetaPlayIE
|
||||||
|
from .pladform import PladformIE
|
||||||
from .played import PlayedIE
|
from .played import PlayedIE
|
||||||
from .playfm import PlayFMIE
|
from .playfm import PlayFMIE
|
||||||
from .playvid import PlayvidIE
|
from .playvid import PlayvidIE
|
||||||
|
from .playwire import PlaywireIE
|
||||||
from .podomatic import PodomaticIE
|
from .podomatic import PodomaticIE
|
||||||
|
from .porn91 import Porn91IE
|
||||||
from .pornhd import PornHdIE
|
from .pornhd import PornHdIE
|
||||||
from .pornhub import (
|
from .pornhub import (
|
||||||
PornHubIE,
|
PornHubIE,
|
||||||
PornHubPlaylistIE,
|
PornHubPlaylistIE,
|
||||||
)
|
)
|
||||||
from .pornotube import PornotubeIE
|
from .pornotube import PornotubeIE
|
||||||
|
from .pornovoisines import PornoVoisinesIE
|
||||||
from .pornoxo import PornoXOIE
|
from .pornoxo import PornoXOIE
|
||||||
|
from .primesharetv import PrimeShareTVIE
|
||||||
from .promptfile import PromptFileIE
|
from .promptfile import PromptFileIE
|
||||||
from .prosiebensat1 import ProSiebenSat1IE
|
from .prosiebensat1 import ProSiebenSat1IE
|
||||||
from .puls4 import Puls4IE
|
from .puls4 import Puls4IE
|
||||||
from .pyvideo import PyvideoIE
|
from .pyvideo import PyvideoIE
|
||||||
|
from .qqmusic import (
|
||||||
|
QQMusicIE,
|
||||||
|
QQMusicSingerIE,
|
||||||
|
QQMusicAlbumIE,
|
||||||
|
QQMusicToplistIE,
|
||||||
|
QQMusicPlaylistIE,
|
||||||
|
)
|
||||||
from .quickvid import QuickVidIE
|
from .quickvid import QuickVidIE
|
||||||
from .r7 import R7IE
|
from .r7 import R7IE
|
||||||
from .radiode import RadioDeIE
|
from .radiode import RadioDeIE
|
||||||
|
from .radiojavan import RadioJavanIE
|
||||||
from .radiobremen import RadioBremenIE
|
from .radiobremen import RadioBremenIE
|
||||||
from .radiofrance import RadioFranceIE
|
from .radiofrance import RadioFranceIE
|
||||||
from .rai import RaiIE
|
from .rai import RaiIE
|
||||||
from .rbmaradio import RBMARadioIE
|
from .rbmaradio import RBMARadioIE
|
||||||
|
from .rds import RDSIE
|
||||||
from .redtube import RedTubeIE
|
from .redtube import RedTubeIE
|
||||||
from .restudy import RestudyIE
|
from .restudy import RestudyIE
|
||||||
from .reverbnation import ReverbNationIE
|
from .reverbnation import ReverbNationIE
|
||||||
@@ -393,11 +472,10 @@ from .roxwel import RoxwelIE
|
|||||||
from .rtbf import RTBFIE
|
from .rtbf import RTBFIE
|
||||||
from .rte import RteIE
|
from .rte import RteIE
|
||||||
from .rtlnl import RtlNlIE
|
from .rtlnl import RtlNlIE
|
||||||
from .rtlnow import RTLnowIE
|
|
||||||
from .rtl2 import RTL2IE
|
from .rtl2 import RTL2IE
|
||||||
from .rtp import RTPIE
|
from .rtp import RTPIE
|
||||||
from .rts import RTSIE
|
from .rts import RTSIE
|
||||||
from .rtve import RTVEALaCartaIE, RTVELiveIE
|
from .rtve import RTVEALaCartaIE, RTVELiveIE, RTVEInfantilIE
|
||||||
from .ruhd import RUHDIE
|
from .ruhd import RUHDIE
|
||||||
from .rutube import (
|
from .rutube import (
|
||||||
RutubeIE,
|
RutubeIE,
|
||||||
@@ -407,14 +485,20 @@ from .rutube import (
|
|||||||
RutubePersonIE,
|
RutubePersonIE,
|
||||||
)
|
)
|
||||||
from .rutv import RUTVIE
|
from .rutv import RUTVIE
|
||||||
|
from .ruutu import RuutuIE
|
||||||
from .sandia import SandiaIE
|
from .sandia import SandiaIE
|
||||||
|
from .safari import (
|
||||||
|
SafariIE,
|
||||||
|
SafariCourseIE,
|
||||||
|
)
|
||||||
from .sapo import SapoIE
|
from .sapo import SapoIE
|
||||||
from .savefrom import SaveFromIE
|
from .savefrom import SaveFromIE
|
||||||
from .sbs import SBSIE
|
from .sbs import SBSIE
|
||||||
from .scivee import SciVeeIE
|
from .scivee import SciVeeIE
|
||||||
from .screencast import ScreencastIE
|
from .screencast import ScreencastIE
|
||||||
from .screencastomatic import ScreencastOMaticIE
|
from .screencastomatic import ScreencastOMaticIE
|
||||||
from .screenwavemedia import CinemassacreIE, ScreenwaveMediaIE, TeamFourIE
|
from .screenwavemedia import ScreenwaveMediaIE, TeamFourIE
|
||||||
|
from .senateisvp import SenateISVPIE
|
||||||
from .servingsys import ServingSysIE
|
from .servingsys import ServingSysIE
|
||||||
from .sexu import SexuIE
|
from .sexu import SexuIE
|
||||||
from .sexykarma import SexyKarmaIE
|
from .sexykarma import SexyKarmaIE
|
||||||
@@ -429,9 +513,16 @@ from .smotri import (
|
|||||||
SmotriUserIE,
|
SmotriUserIE,
|
||||||
SmotriBroadcastIE,
|
SmotriBroadcastIE,
|
||||||
)
|
)
|
||||||
|
from .snagfilms import (
|
||||||
|
SnagFilmsIE,
|
||||||
|
SnagFilmsEmbedIE,
|
||||||
|
)
|
||||||
from .snotr import SnotrIE
|
from .snotr import SnotrIE
|
||||||
from .sockshare import SockshareIE
|
|
||||||
from .sohu import SohuIE
|
from .sohu import SohuIE
|
||||||
|
from .soompi import (
|
||||||
|
SoompiIE,
|
||||||
|
SoompiShowIE,
|
||||||
|
)
|
||||||
from .soundcloud import (
|
from .soundcloud import (
|
||||||
SoundcloudIE,
|
SoundcloudIE,
|
||||||
SoundcloudSetIE,
|
SoundcloudSetIE,
|
||||||
@@ -444,24 +535,36 @@ from .soundgasm import (
|
|||||||
)
|
)
|
||||||
from .southpark import (
|
from .southpark import (
|
||||||
SouthParkIE,
|
SouthParkIE,
|
||||||
SouthparkDeIE,
|
SouthParkDeIE,
|
||||||
|
SouthParkDkIE,
|
||||||
|
SouthParkEsIE,
|
||||||
|
SouthParkNlIE
|
||||||
)
|
)
|
||||||
from .space import SpaceIE
|
from .space import SpaceIE
|
||||||
|
from .spankbang import SpankBangIE
|
||||||
from .spankwire import SpankwireIE
|
from .spankwire import SpankwireIE
|
||||||
from .spiegel import SpiegelIE, SpiegelArticleIE
|
from .spiegel import SpiegelIE, SpiegelArticleIE
|
||||||
from .spiegeltv import SpiegeltvIE
|
from .spiegeltv import SpiegeltvIE
|
||||||
from .spike import SpikeIE
|
from .spike import SpikeIE
|
||||||
from .sport5 import Sport5IE
|
from .sport5 import Sport5IE
|
||||||
from .sportbox import SportBoxIE
|
from .sportbox import (
|
||||||
|
SportBoxIE,
|
||||||
|
SportBoxEmbedIE,
|
||||||
|
)
|
||||||
from .sportdeutschland import SportDeutschlandIE
|
from .sportdeutschland import SportDeutschlandIE
|
||||||
|
from .srf import SrfIE
|
||||||
from .srmediathek import SRMediathekIE
|
from .srmediathek import SRMediathekIE
|
||||||
|
from .ssa import SSAIE
|
||||||
from .stanfordoc import StanfordOpenClassroomIE
|
from .stanfordoc import StanfordOpenClassroomIE
|
||||||
from .steam import SteamIE
|
from .steam import SteamIE
|
||||||
from .streamcloud import StreamcloudIE
|
from .streamcloud import StreamcloudIE
|
||||||
from .streamcz import StreamCZIE
|
from .streamcz import StreamCZIE
|
||||||
from .streetvoice import StreetVoiceIE
|
from .streetvoice import StreetVoiceIE
|
||||||
from .sunporno import SunPornoIE
|
from .sunporno import SunPornoIE
|
||||||
from .svtplay import SVTPlayIE
|
from .svt import (
|
||||||
|
SVTIE,
|
||||||
|
SVTPlayIE,
|
||||||
|
)
|
||||||
from .swrmediathek import SWRMediathekIE
|
from .swrmediathek import SWRMediathekIE
|
||||||
from .syfy import SyfyIE
|
from .syfy import SyfyIE
|
||||||
from .sztvhu import SztvHuIE
|
from .sztvhu import SztvHuIE
|
||||||
@@ -487,11 +590,19 @@ from .tf1 import TF1IE
|
|||||||
from .theonion import TheOnionIE
|
from .theonion import TheOnionIE
|
||||||
from .theplatform import ThePlatformIE
|
from .theplatform import ThePlatformIE
|
||||||
from .thesixtyone import TheSixtyOneIE
|
from .thesixtyone import TheSixtyOneIE
|
||||||
|
from .thisamericanlife import ThisAmericanLifeIE
|
||||||
from .thisav import ThisAVIE
|
from .thisav import ThisAVIE
|
||||||
from .tinypic import TinyPicIE
|
from .tinypic import TinyPicIE
|
||||||
from .tlc import TlcIE, TlcDeIE
|
from .tlc import TlcIE, TlcDeIE
|
||||||
from .tmz import TMZIE
|
from .tmz import (
|
||||||
from .tnaflix import TNAFlixIE
|
TMZIE,
|
||||||
|
TMZArticleIE,
|
||||||
|
)
|
||||||
|
from .tnaflix import (
|
||||||
|
TNAFlixIE,
|
||||||
|
EMPFlixIE,
|
||||||
|
MovieFapIE,
|
||||||
|
)
|
||||||
from .thvideo import (
|
from .thvideo import (
|
||||||
THVideoIE,
|
THVideoIE,
|
||||||
THVideoPlaylistIE
|
THVideoPlaylistIE
|
||||||
@@ -502,17 +613,30 @@ from .traileraddict import TrailerAddictIE
|
|||||||
from .trilulilu import TriluliluIE
|
from .trilulilu import TriluliluIE
|
||||||
from .trutube import TruTubeIE
|
from .trutube import TruTubeIE
|
||||||
from .tube8 import Tube8IE
|
from .tube8 import Tube8IE
|
||||||
|
from .tubitv import TubiTvIE
|
||||||
from .tudou import TudouIE
|
from .tudou import TudouIE
|
||||||
from .tumblr import TumblrIE
|
from .tumblr import TumblrIE
|
||||||
from .tunein import TuneInIE
|
from .tunein import TuneInIE
|
||||||
from .turbo import TurboIE
|
from .turbo import TurboIE
|
||||||
from .tutv import TutvIE
|
from .tutv import TutvIE
|
||||||
|
from .tv2 import (
|
||||||
|
TV2IE,
|
||||||
|
TV2ArticleIE,
|
||||||
|
)
|
||||||
from .tv4 import TV4IE
|
from .tv4 import TV4IE
|
||||||
|
from .tvc import (
|
||||||
|
TVCIE,
|
||||||
|
TVCArticleIE,
|
||||||
|
)
|
||||||
from .tvigle import TvigleIE
|
from .tvigle import TvigleIE
|
||||||
from .tvp import TvpIE, TvpSeriesIE
|
from .tvp import TvpIE, TvpSeriesIE
|
||||||
from .tvplay import TVPlayIE
|
from .tvplay import TVPlayIE
|
||||||
from .tweakers import TweakersIE
|
from .tweakers import TweakersIE
|
||||||
from .twentyfourvideo import TwentyFourVideoIE
|
from .twentyfourvideo import TwentyFourVideoIE
|
||||||
|
from .twentytwotracks import (
|
||||||
|
TwentyTwoTracksIE,
|
||||||
|
TwentyTwoTracksGenreIE
|
||||||
|
)
|
||||||
from .twitch import (
|
from .twitch import (
|
||||||
TwitchVideoIE,
|
TwitchVideoIE,
|
||||||
TwitchChapterIE,
|
TwitchChapterIE,
|
||||||
@@ -522,20 +646,29 @@ from .twitch import (
|
|||||||
TwitchBookmarksIE,
|
TwitchBookmarksIE,
|
||||||
TwitchStreamIE,
|
TwitchStreamIE,
|
||||||
)
|
)
|
||||||
|
from .twitter import TwitterCardIE
|
||||||
from .ubu import UbuIE
|
from .ubu import UbuIE
|
||||||
from .udemy import (
|
from .udemy import (
|
||||||
UdemyIE,
|
UdemyIE,
|
||||||
UdemyCourseIE
|
UdemyCourseIE
|
||||||
)
|
)
|
||||||
|
from .udn import UDNEmbedIE
|
||||||
|
from .ultimedia import UltimediaIE
|
||||||
from .unistra import UnistraIE
|
from .unistra import UnistraIE
|
||||||
from .urort import UrortIE
|
from .urort import UrortIE
|
||||||
from .ustream import UstreamIE, UstreamChannelIE
|
from .ustream import UstreamIE, UstreamChannelIE
|
||||||
|
from .varzesh3 import Varzesh3IE
|
||||||
from .vbox7 import Vbox7IE
|
from .vbox7 import Vbox7IE
|
||||||
from .veehd import VeeHDIE
|
from .veehd import VeeHDIE
|
||||||
from .veoh import VeohIE
|
from .veoh import VeohIE
|
||||||
|
from .vessel import VesselIE
|
||||||
from .vesti import VestiIE
|
from .vesti import VestiIE
|
||||||
from .vevo import VevoIE
|
from .vevo import VevoIE
|
||||||
from .vgtv import VGTVIE
|
from .vgtv import (
|
||||||
|
BTArticleIE,
|
||||||
|
BTVestlendingenIE,
|
||||||
|
VGTVIE,
|
||||||
|
)
|
||||||
from .vh1 import VH1IE
|
from .vh1 import VH1IE
|
||||||
from .vice import ViceIE
|
from .vice import ViceIE
|
||||||
from .viddler import ViddlerIE
|
from .viddler import ViddlerIE
|
||||||
@@ -550,6 +683,7 @@ 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 .vimeo import (
|
from .vimeo import (
|
||||||
VimeoIE,
|
VimeoIE,
|
||||||
VimeoAlbumIE,
|
VimeoAlbumIE,
|
||||||
@@ -565,12 +699,16 @@ from .vine import (
|
|||||||
VineIE,
|
VineIE,
|
||||||
VineUserIE,
|
VineUserIE,
|
||||||
)
|
)
|
||||||
from .viki import VikiIE
|
from .viki import (
|
||||||
|
VikiIE,
|
||||||
|
VikiChannelIE,
|
||||||
|
)
|
||||||
from .vk import (
|
from .vk import (
|
||||||
VKIE,
|
VKIE,
|
||||||
VKUserVideosIE,
|
VKUserVideosIE,
|
||||||
)
|
)
|
||||||
from .vodlocker import VodlockerIE
|
from .vodlocker import VodlockerIE
|
||||||
|
from .voicerepublic import VoiceRepublicIE
|
||||||
from .vporn import VpornIE
|
from .vporn import VpornIE
|
||||||
from .vrt import VRTIE
|
from .vrt import VRTIE
|
||||||
from .vube import VubeIE
|
from .vube import VubeIE
|
||||||
@@ -585,7 +723,10 @@ from .wdr import (
|
|||||||
WDRMobileIE,
|
WDRMobileIE,
|
||||||
WDRMausIE,
|
WDRMausIE,
|
||||||
)
|
)
|
||||||
from .webofstories import WebOfStoriesIE
|
from .webofstories import (
|
||||||
|
WebOfStoriesIE,
|
||||||
|
WebOfStoriesPlaylistIE,
|
||||||
|
)
|
||||||
from .weibo import WeiboIE
|
from .weibo import WeiboIE
|
||||||
from .wimp import WimpIE
|
from .wimp import WimpIE
|
||||||
from .wistia import WistiaIE
|
from .wistia import WistiaIE
|
||||||
@@ -594,19 +735,29 @@ 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 .xhamster import XHamsterIE
|
from .xhamster import (
|
||||||
|
XHamsterIE,
|
||||||
|
XHamsterEmbedIE,
|
||||||
|
)
|
||||||
from .xminus import XMinusIE
|
from .xminus import XMinusIE
|
||||||
from .xnxx import XNXXIE
|
from .xnxx import XNXXIE
|
||||||
from .xvideos import XVideosIE
|
from .xstream import XstreamIE
|
||||||
from .xtube import XTubeUserIE, XTubeIE
|
from .xtube import XTubeUserIE, XTubeIE
|
||||||
from .xuite import XuiteIE
|
from .xuite import XuiteIE
|
||||||
|
from .xvideos import XVideosIE
|
||||||
from .xxxymovies import XXXYMoviesIE
|
from .xxxymovies import XXXYMoviesIE
|
||||||
from .yahoo import (
|
from .yahoo import (
|
||||||
YahooIE,
|
YahooIE,
|
||||||
YahooSearchIE,
|
YahooSearchIE,
|
||||||
)
|
)
|
||||||
from .yam import YamIE
|
from .yam import YamIE
|
||||||
|
from .yandexmusic import (
|
||||||
|
YandexMusicTrackIE,
|
||||||
|
YandexMusicAlbumIE,
|
||||||
|
YandexMusicPlaylistIE,
|
||||||
|
)
|
||||||
from .yesjapan import YesJapanIE
|
from .yesjapan import YesJapanIE
|
||||||
|
from .yinyuetai import YinYueTaiIE
|
||||||
from .ynet import YnetIE
|
from .ynet import YnetIE
|
||||||
from .youjizz import YouJizzIE
|
from .youjizz import YouJizzIE
|
||||||
from .youku import YoukuIE
|
from .youku import YoukuIE
|
||||||
|
|||||||
@@ -11,12 +11,13 @@ from ..compat import (
|
|||||||
)
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
|
qualities,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class AddAnimeIE(InfoExtractor):
|
class AddAnimeIE(InfoExtractor):
|
||||||
_VALID_URL = r'^http://(?:\w+\.)?add-anime\.net/watch_video\.php\?(?:.*?)v=(?P<id>[\w_]+)(?:.*)'
|
_VALID_URL = r'http://(?:\w+\.)?add-anime\.net/(?:watch_video\.php\?(?:.*?)v=|video/)(?P<id>[\w_]+)'
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
'url': 'http://www.add-anime.net/watch_video.php?v=24MR3YO5SAS9',
|
'url': 'http://www.add-anime.net/watch_video.php?v=24MR3YO5SAS9',
|
||||||
'md5': '72954ea10bc979ab5e2eb288b21425a0',
|
'md5': '72954ea10bc979ab5e2eb288b21425a0',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@@ -25,7 +26,10 @@ class AddAnimeIE(InfoExtractor):
|
|||||||
'description': 'One Piece 606',
|
'description': 'One Piece 606',
|
||||||
'title': 'One Piece 606',
|
'title': 'One Piece 606',
|
||||||
}
|
}
|
||||||
}
|
}, {
|
||||||
|
'url': 'http://add-anime.net/video/MDUGWYKNGBD8/One-Piece-687',
|
||||||
|
'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)
|
||||||
@@ -63,8 +67,10 @@ class AddAnimeIE(InfoExtractor):
|
|||||||
note='Confirming after redirect')
|
note='Confirming after redirect')
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
FORMATS = ('normal', 'hq')
|
||||||
|
quality = qualities(FORMATS)
|
||||||
formats = []
|
formats = []
|
||||||
for format_id in ('normal', 'hq'):
|
for format_id in FORMATS:
|
||||||
rex = r"var %s_video_file = '(.*?)';" % re.escape(format_id)
|
rex = r"var %s_video_file = '(.*?)';" % re.escape(format_id)
|
||||||
video_url = self._search_regex(rex, webpage, 'video file URLx',
|
video_url = self._search_regex(rex, webpage, 'video file URLx',
|
||||||
fatal=False)
|
fatal=False)
|
||||||
@@ -73,6 +79,7 @@ class AddAnimeIE(InfoExtractor):
|
|||||||
formats.append({
|
formats.append({
|
||||||
'format_id': format_id,
|
'format_id': format_id,
|
||||||
'url': video_url,
|
'url': video_url,
|
||||||
|
'quality': quality(format_id),
|
||||||
})
|
})
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
video_title = self._og_search_title(webpage)
|
video_title = self._og_search_title(webpage)
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ from ..utils import (
|
|||||||
parse_duration,
|
parse_duration,
|
||||||
unified_strdate,
|
unified_strdate,
|
||||||
str_to_int,
|
str_to_int,
|
||||||
|
float_or_none,
|
||||||
|
ISO639Utils,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -69,3 +71,61 @@ class AdobeTVIE(InfoExtractor):
|
|||||||
'view_count': view_count,
|
'view_count': view_count,
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class AdobeTVVideoIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://video\.tv\.adobe\.com/v/(?P<id>\d+)'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
# From https://helpx.adobe.com/acrobat/how-to/new-experience-acrobat-dc.html?set=acrobat--get-started--essential-beginners
|
||||||
|
'url': 'https://video.tv.adobe.com/v/2456/',
|
||||||
|
'md5': '43662b577c018ad707a63766462b1e87',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '2456',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'New experience with Acrobat DC',
|
||||||
|
'description': 'New experience with Acrobat DC',
|
||||||
|
'duration': 248.667,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
|
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 = [{
|
||||||
|
'url': source['src'],
|
||||||
|
'width': source.get('width'),
|
||||||
|
'height': source.get('height'),
|
||||||
|
'tbr': source.get('bitrate'),
|
||||||
|
} for source in player_params['sources']]
|
||||||
|
|
||||||
|
# For both metadata and downloaded files the duration varies among
|
||||||
|
# formats. I just pick the max one
|
||||||
|
duration = max(filter(None, [
|
||||||
|
float_or_none(source.get('duration'), scale=1000)
|
||||||
|
for source in player_params['sources']]))
|
||||||
|
|
||||||
|
subtitles = {}
|
||||||
|
for translation in player_params.get('translations', []):
|
||||||
|
lang_id = translation.get('language_w3c') or ISO639Utils.long2short(translation['language_medium'])
|
||||||
|
if lang_id not in subtitles:
|
||||||
|
subtitles[lang_id] = []
|
||||||
|
subtitles[lang_id].append({
|
||||||
|
'url': translation['vttPath'],
|
||||||
|
'ext': 'vtt',
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'formats': formats,
|
||||||
|
'title': player_params['title'],
|
||||||
|
'description': self._og_search_description(webpage),
|
||||||
|
'duration': duration,
|
||||||
|
'subtitles': subtitles,
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,13 +2,12 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import json
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
xpath_text,
|
|
||||||
float_or_none,
|
float_or_none,
|
||||||
|
xpath_text,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -60,6 +59,24 @@ class AdultSwimIE(InfoExtractor):
|
|||||||
'title': 'American Dad - Putting Francine Out of Business',
|
'title': 'American Dad - Putting Francine Out of Business',
|
||||||
'description': 'Stan hatches a plan to get Francine out of the real estate business.Watch more American Dad on [adult swim].'
|
'description': 'Stan hatches a plan to get Francine out of the real estate business.Watch more American Dad on [adult swim].'
|
||||||
},
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.adultswim.com/videos/tim-and-eric-awesome-show-great-job/dr-steve-brule-for-your-wine/',
|
||||||
|
'playlist': [
|
||||||
|
{
|
||||||
|
'md5': '3e346a2ab0087d687a05e1e7f3b3e529',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'sY3cMUR_TbuE4YmdjzbIcQ-0',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Tim and Eric Awesome Show Great Job! - Dr. Steve Brule, For Your Wine',
|
||||||
|
'description': 'Dr. Brule reports live from Wine Country with a special report on wines. \r\nWatch Tim and Eric Awesome Show Great Job! episode #20, "Embarrassed" on Adult Swim.\r\n\r\n',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'sY3cMUR_TbuE4YmdjzbIcQ',
|
||||||
|
'title': 'Tim and Eric Awesome Show Great Job! - Dr. Steve Brule, For Your Wine',
|
||||||
|
'description': 'Dr. Brule reports live from Wine Country with a special report on wines. \r\nWatch Tim and Eric Awesome Show Great Job! episode #20, "Embarrassed" on Adult Swim.\r\n\r\n',
|
||||||
|
},
|
||||||
}]
|
}]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -80,6 +97,7 @@ class AdultSwimIE(InfoExtractor):
|
|||||||
for video in collection.get('videos'):
|
for video in collection.get('videos'):
|
||||||
if video.get('slug') == slug:
|
if video.get('slug') == slug:
|
||||||
return collection, video
|
return collection, video
|
||||||
|
return None, None
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
@@ -90,28 +108,30 @@ class AdultSwimIE(InfoExtractor):
|
|||||||
webpage = self._download_webpage(url, episode_path)
|
webpage = self._download_webpage(url, episode_path)
|
||||||
|
|
||||||
# Extract the value of `bootstrappedData` from the Javascript in the page.
|
# Extract the value of `bootstrappedData` from the Javascript in the page.
|
||||||
bootstrappedDataJS = self._search_regex(r'var bootstrappedData = ({.*});', webpage, episode_path)
|
bootstrapped_data = self._parse_json(self._search_regex(
|
||||||
|
r'var bootstrappedData = ({.*});', webpage, 'bootstraped data'), episode_path)
|
||||||
try:
|
|
||||||
bootstrappedData = json.loads(bootstrappedDataJS)
|
|
||||||
except ValueError as ve:
|
|
||||||
errmsg = '%s: Failed to parse JSON ' % episode_path
|
|
||||||
raise ExtractorError(errmsg, cause=ve)
|
|
||||||
|
|
||||||
# Downloading videos from a /videos/playlist/ URL needs to be handled differently.
|
# Downloading videos from a /videos/playlist/ URL needs to be handled differently.
|
||||||
# NOTE: We are only downloading one video (the current one) not the playlist
|
# NOTE: We are only downloading one video (the current one) not the playlist
|
||||||
if is_playlist:
|
if is_playlist:
|
||||||
collections = bootstrappedData['playlists']['collections']
|
collections = bootstrapped_data['playlists']['collections']
|
||||||
collection = self.find_collection_by_linkURL(collections, show_path)
|
collection = self.find_collection_by_linkURL(collections, show_path)
|
||||||
video_info = self.find_video_info(collection, episode_path)
|
video_info = self.find_video_info(collection, episode_path)
|
||||||
|
|
||||||
show_title = video_info['showTitle']
|
show_title = video_info['showTitle']
|
||||||
segment_ids = [video_info['videoPlaybackID']]
|
segment_ids = [video_info['videoPlaybackID']]
|
||||||
else:
|
else:
|
||||||
collections = bootstrappedData['show']['collections']
|
collections = bootstrapped_data['show']['collections']
|
||||||
collection, video_info = self.find_collection_containing_video(collections, episode_path)
|
collection, video_info = self.find_collection_containing_video(collections, episode_path)
|
||||||
|
|
||||||
show = bootstrappedData['show']
|
# Video wasn't found in the collections, let's try `slugged_video`.
|
||||||
|
if video_info is None:
|
||||||
|
if bootstrapped_data.get('slugged_video', {}).get('slug') == episode_path:
|
||||||
|
video_info = bootstrapped_data['slugged_video']
|
||||||
|
else:
|
||||||
|
raise ExtractorError('Unable to find video info')
|
||||||
|
|
||||||
|
show = bootstrapped_data['show']
|
||||||
show_title = show['title']
|
show_title = show['title']
|
||||||
segment_ids = [clip['videoPlaybackID'] for clip in video_info['clips']]
|
segment_ids = [clip['videoPlaybackID'] for clip in video_info['clips']]
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,13 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
|
||||||
int_or_none,
|
|
||||||
parse_iso8601,
|
|
||||||
xpath_with_ns,
|
|
||||||
xpath_text,
|
|
||||||
find_xpath_attr,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class AftenpostenIE(InfoExtractor):
|
class AftenpostenIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?aftenposten\.no/webtv/([^/]+/)*(?P<id>[^/]+)-\d+\.html'
|
_VALID_URL = r'https?://(?:www\.)?aftenposten\.no/webtv/(?:#!/)?video/(?P<id>\d+)'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.aftenposten.no/webtv/serier-og-programmer/sweatshopenglish/TRAILER-SWEATSHOP---I-cant-take-any-more-7800835.html?paging=§ion=webtv_serierogprogrammer_sweatshop_sweatshopenglish',
|
'url': 'http://www.aftenposten.no/webtv/#!/video/21039/trailer-sweatshop-i-can-t-take-any-more',
|
||||||
'md5': 'fd828cd29774a729bf4d4425fe192972',
|
'md5': 'fd828cd29774a729bf4d4425fe192972',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '21039',
|
'id': '21039',
|
||||||
@@ -30,74 +20,4 @@ class AftenpostenIE(InfoExtractor):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
display_id = self._match_id(url)
|
return self.url_result('xstream:ap:%s' % self._match_id(url), 'Xstream')
|
||||||
|
|
||||||
webpage = self._download_webpage(url, display_id)
|
|
||||||
|
|
||||||
video_id = self._html_search_regex(
|
|
||||||
r'data-xs-id="(\d+)"', webpage, 'video id')
|
|
||||||
|
|
||||||
data = self._download_xml(
|
|
||||||
'http://frontend.xstream.dk/ap/feed/video/?platform=web&id=%s' % video_id, video_id)
|
|
||||||
|
|
||||||
NS_MAP = {
|
|
||||||
'atom': 'http://www.w3.org/2005/Atom',
|
|
||||||
'xt': 'http://xstream.dk/',
|
|
||||||
'media': 'http://search.yahoo.com/mrss/',
|
|
||||||
}
|
|
||||||
|
|
||||||
entry = data.find(xpath_with_ns('./atom:entry', NS_MAP))
|
|
||||||
|
|
||||||
title = xpath_text(
|
|
||||||
entry, xpath_with_ns('./atom:title', NS_MAP), 'title')
|
|
||||||
description = xpath_text(
|
|
||||||
entry, xpath_with_ns('./atom:summary', NS_MAP), 'description')
|
|
||||||
timestamp = parse_iso8601(xpath_text(
|
|
||||||
entry, xpath_with_ns('./atom:published', NS_MAP), 'upload date'))
|
|
||||||
|
|
||||||
formats = []
|
|
||||||
media_group = entry.find(xpath_with_ns('./media:group', NS_MAP))
|
|
||||||
for media_content in media_group.findall(xpath_with_ns('./media:content', NS_MAP)):
|
|
||||||
media_url = media_content.get('url')
|
|
||||||
if not media_url:
|
|
||||||
continue
|
|
||||||
tbr = int_or_none(media_content.get('bitrate'))
|
|
||||||
mobj = re.search(r'^(?P<url>rtmp://[^/]+/(?P<app>[^/]+))/(?P<playpath>.+)$', media_url)
|
|
||||||
if mobj:
|
|
||||||
formats.append({
|
|
||||||
'url': mobj.group('url'),
|
|
||||||
'play_path': 'mp4:%s' % mobj.group('playpath'),
|
|
||||||
'app': mobj.group('app'),
|
|
||||||
'ext': 'flv',
|
|
||||||
'tbr': tbr,
|
|
||||||
'format_id': 'rtmp-%d' % tbr,
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
formats.append({
|
|
||||||
'url': media_url,
|
|
||||||
'tbr': tbr,
|
|
||||||
})
|
|
||||||
self._sort_formats(formats)
|
|
||||||
|
|
||||||
link = find_xpath_attr(
|
|
||||||
entry, xpath_with_ns('./atom:link', NS_MAP), 'rel', 'original')
|
|
||||||
if link is not None:
|
|
||||||
formats.append({
|
|
||||||
'url': link.get('href'),
|
|
||||||
'format_id': link.get('rel'),
|
|
||||||
})
|
|
||||||
|
|
||||||
thumbnails = [{
|
|
||||||
'url': splash.get('url'),
|
|
||||||
'width': int_or_none(splash.get('width')),
|
|
||||||
'height': int_or_none(splash.get('height')),
|
|
||||||
} for splash in media_group.findall(xpath_with_ns('./xt:splash', NS_MAP))]
|
|
||||||
|
|
||||||
return {
|
|
||||||
'id': video_id,
|
|
||||||
'title': title,
|
|
||||||
'description': description,
|
|
||||||
'timestamp': timestamp,
|
|
||||||
'formats': formats,
|
|
||||||
'thumbnails': thumbnails,
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,14 +2,15 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from ..utils import int_or_none
|
||||||
|
|
||||||
|
|
||||||
class AftonbladetIE(InfoExtractor):
|
class AftonbladetIE(InfoExtractor):
|
||||||
_VALID_URL = r'^http://tv\.aftonbladet\.se/webbtv.+?(?P<video_id>article[0-9]+)\.ab(?:$|[?#])'
|
_VALID_URL = r'http://tv\.aftonbladet\.se/abtv/articles/(?P<id>[0-9]+)'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://tv.aftonbladet.se/webbtv/nyheter/vetenskap/rymden/article36015.ab',
|
'url': 'http://tv.aftonbladet.se/abtv/articles/36015',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'article36015',
|
'id': '36015',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Vulkanutbrott i rymden - nu släpper NASA bilderna',
|
'title': 'Vulkanutbrott i rymden - nu släpper NASA bilderna',
|
||||||
'description': 'Jupiters måne mest aktiv av alla himlakroppar',
|
'description': 'Jupiters måne mest aktiv av alla himlakroppar',
|
||||||
@@ -24,8 +25,9 @@ class AftonbladetIE(InfoExtractor):
|
|||||||
|
|
||||||
# find internal video meta data
|
# find internal video meta data
|
||||||
meta_url = 'http://aftonbladet-play.drlib.aptoma.no/video/%s.json'
|
meta_url = 'http://aftonbladet-play.drlib.aptoma.no/video/%s.json'
|
||||||
internal_meta_id = self._html_search_regex(
|
player_config = self._parse_json(self._html_search_regex(
|
||||||
r'data-aptomaId="([\w\d]+)"', webpage, 'internal_meta_id')
|
r'data-player-config="([^"]+)"', webpage, 'player config'), video_id)
|
||||||
|
internal_meta_id = player_config['videoId']
|
||||||
internal_meta_url = meta_url % internal_meta_id
|
internal_meta_url = meta_url % internal_meta_id
|
||||||
internal_meta_json = self._download_json(
|
internal_meta_json = self._download_json(
|
||||||
internal_meta_url, video_id, 'Downloading video meta data')
|
internal_meta_url, video_id, 'Downloading video meta data')
|
||||||
@@ -43,9 +45,9 @@ class AftonbladetIE(InfoExtractor):
|
|||||||
formats.append({
|
formats.append({
|
||||||
'url': 'http://%s:%d/%s/%s' % (p['address'], p['port'], p['path'], p['filename']),
|
'url': 'http://%s:%d/%s/%s' % (p['address'], p['port'], p['path'], p['filename']),
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'width': fmt['width'],
|
'width': int_or_none(fmt.get('width')),
|
||||||
'height': fmt['height'],
|
'height': int_or_none(fmt.get('height')),
|
||||||
'tbr': fmt['bitrate'],
|
'tbr': int_or_none(fmt.get('bitrate')),
|
||||||
'protocol': 'http',
|
'protocol': 'http',
|
||||||
})
|
})
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
@@ -54,9 +56,9 @@ class AftonbladetIE(InfoExtractor):
|
|||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': internal_meta_json['title'],
|
'title': internal_meta_json['title'],
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
'thumbnail': internal_meta_json['imageUrl'],
|
'thumbnail': internal_meta_json.get('imageUrl'),
|
||||||
'description': internal_meta_json['shortPreamble'],
|
'description': internal_meta_json.get('shortPreamble'),
|
||||||
'timestamp': internal_meta_json['timePublished'],
|
'timestamp': int_or_none(internal_meta_json.get('timePublished')),
|
||||||
'duration': internal_meta_json['duration'],
|
'duration': int_or_none(internal_meta_json.get('duration')),
|
||||||
'view_count': internal_meta_json['views'],
|
'view_count': int_or_none(internal_meta_json.get('views')),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class ArchiveOrgIE(InfoExtractor):
|
|||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
json_url = url + ('?' if '?' in url else '&') + 'output=json'
|
json_url = url + ('&' if '?' in url else '?') + 'output=json'
|
||||||
data = self._download_json(json_url, video_id)
|
data = self._download_json(json_url, video_id)
|
||||||
|
|
||||||
def get_optional(data_dict, field):
|
def get_optional(data_dict, field):
|
||||||
|
|||||||
@@ -50,6 +50,9 @@ class ARDMediathekIE(InfoExtractor):
|
|||||||
if '>Der gewünschte Beitrag ist nicht mehr verfügbar.<' in webpage:
|
if '>Der gewünschte Beitrag ist nicht mehr verfügbar.<' in webpage:
|
||||||
raise ExtractorError('Video %s is no longer available' % video_id, expected=True)
|
raise ExtractorError('Video %s is no longer available' % video_id, expected=True)
|
||||||
|
|
||||||
|
if 'Diese Sendung ist für Jugendliche unter 12 Jahren nicht geeignet. Der Clip ist deshalb nur von 20 bis 6 Uhr verfügbar.' in webpage:
|
||||||
|
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 = parse_xml(webpage)
|
||||||
if doc.tag == 'rss':
|
if doc.tag == 'rss':
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ from .common import InfoExtractor
|
|||||||
from ..utils import (
|
from ..utils import (
|
||||||
find_xpath_attr,
|
find_xpath_attr,
|
||||||
unified_strdate,
|
unified_strdate,
|
||||||
get_element_by_id,
|
|
||||||
get_element_by_attribute,
|
get_element_by_attribute,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
qualities,
|
qualities,
|
||||||
@@ -146,6 +145,7 @@ class ArteTVPlus7IE(InfoExtractor):
|
|||||||
|
|
||||||
formats.append(format)
|
formats.append(format)
|
||||||
|
|
||||||
|
self._check_formats(formats, video_id)
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
info_dict['formats'] = formats
|
info_dict['formats'] = formats
|
||||||
@@ -194,7 +194,9 @@ class ArteTVFutureIE(ArteTVPlus7IE):
|
|||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
anchor_id, lang = self._extract_url_info(url)
|
anchor_id, lang = self._extract_url_info(url)
|
||||||
webpage = self._download_webpage(url, anchor_id)
|
webpage = self._download_webpage(url, anchor_id)
|
||||||
row = get_element_by_id(anchor_id, webpage)
|
row = self._search_regex(
|
||||||
|
r'(?s)id="%s"[^>]*>.+?(<div[^>]*arte_vp_url[^>]*>)' % anchor_id,
|
||||||
|
webpage, 'row')
|
||||||
return self._extract_from_webpage(row, anchor_id, lang)
|
return self._extract_from_webpage(row, anchor_id, lang)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
69
youtube_dl/extractor/baidu.py
Normal file
69
youtube_dl/extractor/baidu.py
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..compat import compat_urlparse
|
||||||
|
|
||||||
|
|
||||||
|
class BaiduVideoIE(InfoExtractor):
|
||||||
|
IE_DESC = '百度视频'
|
||||||
|
_VALID_URL = r'http://v\.baidu\.com/(?P<type>[a-z]+)/(?P<id>\d+)\.htm'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://v.baidu.com/comic/1069.htm?frp=bdbrand&q=%E4%B8%AD%E5%8D%8E%E5%B0%8F%E5%BD%93%E5%AE%B6',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '1069',
|
||||||
|
'title': '中华小当家 TV版 (全52集)',
|
||||||
|
'description': 'md5:395a419e41215e531c857bb037bbaf80',
|
||||||
|
},
|
||||||
|
'playlist_count': 52,
|
||||||
|
}, {
|
||||||
|
'url': 'http://v.baidu.com/show/11595.htm?frp=bdbrand',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '11595',
|
||||||
|
'title': 're:^奔跑吧兄弟',
|
||||||
|
'description': 'md5:1bf88bad6d850930f542d51547c089b8',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 3,
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
playlist_id = mobj.group('id')
|
||||||
|
category = category2 = mobj.group('type')
|
||||||
|
if category == 'show':
|
||||||
|
category2 = 'tvshow'
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, playlist_id)
|
||||||
|
|
||||||
|
playlist_title = self._html_search_regex(
|
||||||
|
r'title\s*:\s*(["\'])(?P<title>[^\']+)\1', webpage,
|
||||||
|
'playlist title', group='title')
|
||||||
|
playlist_description = self._html_search_regex(
|
||||||
|
r'<input[^>]+class="j-data-intro"[^>]+value="([^"]+)"/>', webpage,
|
||||||
|
playlist_id, 'playlist description')
|
||||||
|
|
||||||
|
site = self._html_search_regex(
|
||||||
|
r'filterSite\s*:\s*["\']([^"]*)["\']', webpage,
|
||||||
|
'primary provider site')
|
||||||
|
api_result = self._download_json(
|
||||||
|
'http://v.baidu.com/%s_intro/?dtype=%sPlayUrl&id=%s&site=%s' % (
|
||||||
|
category, category2, playlist_id, site),
|
||||||
|
playlist_id, 'Get playlist links')
|
||||||
|
|
||||||
|
entries = []
|
||||||
|
for episode in api_result[0]['episodes']:
|
||||||
|
episode_id = '%s_%s' % (playlist_id, episode['episode'])
|
||||||
|
|
||||||
|
redirect_page = self._download_webpage(
|
||||||
|
compat_urlparse.urljoin(url, episode['url']), episode_id,
|
||||||
|
note='Download Baidu redirect page')
|
||||||
|
real_url = self._html_search_regex(
|
||||||
|
r'location\.replace\("([^"]+)"\)', redirect_page, 'real URL')
|
||||||
|
|
||||||
|
entries.append(self.url_result(
|
||||||
|
real_url, video_title=episode['single_title']))
|
||||||
|
|
||||||
|
return self.playlist_result(
|
||||||
|
entries, playlist_id, playlist_title, playlist_description)
|
||||||
@@ -1,12 +1,18 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import json
|
|
||||||
import itertools
|
import itertools
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import (
|
||||||
|
compat_urllib_parse,
|
||||||
compat_urllib_request,
|
compat_urllib_request,
|
||||||
|
compat_str,
|
||||||
|
)
|
||||||
|
from ..utils import (
|
||||||
|
ExtractorError,
|
||||||
|
int_or_none,
|
||||||
|
float_or_none,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -14,6 +20,8 @@ class BambuserIE(InfoExtractor):
|
|||||||
IE_NAME = 'bambuser'
|
IE_NAME = 'bambuser'
|
||||||
_VALID_URL = r'https?://bambuser\.com/v/(?P<id>\d+)'
|
_VALID_URL = r'https?://bambuser\.com/v/(?P<id>\d+)'
|
||||||
_API_KEY = '005f64509e19a868399060af746a00aa'
|
_API_KEY = '005f64509e19a868399060af746a00aa'
|
||||||
|
_LOGIN_URL = 'https://bambuser.com/user'
|
||||||
|
_NETRC_MACHINE = 'bambuser'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://bambuser.com/v/4050584',
|
'url': 'http://bambuser.com/v/4050584',
|
||||||
@@ -26,6 +34,9 @@ class BambuserIE(InfoExtractor):
|
|||||||
'duration': 3741,
|
'duration': 3741,
|
||||||
'uploader': 'pixelversity',
|
'uploader': 'pixelversity',
|
||||||
'uploader_id': '344706',
|
'uploader_id': '344706',
|
||||||
|
'timestamp': 1382976692,
|
||||||
|
'upload_date': '20131028',
|
||||||
|
'view_count': int,
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
# It doesn't respect the 'Range' header, it would download the whole video
|
# It doesn't respect the 'Range' header, it would download the whole video
|
||||||
@@ -34,23 +45,60 @@ class BambuserIE(InfoExtractor):
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def _login(self):
|
||||||
|
(username, password) = self._get_login_info()
|
||||||
|
if username is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
login_form = {
|
||||||
|
'form_id': 'user_login',
|
||||||
|
'op': 'Log in',
|
||||||
|
'name': username,
|
||||||
|
'pass': password,
|
||||||
|
}
|
||||||
|
|
||||||
|
request = compat_urllib_request.Request(
|
||||||
|
self._LOGIN_URL, compat_urllib_parse.urlencode(login_form).encode('utf-8'))
|
||||||
|
request.add_header('Referer', self._LOGIN_URL)
|
||||||
|
response = self._download_webpage(
|
||||||
|
request, None, 'Logging in as %s' % username)
|
||||||
|
|
||||||
|
login_error = self._html_search_regex(
|
||||||
|
r'(?s)<div class="messages error">(.+?)</div>',
|
||||||
|
response, 'login error', default=None)
|
||||||
|
if login_error:
|
||||||
|
raise ExtractorError(
|
||||||
|
'Unable to login: %s' % login_error, expected=True)
|
||||||
|
|
||||||
|
def _real_initialize(self):
|
||||||
|
self._login()
|
||||||
|
|
||||||
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')
|
|
||||||
info_url = ('http://player-c.api.bambuser.com/getVideo.json?'
|
info = self._download_json(
|
||||||
'&api_key=%s&vid=%s' % (self._API_KEY, video_id))
|
'http://player-c.api.bambuser.com/getVideo.json?api_key=%s&vid=%s'
|
||||||
info_json = self._download_webpage(info_url, video_id)
|
% (self._API_KEY, video_id), video_id)
|
||||||
info = json.loads(info_json)['result']
|
|
||||||
|
error = info.get('error')
|
||||||
|
if error:
|
||||||
|
raise ExtractorError(
|
||||||
|
'%s returned error: %s' % (self.IE_NAME, error), expected=True)
|
||||||
|
|
||||||
|
result = info['result']
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': info['title'],
|
'title': result['title'],
|
||||||
'url': info['url'],
|
'url': result['url'],
|
||||||
'thumbnail': info.get('preview'),
|
'thumbnail': result.get('preview'),
|
||||||
'duration': int(info['length']),
|
'duration': int_or_none(result.get('length')),
|
||||||
'view_count': int(info['views_total']),
|
'uploader': result.get('username'),
|
||||||
'uploader': info['username'],
|
'uploader_id': compat_str(result.get('owner', {}).get('uid')),
|
||||||
'uploader_id': info['owner']['uid'],
|
'timestamp': int_or_none(result.get('created')),
|
||||||
|
'fps': float_or_none(result.get('framerate')),
|
||||||
|
'view_count': int_or_none(result.get('views_total')),
|
||||||
|
'comment_count': int_or_none(result.get('comment_count')),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ class BandcampIE(InfoExtractor):
|
|||||||
|
|
||||||
download_link = m_download.group(1)
|
download_link = m_download.group(1)
|
||||||
video_id = self._search_regex(
|
video_id = self._search_regex(
|
||||||
r'(?ms)var TralbumData = {.*?id: (?P<id>\d+),?$',
|
r'(?ms)var TralbumData = .*?[{,]\s*id: (?P<id>\d+),?$',
|
||||||
webpage, 'video id')
|
webpage, 'video id')
|
||||||
|
|
||||||
download_webpage = self._download_webpage(download_link, video_id, 'Downloading free downloads page')
|
download_webpage = self._download_webpage(download_link, video_id, 'Downloading free downloads page')
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ from __future__ import unicode_literals
|
|||||||
import xml.etree.ElementTree
|
import xml.etree.ElementTree
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import ExtractorError
|
from ..utils import (
|
||||||
|
ExtractorError,
|
||||||
|
int_or_none,
|
||||||
|
)
|
||||||
from ..compat import compat_HTTPError
|
from ..compat import compat_HTTPError
|
||||||
|
|
||||||
|
|
||||||
@@ -112,6 +115,34 @@ class BBCCoUkIE(InfoExtractor):
|
|||||||
# rtmp download
|
# rtmp download
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.bbc.co.uk/iplayer/episode/b054fn09/ad/natural-world-20152016-2-super-powered-owls',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'p02n76xf',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Natural World, 2015-2016: 2. Super Powered Owls',
|
||||||
|
'description': 'md5:e4db5c937d0e95a7c6b5e654d429183d',
|
||||||
|
'duration': 3540,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# rtmp download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
'skip': 'geolocation',
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.bbc.co.uk/iplayer/episode/b05zmgwn/royal-academy-summer-exhibition',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'b05zmgw1',
|
||||||
|
'ext': 'flv',
|
||||||
|
'description': 'Kirsty Wark and Morgan Quaintance visit the Royal Academy as it prepares for its annual artistic extravaganza, meeting people who have come together to make the show unique.',
|
||||||
|
'title': 'Royal Academy Summer Exhibition',
|
||||||
|
'duration': 3540,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# rtmp download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
'skip': 'geolocation',
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.bbc.co.uk/iplayer/playlist/p01dvks4',
|
'url': 'http://www.bbc.co.uk/iplayer/playlist/p01dvks4',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
@@ -220,26 +251,11 @@ class BBCCoUkIE(InfoExtractor):
|
|||||||
for connection in self._extract_connections(media):
|
for connection in self._extract_connections(media):
|
||||||
captions = self._download_xml(connection.get('href'), programme_id, 'Downloading captions')
|
captions = self._download_xml(connection.get('href'), programme_id, 'Downloading captions')
|
||||||
lang = captions.get('{http://www.w3.org/XML/1998/namespace}lang', 'en')
|
lang = captions.get('{http://www.w3.org/XML/1998/namespace}lang', 'en')
|
||||||
ps = captions.findall('./{0}body/{0}div/{0}p'.format('{http://www.w3.org/2006/10/ttaf1}'))
|
|
||||||
srt = ''
|
|
||||||
|
|
||||||
def _extract_text(p):
|
|
||||||
if p.text is not None:
|
|
||||||
stripped_text = p.text.strip()
|
|
||||||
if stripped_text:
|
|
||||||
return stripped_text
|
|
||||||
return ' '.join(span.text.strip() for span in p.findall('{http://www.w3.org/2006/10/ttaf1}span'))
|
|
||||||
for pos, p in enumerate(ps):
|
|
||||||
srt += '%s\r\n%s --> %s\r\n%s\r\n\r\n' % (str(pos), p.get('begin'), p.get('end'), _extract_text(p))
|
|
||||||
subtitles[lang] = [
|
subtitles[lang] = [
|
||||||
{
|
{
|
||||||
'url': connection.get('href'),
|
'url': connection.get('href'),
|
||||||
'ext': 'ttml',
|
'ext': 'ttml',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
'data': srt,
|
|
||||||
'ext': 'srt',
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
return subtitles
|
return subtitles
|
||||||
|
|
||||||
@@ -250,7 +266,7 @@ class BBCCoUkIE(InfoExtractor):
|
|||||||
programme_id, 'Downloading media selection XML')
|
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 == 403:
|
||||||
media_selection = xml.etree.ElementTree.fromstring(ee.cause.read().encode('utf-8'))
|
media_selection = xml.etree.ElementTree.fromstring(ee.cause.read().decode('utf-8'))
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
@@ -326,16 +342,27 @@ 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
|
||||||
|
|
||||||
|
tviplayer = self._search_regex(
|
||||||
|
r'mediator\.bind\(({.+?})\s*,\s*document\.getElementById',
|
||||||
|
webpage, 'player', default=None)
|
||||||
|
|
||||||
|
if tviplayer:
|
||||||
|
player = self._parse_json(tviplayer, group_id).get('player', {})
|
||||||
|
duration = int_or_none(player.get('duration'))
|
||||||
|
programme_id = player.get('vpid')
|
||||||
|
|
||||||
|
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*"([\da-z]{8})"', webpage, 'vpid', fatal=False, default=None)
|
||||||
|
|
||||||
if programme_id:
|
if programme_id:
|
||||||
player = self._download_json(
|
|
||||||
'http://www.bbc.co.uk/iplayer/episode/%s.json' % group_id,
|
|
||||||
group_id)['jsConf']['player']
|
|
||||||
title = player['title']
|
|
||||||
description = player['subtitle']
|
|
||||||
duration = player['duration']
|
|
||||||
formats, subtitles = self._download_media_selector(programme_id)
|
formats, subtitles = self._download_media_selector(programme_id)
|
||||||
|
title = self._og_search_title(webpage)
|
||||||
|
description = self._search_regex(
|
||||||
|
r'<p class="[^"]*medium-description[^"]*">([^<]+)</p>',
|
||||||
|
webpage, 'description', fatal=False)
|
||||||
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)
|
||||||
|
|
||||||
@@ -345,6 +372,7 @@ class BBCCoUkIE(InfoExtractor):
|
|||||||
'id': programme_id,
|
'id': programme_id,
|
||||||
'title': title,
|
'title': title,
|
||||||
'description': description,
|
'description': description,
|
||||||
|
'thumbnail': self._og_search_thumbnail(webpage, default=None),
|
||||||
'duration': duration,
|
'duration': duration,
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
'subtitles': subtitles,
|
'subtitles': subtitles,
|
||||||
|
|||||||
103
youtube_dl/extractor/beatportpro.py
Normal file
103
youtube_dl/extractor/beatportpro.py
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
# 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 BeatportProIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://pro\.beatport\.com/track/(?P<display_id>[^/]+)/(?P<id>[0-9]+)'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://pro.beatport.com/track/synesthesia-original-mix/5379371',
|
||||||
|
'md5': 'b3c34d8639a2f6a7f734382358478887',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '5379371',
|
||||||
|
'display_id': 'synesthesia-original-mix',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Froxic - Synesthesia (Original Mix)',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'https://pro.beatport.com/track/love-and-war-original-mix/3756896',
|
||||||
|
'md5': 'e44c3025dfa38c6577fbaeb43da43514',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '3756896',
|
||||||
|
'display_id': 'love-and-war-original-mix',
|
||||||
|
'ext': 'mp3',
|
||||||
|
'title': 'Wolfgang Gartner - Love & War (Original Mix)',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'https://pro.beatport.com/track/birds-original-mix/4991738',
|
||||||
|
'md5': 'a1fd8e8046de3950fd039304c186c05f',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '4991738',
|
||||||
|
'display_id': 'birds-original-mix',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': "Tos, Middle Milk, Mumblin' Johnsson - Birds (Original Mix)",
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
track_id = mobj.group('id')
|
||||||
|
display_id = mobj.group('display_id')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
|
||||||
|
playables = self._parse_json(
|
||||||
|
self._search_regex(
|
||||||
|
r'window\.Playables\s*=\s*({.+?});', webpage,
|
||||||
|
'playables info', flags=re.DOTALL),
|
||||||
|
track_id)
|
||||||
|
|
||||||
|
track = next(t for t in playables['tracks'] if t['id'] == int(track_id))
|
||||||
|
|
||||||
|
title = ', '.join((a['name'] for a in track['artists'])) + ' - ' + track['name']
|
||||||
|
if track['mix']:
|
||||||
|
title += ' (' + track['mix'] + ')'
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
for ext, info in track['preview'].items():
|
||||||
|
if not info['url']:
|
||||||
|
continue
|
||||||
|
fmt = {
|
||||||
|
'url': info['url'],
|
||||||
|
'ext': ext,
|
||||||
|
'format_id': ext,
|
||||||
|
'vcodec': 'none',
|
||||||
|
}
|
||||||
|
if ext == 'mp3':
|
||||||
|
fmt['preference'] = 0
|
||||||
|
fmt['acodec'] = 'mp3'
|
||||||
|
fmt['abr'] = 96
|
||||||
|
fmt['asr'] = 44100
|
||||||
|
elif ext == 'mp4':
|
||||||
|
fmt['preference'] = 1
|
||||||
|
fmt['acodec'] = 'aac'
|
||||||
|
fmt['abr'] = 96
|
||||||
|
fmt['asr'] = 44100
|
||||||
|
formats.append(fmt)
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
images = []
|
||||||
|
for name, info in track['images'].items():
|
||||||
|
image_url = info.get('url')
|
||||||
|
if name == 'dynamic' or not image_url:
|
||||||
|
continue
|
||||||
|
image = {
|
||||||
|
'id': name,
|
||||||
|
'url': image_url,
|
||||||
|
'height': int_or_none(info.get('height')),
|
||||||
|
'width': int_or_none(info.get('width')),
|
||||||
|
}
|
||||||
|
images.append(image)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': compat_str(track.get('id')) or track_id,
|
||||||
|
'display_id': track.get('slug') or display_id,
|
||||||
|
'title': title,
|
||||||
|
'formats': formats,
|
||||||
|
'thumbnails': images,
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
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_unquote
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
xpath_text,
|
xpath_text,
|
||||||
xpath_with_ns,
|
xpath_with_ns,
|
||||||
@@ -16,11 +16,11 @@ class BetIE(InfoExtractor):
|
|||||||
{
|
{
|
||||||
'url': 'http://www.bet.com/news/politics/2014/12/08/in-bet-exclusive-obama-talks-race-and-racism.html',
|
'url': 'http://www.bet.com/news/politics/2014/12/08/in-bet-exclusive-obama-talks-race-and-racism.html',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '740ab250-bb94-4a8a-8787-fe0de7c74471',
|
'id': 'news/national/2014/a-conversation-with-president-obama',
|
||||||
'display_id': 'in-bet-exclusive-obama-talks-race-and-racism',
|
'display_id': 'in-bet-exclusive-obama-talks-race-and-racism',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'title': 'BET News Presents: A Conversation With President Obama',
|
'title': 'A Conversation With President Obama',
|
||||||
'description': 'md5:5a88d8ae912c1b33e090290af7ec33c6',
|
'description': 'md5:699d0652a350cf3e491cd15cc745b5da',
|
||||||
'duration': 1534,
|
'duration': 1534,
|
||||||
'timestamp': 1418075340,
|
'timestamp': 1418075340,
|
||||||
'upload_date': '20141208',
|
'upload_date': '20141208',
|
||||||
@@ -35,7 +35,7 @@ class BetIE(InfoExtractor):
|
|||||||
{
|
{
|
||||||
'url': 'http://www.bet.com/video/news/national/2014/justice-for-ferguson-a-community-reacts.html',
|
'url': 'http://www.bet.com/video/news/national/2014/justice-for-ferguson-a-community-reacts.html',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'bcd1b1df-673a-42cf-8d01-b282db608f2d',
|
'id': 'news/national/2014/justice-for-ferguson-a-community-reacts',
|
||||||
'display_id': 'justice-for-ferguson-a-community-reacts',
|
'display_id': 'justice-for-ferguson-a-community-reacts',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'title': 'Justice for Ferguson: A Community Reacts',
|
'title': 'Justice for Ferguson: A Community Reacts',
|
||||||
@@ -57,10 +57,13 @@ class BetIE(InfoExtractor):
|
|||||||
display_id = self._match_id(url)
|
display_id = self._match_id(url)
|
||||||
webpage = self._download_webpage(url, display_id)
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
|
||||||
media_url = compat_urllib_parse.unquote(self._search_regex(
|
media_url = compat_urllib_parse_unquote(self._search_regex(
|
||||||
[r'mediaURL\s*:\s*"([^"]+)"', r"var\s+mrssMediaUrl\s*=\s*'([^']+)'"],
|
[r'mediaURL\s*:\s*"([^"]+)"', r"var\s+mrssMediaUrl\s*=\s*'([^']+)'"],
|
||||||
webpage, 'media URL'))
|
webpage, 'media URL'))
|
||||||
|
|
||||||
|
video_id = self._search_regex(
|
||||||
|
r'/video/(.*)/_jcr_content/', media_url, 'video id')
|
||||||
|
|
||||||
mrss = self._download_xml(media_url, display_id)
|
mrss = self._download_xml(media_url, display_id)
|
||||||
|
|
||||||
item = mrss.find('./channel/item')
|
item = mrss.find('./channel/item')
|
||||||
@@ -75,8 +78,6 @@ class BetIE(InfoExtractor):
|
|||||||
description = xpath_text(
|
description = xpath_text(
|
||||||
item, './description', 'description', fatal=False)
|
item, './description', 'description', fatal=False)
|
||||||
|
|
||||||
video_id = xpath_text(item, './guid', 'video id', fatal=False)
|
|
||||||
|
|
||||||
timestamp = parse_iso8601(xpath_text(
|
timestamp = parse_iso8601(xpath_text(
|
||||||
item, xpath_with_ns('./dc:date', NS_MAP),
|
item, xpath_with_ns('./dc:date', NS_MAP),
|
||||||
'upload date', fatal=False))
|
'upload date', fatal=False))
|
||||||
|
|||||||
@@ -2,7 +2,10 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import int_or_none
|
from ..utils import (
|
||||||
|
int_or_none,
|
||||||
|
fix_xml_ampersands,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class BildIE(InfoExtractor):
|
class BildIE(InfoExtractor):
|
||||||
@@ -15,7 +18,7 @@ class BildIE(InfoExtractor):
|
|||||||
'id': '38184146',
|
'id': '38184146',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'BILD hat sie getestet',
|
'title': 'BILD hat sie getestet',
|
||||||
'thumbnail': 'http://bilder.bild.de/fotos/stand-das-koennen-die-neuen-ipads-38184138/Bild/1.bild.jpg',
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
'duration': 196,
|
'duration': 196,
|
||||||
'description': 'Mit dem iPad Air 2 und dem iPad Mini 3 hat Apple zwei neue Tablet-Modelle präsentiert. BILD-Reporter Sven Stein durfte die Geräte bereits testen. ',
|
'description': 'Mit dem iPad Air 2 und dem iPad Mini 3 hat Apple zwei neue Tablet-Modelle präsentiert. BILD-Reporter Sven Stein durfte die Geräte bereits testen. ',
|
||||||
}
|
}
|
||||||
@@ -25,7 +28,7 @@ class BildIE(InfoExtractor):
|
|||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
xml_url = url.split(".bild.html")[0] + ",view=xml.bild.xml"
|
xml_url = url.split(".bild.html")[0] + ",view=xml.bild.xml"
|
||||||
doc = self._download_xml(xml_url, video_id)
|
doc = self._download_xml(xml_url, video_id, transform_source=fix_xml_ampersands)
|
||||||
|
|
||||||
duration = int_or_none(doc.attrib.get('duration'), scale=1000)
|
duration = int_or_none(doc.attrib.get('duration'), scale=1000)
|
||||||
|
|
||||||
|
|||||||
@@ -2,34 +2,47 @@
|
|||||||
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 ..utils import (
|
from ..utils import (
|
||||||
int_or_none,
|
int_or_none,
|
||||||
unified_strdate,
|
unified_strdate,
|
||||||
|
ExtractorError,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
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>[0-9]+)/'
|
||||||
|
|
||||||
_TEST = {
|
_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',
|
'id': '1074402_part1',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'title': '【金坷垃】金泡沫',
|
'title': '【金坷垃】金泡沫',
|
||||||
'duration': 308,
|
'duration': 308,
|
||||||
'upload_date': '20140420',
|
'upload_date': '20140420',
|
||||||
'thumbnail': 're:^https?://.+\.jpg',
|
'thumbnail': 're:^https?://.+\.jpg',
|
||||||
},
|
},
|
||||||
}
|
}, {
|
||||||
|
'url': 'http://www.bilibili.com/video/av1041170/',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '1041170',
|
||||||
|
'title': '【BD1080P】刀语【诸神&异域】',
|
||||||
|
},
|
||||||
|
'playlist_count': 9,
|
||||||
|
}]
|
||||||
|
|
||||||
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)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
if self._search_regex(r'(此视频不存在或被删除)', webpage, 'error message', default=None):
|
||||||
|
raise ExtractorError('The video does not exist or was deleted', expected=True)
|
||||||
video_code = self._search_regex(
|
video_code = self._search_regex(
|
||||||
r'(?s)<div itemprop="video".*?>(.*?)</div>', webpage, 'video code')
|
r'(?s)<div itemprop="video".*?>(.*?)</div>', webpage, 'video code')
|
||||||
|
|
||||||
@@ -54,19 +67,22 @@ class BiliBiliIE(InfoExtractor):
|
|||||||
|
|
||||||
cid = self._search_regex(r'cid=(\d+)', webpage, 'cid')
|
cid = self._search_regex(r'cid=(\d+)', webpage, 'cid')
|
||||||
|
|
||||||
lq_doc = self._download_xml(
|
entries = []
|
||||||
|
|
||||||
|
lq_page = self._download_webpage(
|
||||||
'http://interface.bilibili.com/v_cdn_play?appkey=1&cid=%s' % cid,
|
'http://interface.bilibili.com/v_cdn_play?appkey=1&cid=%s' % cid,
|
||||||
video_id,
|
video_id,
|
||||||
note='Downloading LQ video info'
|
note='Downloading LQ video info'
|
||||||
)
|
)
|
||||||
lq_durl = lq_doc.find('./durl')
|
try:
|
||||||
formats = [{
|
err_info = json.loads(lq_page)
|
||||||
'format_id': 'lq',
|
raise ExtractorError(
|
||||||
'quality': 1,
|
'BiliBili said: ' + err_info['error_text'], expected=True)
|
||||||
'url': lq_durl.find('./url').text,
|
except ValueError:
|
||||||
'filesize': int_or_none(
|
pass
|
||||||
lq_durl.find('./size'), get_attr='text'),
|
|
||||||
}]
|
lq_doc = ET.fromstring(lq_page)
|
||||||
|
lq_durls = lq_doc.findall('./durl')
|
||||||
|
|
||||||
hq_doc = self._download_xml(
|
hq_doc = self._download_xml(
|
||||||
'http://interface.bilibili.com/playurl?appkey=1&cid=%s' % cid,
|
'http://interface.bilibili.com/playurl?appkey=1&cid=%s' % cid,
|
||||||
@@ -75,7 +91,21 @@ class BiliBiliIE(InfoExtractor):
|
|||||||
fatal=False,
|
fatal=False,
|
||||||
)
|
)
|
||||||
if hq_doc is not False:
|
if hq_doc is not False:
|
||||||
hq_durl = hq_doc.find('./durl')
|
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 = [{
|
||||||
|
'format_id': 'lq',
|
||||||
|
'quality': 1,
|
||||||
|
'url': lq_durl.find('./url').text,
|
||||||
|
'filesize': int_or_none(
|
||||||
|
lq_durl.find('./size'), get_attr='text'),
|
||||||
|
}]
|
||||||
|
if hq_durl is not None:
|
||||||
formats.append({
|
formats.append({
|
||||||
'format_id': 'hq',
|
'format_id': 'hq',
|
||||||
'quality': 2,
|
'quality': 2,
|
||||||
@@ -84,13 +114,22 @@ class BiliBiliIE(InfoExtractor):
|
|||||||
'filesize': int_or_none(
|
'filesize': int_or_none(
|
||||||
hq_durl.find('./size'), get_attr='text'),
|
hq_durl.find('./size'), get_attr='text'),
|
||||||
})
|
})
|
||||||
|
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
return {
|
|
||||||
'id': video_id,
|
entries.append({
|
||||||
|
'id': '%s_part%d' % (video_id, i),
|
||||||
'title': title,
|
'title': title,
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
'duration': duration,
|
'duration': duration,
|
||||||
'upload_date': upload_date,
|
'upload_date': upload_date,
|
||||||
'thumbnail': thumbnail,
|
'thumbnail': thumbnail,
|
||||||
|
})
|
||||||
|
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
return {
|
||||||
|
'_type': 'multi_video',
|
||||||
|
'entries': entries,
|
||||||
|
'id': video_id,
|
||||||
|
'title': title
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ from ..utils import (
|
|||||||
int_or_none,
|
int_or_none,
|
||||||
parse_iso8601,
|
parse_iso8601,
|
||||||
unescapeHTML,
|
unescapeHTML,
|
||||||
|
xpath_text,
|
||||||
|
xpath_with_ns,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -23,10 +25,10 @@ class BlipTVIE(InfoExtractor):
|
|||||||
_TESTS = [
|
_TESTS = [
|
||||||
{
|
{
|
||||||
'url': 'http://blip.tv/cbr/cbr-exclusive-gotham-city-imposters-bats-vs-jokerz-short-3-5796352',
|
'url': 'http://blip.tv/cbr/cbr-exclusive-gotham-city-imposters-bats-vs-jokerz-short-3-5796352',
|
||||||
'md5': 'c6934ad0b6acf2bd920720ec888eb812',
|
'md5': '80baf1ec5c3d2019037c1c707d676b9f',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '5779306',
|
'id': '5779306',
|
||||||
'ext': 'mov',
|
'ext': 'm4v',
|
||||||
'title': 'CBR EXCLUSIVE: "Gotham City Imposters" Bats VS Jokerz Short 3',
|
'title': 'CBR EXCLUSIVE: "Gotham City Imposters" Bats VS Jokerz Short 3',
|
||||||
'description': 'md5:9bc31f227219cde65e47eeec8d2dc596',
|
'description': 'md5:9bc31f227219cde65e47eeec8d2dc596',
|
||||||
'timestamp': 1323138843,
|
'timestamp': 1323138843,
|
||||||
@@ -100,8 +102,31 @@ class BlipTVIE(InfoExtractor):
|
|||||||
'vcodec': 'none',
|
'vcodec': 'none',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
# missing duration
|
||||||
|
'url': 'http://blip.tv/rss/flash/6700880',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '6684191',
|
||||||
|
'ext': 'm4v',
|
||||||
|
'title': 'Cowboy Bebop: Gateway Shuffle Review',
|
||||||
|
'description': 'md5:3acc480c0f9ae157f5fe88547ecaf3f8',
|
||||||
|
'timestamp': 1386639757,
|
||||||
|
'upload_date': '20131210',
|
||||||
|
'uploader': 'sfdebris',
|
||||||
|
'uploader_id': '706520',
|
||||||
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _extract_url(webpage):
|
||||||
|
mobj = re.search(r'<meta\s[^>]*https?://api\.blip\.tv/\w+/redirect/\w+/(\d+)', webpage)
|
||||||
|
if mobj:
|
||||||
|
return 'http://blip.tv/a/a-' + mobj.group(1)
|
||||||
|
mobj = re.search(r'<(?:iframe|embed|object)\s[^>]*(https?://(?:\w+\.)?blip\.tv/(?:play/|api\.swf#)[a-zA-Z0-9_]+)', webpage)
|
||||||
|
if mobj:
|
||||||
|
return mobj.group(1)
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
lookup_id = mobj.group('lookup_id')
|
lookup_id = mobj.group('lookup_id')
|
||||||
@@ -119,35 +144,34 @@ class BlipTVIE(InfoExtractor):
|
|||||||
|
|
||||||
rss = self._download_xml('http://blip.tv/rss/flash/%s' % video_id, video_id, 'Downloading video RSS')
|
rss = self._download_xml('http://blip.tv/rss/flash/%s' % video_id, video_id, 'Downloading video RSS')
|
||||||
|
|
||||||
def blip(s):
|
def _x(p):
|
||||||
return '{http://blip.tv/dtd/blip/1.0}%s' % s
|
return xpath_with_ns(p, {
|
||||||
|
'blip': 'http://blip.tv/dtd/blip/1.0',
|
||||||
def media(s):
|
'media': 'http://search.yahoo.com/mrss/',
|
||||||
return '{http://search.yahoo.com/mrss/}%s' % s
|
'itunes': 'http://www.itunes.com/dtds/podcast-1.0.dtd',
|
||||||
|
})
|
||||||
def itunes(s):
|
|
||||||
return '{http://www.itunes.com/dtds/podcast-1.0.dtd}%s' % s
|
|
||||||
|
|
||||||
item = rss.find('channel/item')
|
item = rss.find('channel/item')
|
||||||
|
|
||||||
video_id = item.find(blip('item_id')).text
|
video_id = xpath_text(item, _x('blip:item_id'), 'video id') or lookup_id
|
||||||
title = item.find('./title').text
|
title = xpath_text(item, 'title', 'title', fatal=True)
|
||||||
description = clean_html(compat_str(item.find(blip('puredescription')).text))
|
description = clean_html(xpath_text(item, _x('blip:puredescription'), 'description'))
|
||||||
timestamp = parse_iso8601(item.find(blip('datestamp')).text)
|
timestamp = parse_iso8601(xpath_text(item, _x('blip:datestamp'), 'timestamp'))
|
||||||
uploader = item.find(blip('user')).text
|
uploader = xpath_text(item, _x('blip:user'), 'uploader')
|
||||||
uploader_id = item.find(blip('userid')).text
|
uploader_id = xpath_text(item, _x('blip:userid'), 'uploader id')
|
||||||
duration = int(item.find(blip('runtime')).text)
|
duration = int_or_none(xpath_text(item, _x('blip:runtime'), 'duration'))
|
||||||
media_thumbnail = item.find(media('thumbnail'))
|
media_thumbnail = item.find(_x('media:thumbnail'))
|
||||||
thumbnail = media_thumbnail.get('url') if media_thumbnail is not None else item.find(itunes('image')).text
|
thumbnail = (media_thumbnail.get('url') if media_thumbnail is not None
|
||||||
categories = [category.text for category in item.findall('category')]
|
else xpath_text(item, 'image', 'thumbnail'))
|
||||||
|
categories = [category.text for category in item.findall('category') if category is not None]
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
subtitles_urls = {}
|
subtitles_urls = {}
|
||||||
|
|
||||||
media_group = item.find(media('group'))
|
media_group = item.find(_x('media:group'))
|
||||||
for media_content in media_group.findall(media('content')):
|
for media_content in media_group.findall(_x('media:content')):
|
||||||
url = media_content.get('url')
|
url = media_content.get('url')
|
||||||
role = media_content.get(blip('role'))
|
role = media_content.get(_x('blip:role'))
|
||||||
msg = self._download_webpage(
|
msg = self._download_webpage(
|
||||||
url + '?showplayer=20140425131715&referrer=http://blip.tv&mask=7&skin=flashvars&view=url',
|
url + '?showplayer=20140425131715&referrer=http://blip.tv&mask=7&skin=flashvars&view=url',
|
||||||
video_id, 'Resolving URL for %s' % role)
|
video_id, 'Resolving URL for %s' % role)
|
||||||
@@ -166,12 +190,13 @@ class BlipTVIE(InfoExtractor):
|
|||||||
'url': real_url,
|
'url': real_url,
|
||||||
'format_id': role,
|
'format_id': role,
|
||||||
'format_note': media_type,
|
'format_note': media_type,
|
||||||
'vcodec': media_content.get(blip('vcodec')) or 'none',
|
'vcodec': media_content.get(_x('blip:vcodec')) or 'none',
|
||||||
'acodec': media_content.get(blip('acodec')),
|
'acodec': media_content.get(_x('blip:acodec')),
|
||||||
'filesize': media_content.get('filesize'),
|
'filesize': media_content.get('filesize'),
|
||||||
'width': int_or_none(media_content.get('width')),
|
'width': int_or_none(media_content.get('width')),
|
||||||
'height': int_or_none(media_content.get('height')),
|
'height': int_or_none(media_content.get('height')),
|
||||||
})
|
})
|
||||||
|
self._check_formats(formats, video_id)
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
subtitles = self.extract_subtitles(video_id, subtitles_urls)
|
subtitles = self.extract_subtitles(video_id, subtitles_urls)
|
||||||
|
|||||||
@@ -6,32 +6,39 @@ from .common import InfoExtractor
|
|||||||
|
|
||||||
|
|
||||||
class BloombergIE(InfoExtractor):
|
class BloombergIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://www\.bloomberg\.com/video/(?P<id>.+?)\.html'
|
_VALID_URL = r'https?://www\.bloomberg\.com/news/videos/[^/]+/(?P<id>[^/?#]+)'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.bloomberg.com/video/shah-s-presentation-on-foreign-exchange-strategies-qurhIVlJSB6hzkVi229d8g.html',
|
'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': {
|
||||||
'id': 'qurhIVlJSB6hzkVi229d8g',
|
'id': 'qurhIVlJSB6hzkVi229d8g',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'title': 'Shah\'s Presentation on Foreign-Exchange Strategies',
|
'title': 'Shah\'s Presentation on Foreign-Exchange Strategies',
|
||||||
'description': 'md5:0681e0d30dcdfc6abf34594961d8ea88',
|
'description': 'md5:a8ba0302912d03d246979735c17d2761',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
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')
|
||||||
f4m_url = self._search_regex(
|
|
||||||
r'<source src="(https?://[^"]+\.f4m.*?)"', webpage,
|
|
||||||
'f4m url')
|
|
||||||
title = re.sub(': Video$', '', self._og_search_title(webpage))
|
title = re.sub(': Video$', '', self._og_search_title(webpage))
|
||||||
|
|
||||||
|
embed_info = self._download_json(
|
||||||
|
'http://www.bloomberg.com/api/embed?id=%s' % video_id, video_id)
|
||||||
|
formats = []
|
||||||
|
for stream in embed_info['streams']:
|
||||||
|
if stream["muxing_format"] == "TS":
|
||||||
|
formats.extend(self._extract_m3u8_formats(stream['url'], video_id))
|
||||||
|
else:
|
||||||
|
formats.extend(self._extract_f4m_formats(stream['url'], video_id))
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': name.split('-')[-1],
|
'id': video_id,
|
||||||
'title': title,
|
'title': title,
|
||||||
'formats': self._extract_f4m_formats(f4m_url, name),
|
'formats': formats,
|
||||||
'description': self._og_search_description(webpage),
|
'description': self._og_search_description(webpage),
|
||||||
'thumbnail': self._og_search_thumbnail(webpage),
|
'thumbnail': self._og_search_thumbnail(webpage),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,27 +16,38 @@ class BRIE(InfoExtractor):
|
|||||||
|
|
||||||
_TESTS = [
|
_TESTS = [
|
||||||
{
|
{
|
||||||
'url': 'http://www.br.de/mediathek/video/sendungen/heimatsound/heimatsound-festival-2014-trailer-100.html',
|
'url': 'http://www.br.de/mediathek/video/sendungen/abendschau/betriebliche-altersvorsorge-104.html',
|
||||||
'md5': '93556dd2bcb2948d9259f8670c516d59',
|
'md5': '83a0477cf0b8451027eb566d88b51106',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '25e279aa-1ffd-40fd-9955-5325bd48a53a',
|
'id': '48f656ef-287e-486f-be86-459122db22cc',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Wenn das Traditions-Theater wackelt',
|
'title': 'Die böse Überraschung',
|
||||||
'description': 'Heimatsound-Festival 2014: Wenn das Traditions-Theater wackelt',
|
'description': 'Betriebliche Altersvorsorge: Die böse Überraschung',
|
||||||
'duration': 34,
|
'duration': 180,
|
||||||
'uploader': 'BR',
|
'uploader': 'Reinhard Weber',
|
||||||
'upload_date': '20140802',
|
'upload_date': '20150422',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'url': 'http://www.br.de/nachrichten/schaeuble-haushaltsentwurf-bundestag-100.html',
|
'url': 'http://www.br.de/nachrichten/oberbayern/inhalt/muenchner-polizeipraesident-schreiber-gestorben-100.html',
|
||||||
'md5': '3db0df1a9a9cd9fa0c70e6ea8aa8e820',
|
'md5': 'a44396d73ab6a68a69a568fae10705bb',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'c6aae3de-2cf9-43f2-957f-f17fef9afaab',
|
'id': 'a4b83e34-123d-4b81-9f4e-c0d3121a4e05',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Manfred Schreiber ist tot',
|
||||||
|
'description': 'Abendschau kompakt: Manfred Schreiber ist tot',
|
||||||
|
'duration': 26,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://www.br.de/radio/br-klassik/sendungen/allegro/premiere-urauffuehrung-the-land-2015-dance-festival-muenchen-100.html',
|
||||||
|
'md5': '8b5b27c0b090f3b35eac4ab3f7a73d3d',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '74c603c9-26d3-48bb-b85b-079aeed66e0b',
|
||||||
'ext': 'aac',
|
'ext': 'aac',
|
||||||
'title': '"Keine neuen Schulden im nächsten Jahr"',
|
'title': 'Kurzweilig und sehr bewegend',
|
||||||
'description': 'Haushaltsentwurf: "Keine neuen Schulden im nächsten Jahr"',
|
'description': '"The Land" von Peeping Tom: Kurzweilig und sehr bewegend',
|
||||||
'duration': 64,
|
'duration': 296,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class BreakIE(InfoExtractor):
|
|||||||
'tbr': media['bitRate'],
|
'tbr': media['bitRate'],
|
||||||
'width': media['width'],
|
'width': media['width'],
|
||||||
'height': media['height'],
|
'height': media['height'],
|
||||||
} for media in info['media']]
|
} for media in info['media'] if media.get('mediaPurpose') == 'play']
|
||||||
|
|
||||||
if not formats:
|
if not formats:
|
||||||
formats.append({
|
formats.append({
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ from ..compat import (
|
|||||||
compat_urllib_parse_urlparse,
|
compat_urllib_parse_urlparse,
|
||||||
compat_urllib_request,
|
compat_urllib_request,
|
||||||
compat_urlparse,
|
compat_urlparse,
|
||||||
|
compat_xml_parse_error,
|
||||||
)
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
determine_ext,
|
determine_ext,
|
||||||
@@ -117,7 +118,10 @@ class BrightcoveIE(InfoExtractor):
|
|||||||
object_str = re.sub(r'(<object[^>]*)(xmlns=".*?")', r'\1', object_str)
|
object_str = re.sub(r'(<object[^>]*)(xmlns=".*?")', r'\1', object_str)
|
||||||
object_str = fix_xml_ampersands(object_str)
|
object_str = fix_xml_ampersands(object_str)
|
||||||
|
|
||||||
|
try:
|
||||||
object_doc = xml.etree.ElementTree.fromstring(object_str.encode('utf-8'))
|
object_doc = xml.etree.ElementTree.fromstring(object_str.encode('utf-8'))
|
||||||
|
except compat_xml_parse_error:
|
||||||
|
return
|
||||||
|
|
||||||
fv_el = find_xpath_attr(object_doc, './param', 'name', 'flashVars')
|
fv_el = find_xpath_attr(object_doc, './param', 'name', 'flashVars')
|
||||||
if fv_el is not None:
|
if fv_el is not None:
|
||||||
@@ -153,6 +157,28 @@ class BrightcoveIE(InfoExtractor):
|
|||||||
linkBase = find_param('linkBaseURL')
|
linkBase = find_param('linkBaseURL')
|
||||||
if linkBase is not None:
|
if linkBase is not None:
|
||||||
params['linkBaseURL'] = linkBase
|
params['linkBaseURL'] = linkBase
|
||||||
|
return cls._make_brightcove_url(params)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _build_brighcove_url_from_js(cls, object_js):
|
||||||
|
# The layout of JS is as follows:
|
||||||
|
# customBC.createVideo = function (width, height, playerID, playerKey, videoPlayer, VideoRandomID) {
|
||||||
|
# // build Brightcove <object /> XML
|
||||||
|
# }
|
||||||
|
m = re.search(
|
||||||
|
r'''(?x)customBC.\createVideo\(
|
||||||
|
.*? # skipping width and height
|
||||||
|
["\'](?P<playerID>\d+)["\']\s*,\s* # playerID
|
||||||
|
["\'](?P<playerKey>AQ[^"\']{48})[^"\']*["\']\s*,\s* # playerKey begins with AQ and is 50 characters
|
||||||
|
# in length, however it's appended to itself
|
||||||
|
# in places, so truncate
|
||||||
|
["\'](?P<videoID>\d+)["\'] # @videoPlayer
|
||||||
|
''', object_js)
|
||||||
|
if m:
|
||||||
|
return cls._make_brightcove_url(m.groupdict())
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _make_brightcove_url(cls, params):
|
||||||
data = compat_urllib_parse.urlencode(params)
|
data = compat_urllib_parse.urlencode(params)
|
||||||
return cls._FEDERATED_URL_TEMPLATE % data
|
return cls._FEDERATED_URL_TEMPLATE % data
|
||||||
|
|
||||||
@@ -169,7 +195,7 @@ class BrightcoveIE(InfoExtractor):
|
|||||||
"""Return a list of all Brightcove URLs from the webpage """
|
"""Return a list of all Brightcove URLs from the webpage """
|
||||||
|
|
||||||
url_m = re.search(
|
url_m = re.search(
|
||||||
r'<meta\s+property="og:video"\s+content="(https?://(?:secure|c)\.brightcove.com/[^"]+)"',
|
r'<meta\s+property=[\'"]og:video[\'"]\s+content=[\'"](https?://(?:secure|c)\.brightcove.com/[^\'"]+)[\'"]',
|
||||||
webpage)
|
webpage)
|
||||||
if url_m:
|
if url_m:
|
||||||
url = unescapeHTML(url_m.group(1))
|
url = unescapeHTML(url_m.group(1))
|
||||||
@@ -183,9 +209,14 @@ class BrightcoveIE(InfoExtractor):
|
|||||||
(?:
|
(?:
|
||||||
[^>]+?class=[\'"][^>]*?BrightcoveExperience.*?[\'"] |
|
[^>]+?class=[\'"][^>]*?BrightcoveExperience.*?[\'"] |
|
||||||
[^>]*?>\s*<param\s+name="movie"\s+value="https?://[^/]*brightcove\.com/
|
[^>]*?>\s*<param\s+name="movie"\s+value="https?://[^/]*brightcove\.com/
|
||||||
).+?</object>''',
|
).+?>\s*</object>''',
|
||||||
webpage)
|
webpage)
|
||||||
return [cls._build_brighcove_url(m) for m in matches]
|
if matches:
|
||||||
|
return list(filter(None, [cls._build_brighcove_url(m) for m in matches]))
|
||||||
|
|
||||||
|
return list(filter(None, [
|
||||||
|
cls._build_brighcove_url_from_js(custom_bc)
|
||||||
|
for custom_bc in re.findall(r'(customBC\.createVideo\(.+?\);)', webpage)]))
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
url, smuggled_data = unsmuggle_url(url, {})
|
url, smuggled_data = unsmuggle_url(url, {})
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class BYUtvIE(InfoExtractor):
|
|||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'description': 'md5:5438d33774b6bdc662f9485a340401cc',
|
'description': 'md5:5438d33774b6bdc662f9485a340401cc',
|
||||||
'title': 'Season 5 Episode 5',
|
'title': 'Season 5 Episode 5',
|
||||||
'thumbnail': 're:^https?://.*promo.*'
|
'thumbnail': 're:^https?://.*\.jpg$'
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
|
|||||||
@@ -25,14 +25,14 @@ class CanalplusIE(InfoExtractor):
|
|||||||
}
|
}
|
||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.canalplus.fr/c-infos-documentaires/pid1830-c-zapping.html?vid=922470',
|
'url': 'http://www.canalplus.fr/c-emissions/pid1830-c-zapping.html?vid=1263092',
|
||||||
'md5': '3db39fb48b9685438ecf33a1078023e4',
|
'md5': 'b3481d7ca972f61e37420798d0a9d934',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '922470',
|
'id': '1263092',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'title': 'Zapping - 26/08/13',
|
'title': 'Le Zapping - 13/05/15',
|
||||||
'description': 'Le meilleur de toutes les chaînes, tous les jours.\nEmission du 26 août 2013',
|
'description': 'md5:09738c0d06be4b5d06a0940edb0da73f',
|
||||||
'upload_date': '20130826',
|
'upload_date': '20150513',
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.piwiplus.fr/videos-piwi/pid1405-le-labyrinthe-boing-super-ranger.html?vid=1108190',
|
'url': 'http://www.piwiplus.fr/videos-piwi/pid1405-le-labyrinthe-boing-super-ranger.html?vid=1108190',
|
||||||
@@ -56,7 +56,7 @@ class CanalplusIE(InfoExtractor):
|
|||||||
'skip': 'videos get deleted after a while',
|
'skip': 'videos get deleted after a while',
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.itele.fr/france/video/aubervilliers-un-lycee-en-colere-111559',
|
'url': 'http://www.itele.fr/france/video/aubervilliers-un-lycee-en-colere-111559',
|
||||||
'md5': '65aa83ad62fe107ce29e564bb8712580',
|
'md5': 'f3a46edcdf28006598ffaf5b30e6a2d4',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '1213714',
|
'id': '1213714',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
|
|||||||
@@ -4,12 +4,13 @@ from .common import InfoExtractor
|
|||||||
|
|
||||||
|
|
||||||
class CBSIE(InfoExtractor):
|
class CBSIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?cbs\.com/shows/[^/]+/(?:video|artist)/(?P<id>[^/]+)/.*'
|
_VALID_URL = r'https?://(?:www\.)?(?:cbs\.com/shows/[^/]+/(?:video|artist)|colbertlateshow\.com/(?:video|podcasts))/[^/]+/(?P<id>[^/]+)'
|
||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.cbs.com/shows/garth-brooks/video/_u7W953k6la293J7EPTd9oHkSPs6Xn6_/connect-chat-feat-garth-brooks/',
|
'url': 'http://www.cbs.com/shows/garth-brooks/video/_u7W953k6la293J7EPTd9oHkSPs6Xn6_/connect-chat-feat-garth-brooks/',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '4JUVEwq3wUT7',
|
'id': '4JUVEwq3wUT7',
|
||||||
|
'display_id': 'connect-chat-feat-garth-brooks',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'title': 'Connect Chat feat. Garth Brooks',
|
'title': 'Connect Chat feat. Garth Brooks',
|
||||||
'description': 'Connect with country music singer Garth Brooks, as he chats with fans on Wednesday November 27, 2013. Be sure to tune in to Garth Brooks: Live from Las Vegas, Friday November 29, at 9/8c on CBS!',
|
'description': 'Connect with country music singer Garth Brooks, as he chats with fans on Wednesday November 27, 2013. Be sure to tune in to Garth Brooks: Live from Las Vegas, Friday November 29, at 9/8c on CBS!',
|
||||||
@@ -24,6 +25,7 @@ class CBSIE(InfoExtractor):
|
|||||||
'url': 'http://www.cbs.com/shows/liveonletterman/artist/221752/st-vincent/',
|
'url': 'http://www.cbs.com/shows/liveonletterman/artist/221752/st-vincent/',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'WWF_5KqY3PK1',
|
'id': 'WWF_5KqY3PK1',
|
||||||
|
'display_id': 'st-vincent',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'title': 'Live on Letterman - St. Vincent',
|
'title': 'Live on Letterman - St. Vincent',
|
||||||
'description': 'Live On Letterman: St. Vincent in concert from New York\'s Ed Sullivan Theater on Tuesday, July 16, 2014.',
|
'description': 'Live On Letterman: St. Vincent in concert from New York\'s Ed Sullivan Theater on Tuesday, July 16, 2014.',
|
||||||
@@ -34,12 +36,23 @@ class CBSIE(InfoExtractor):
|
|||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
},
|
},
|
||||||
'_skip': 'Blocked outside the US',
|
'_skip': 'Blocked outside the US',
|
||||||
|
}, {
|
||||||
|
'url': 'http://colbertlateshow.com/video/8GmB0oY0McANFvp2aEffk9jZZZ2YyXxy/the-colbeard/',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.colbertlateshow.com/podcasts/dYSwjqPs_X1tvbV_P2FcPWRa_qT6akTC/in-the-bad-room-with-stephen/',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
display_id = self._match_id(url)
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, display_id)
|
||||||
real_id = self._search_regex(
|
real_id = self._search_regex(
|
||||||
r"video\.settings\.pid\s*=\s*'([^']+)';",
|
[r"video\.settings\.pid\s*=\s*'([^']+)';", r"cbsplayer\.pid\s*=\s*'([^']+)';"],
|
||||||
webpage, 'real video ID')
|
webpage, 'real video ID')
|
||||||
return self.url_result('theplatform:%s' % real_id)
|
return {
|
||||||
|
'_type': 'url_transparent',
|
||||||
|
'ie_key': 'ThePlatform',
|
||||||
|
'url': 'theplatform:%s' % real_id,
|
||||||
|
'display_id': display_id,
|
||||||
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class CBSNewsIE(InfoExtractor):
|
|||||||
'id': 'fort-hood-shooting-army-downplays-mental-illness-as-cause-of-attack',
|
'id': 'fort-hood-shooting-army-downplays-mental-illness-as-cause-of-attack',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'title': 'Fort Hood shooting: Army downplays mental illness as cause of attack',
|
'title': 'Fort Hood shooting: Army downplays mental illness as cause of attack',
|
||||||
'thumbnail': 'http://cbsnews2.cbsistatic.com/hub/i/r/2014/04/04/0c9fbc66-576b-41ca-8069-02d122060dd2/thumbnail/140x90/6dad7a502f88875ceac38202984b6d58/en-0404-werner-replace-640x360.jpg',
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
'duration': 205,
|
'duration': 205,
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class CCCIE(InfoExtractor):
|
|||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://media.ccc.de/browse/congress/2013/30C3_-_5443_-_en_-_saal_g_-_201312281830_-_introduction_to_processor_design_-_byterazor.html#video',
|
'url': 'http://media.ccc.de/browse/congress/2013/30C3_-_5443_-_en_-_saal_g_-_201312281830_-_introduction_to_processor_design_-_byterazor.html#video',
|
||||||
'md5': '205a365d0d57c0b1e43a12c9ffe8f9be',
|
'md5': '3a1eda8f3a29515d27f5adb967d7e740',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '20131228183',
|
'id': '20131228183',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
@@ -51,7 +51,7 @@ class CCCIE(InfoExtractor):
|
|||||||
|
|
||||||
matches = re.finditer(r'''(?xs)
|
matches = re.finditer(r'''(?xs)
|
||||||
<(?:span|div)\s+class='label\s+filetype'>(?P<format>.*?)</(?:span|div)>\s*
|
<(?:span|div)\s+class='label\s+filetype'>(?P<format>.*?)</(?:span|div)>\s*
|
||||||
<a\s+href='(?P<http_url>[^']+)'>\s*
|
<a\s+download\s+href='(?P<http_url>[^']+)'>\s*
|
||||||
(?:
|
(?:
|
||||||
.*?
|
.*?
|
||||||
<a\s+href='(?P<torrent_url>[^']+\.torrent)'
|
<a\s+href='(?P<torrent_url>[^']+\.torrent)'
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from .common import InfoExtractor
|
|||||||
from ..compat import (
|
from ..compat import (
|
||||||
compat_urllib_request,
|
compat_urllib_request,
|
||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
|
compat_urllib_parse_unquote,
|
||||||
compat_urllib_parse_urlparse,
|
compat_urllib_parse_urlparse,
|
||||||
)
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
@@ -88,7 +89,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 = compat_urllib_request.Request(compat_urllib_parse_unquote(playlist_url))
|
||||||
req.add_header('Referer', url)
|
req.add_header('Referer', url)
|
||||||
|
|
||||||
playlist = self._download_json(req, video_id)
|
playlist = self._download_json(req, video_id)
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ class ChilloutzoneIE(InfoExtractor):
|
|||||||
|
|
||||||
base64_video_info = self._html_search_regex(
|
base64_video_info = self._html_search_regex(
|
||||||
r'var cozVidData = "(.+?)";', webpage, 'video data')
|
r'var cozVidData = "(.+?)";', webpage, 'video data')
|
||||||
decoded_video_info = base64.b64decode(base64_video_info).decode("utf-8")
|
decoded_video_info = base64.b64decode(base64_video_info.encode('utf-8')).decode('utf-8')
|
||||||
video_info_dict = json.loads(decoded_video_info)
|
video_info_dict = json.loads(decoded_video_info)
|
||||||
|
|
||||||
# get video information from dict
|
# get video information from dict
|
||||||
|
|||||||
110
youtube_dl/extractor/cinemassacre.py
Normal file
110
youtube_dl/extractor/cinemassacre.py
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
# encoding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import ExtractorError
|
||||||
|
from .bliptv import BlipTVIE
|
||||||
|
|
||||||
|
|
||||||
|
class CinemassacreIE(InfoExtractor):
|
||||||
|
_VALID_URL = 'https?://(?:www\.)?cinemassacre\.com/(?P<date_y>[0-9]{4})/(?P<date_m>[0-9]{2})/(?P<date_d>[0-9]{2})/(?P<display_id>[^?#/]+)'
|
||||||
|
_TESTS = [
|
||||||
|
{
|
||||||
|
'url': 'http://cinemassacre.com/2012/11/10/avgn-the-movie-trailer/',
|
||||||
|
'md5': 'fde81fbafaee331785f58cd6c0d46190',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'Cinemassacre-19911',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'upload_date': '20121110',
|
||||||
|
'title': '“Angry Video Game Nerd: The Movie” – Trailer',
|
||||||
|
'description': 'md5:fb87405fcb42a331742a0dce2708560b',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://cinemassacre.com/2013/10/02/the-mummys-hand-1940',
|
||||||
|
'md5': 'd72f10cd39eac4215048f62ab477a511',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'Cinemassacre-521be8ef82b16',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'upload_date': '20131002',
|
||||||
|
'title': 'The Mummy’s Hand (1940)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
# blip.tv embedded video
|
||||||
|
'url': 'http://cinemassacre.com/2006/12/07/chronologically-confused-about-bad-movie-and-video-game-sequel-titles/',
|
||||||
|
'md5': 'ca9b3c8dd5a66f9375daeb5135f5a3de',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '4065369',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'AVGN: Chronologically Confused about Bad Movie and Video Game Sequel Titles',
|
||||||
|
'upload_date': '20061207',
|
||||||
|
'uploader': 'cinemassacre',
|
||||||
|
'uploader_id': '250778',
|
||||||
|
'timestamp': 1283233867,
|
||||||
|
'description': 'md5:0a108c78d130676b207d0f6d029ecffd',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
# Youtube embedded video
|
||||||
|
'url': 'http://cinemassacre.com/2006/09/01/mckids/',
|
||||||
|
'md5': '6eb30961fa795fedc750eac4881ad2e1',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'FnxsNhuikpo',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'upload_date': '20060901',
|
||||||
|
'uploader': 'Cinemassacre Extras',
|
||||||
|
'description': 'md5:de9b751efa9e45fbaafd9c8a1123ed53',
|
||||||
|
'uploader_id': 'Cinemassacre',
|
||||||
|
'title': 'AVGN: McKids',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://cinemassacre.com/2015/05/25/mario-kart-64-nintendo-64-james-mike-mondays/',
|
||||||
|
'md5': '1376908e49572389e7b06251a53cdd08',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'Cinemassacre-555779690c440',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'description': 'Let’s Play Mario Kart 64 !! Mario Kart 64 is a classic go-kart racing game released for the Nintendo 64 (N64). Today James & Mike do 4 player Battle Mode with Kyle and Bootsy!',
|
||||||
|
'title': 'Mario Kart 64 (Nintendo 64) James & Mike Mondays',
|
||||||
|
'upload_date': '20150525',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
display_id = mobj.group('display_id')
|
||||||
|
video_date = mobj.group('date_y') + mobj.group('date_m') + mobj.group('date_d')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
|
||||||
|
playerdata_url = self._search_regex(
|
||||||
|
[
|
||||||
|
r'src="(http://(?:player2\.screenwavemedia\.com|player\.screenwavemedia\.com/play)/[a-zA-Z]+\.php\?[^"]*\bid=.+?)"',
|
||||||
|
r'<iframe[^>]+src="((?:https?:)?//(?:[^.]+\.)?youtube\.com/.+?)"',
|
||||||
|
],
|
||||||
|
webpage, 'player data URL', default=None)
|
||||||
|
if not playerdata_url:
|
||||||
|
playerdata_url = BlipTVIE._extract_url(webpage)
|
||||||
|
if not playerdata_url:
|
||||||
|
raise ExtractorError('Unable to find player data')
|
||||||
|
|
||||||
|
video_title = self._html_search_regex(
|
||||||
|
r'<title>(?P<title>.+?)\|', webpage, 'title')
|
||||||
|
video_description = self._html_search_regex(
|
||||||
|
r'<div class="entry-content">(?P<description>.+?)</div>',
|
||||||
|
webpage, 'description', flags=re.DOTALL, fatal=False)
|
||||||
|
video_thumbnail = self._og_search_thumbnail(webpage)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'_type': 'url_transparent',
|
||||||
|
'display_id': display_id,
|
||||||
|
'title': video_title,
|
||||||
|
'description': video_description,
|
||||||
|
'upload_date': video_date,
|
||||||
|
'thumbnail': video_thumbnail,
|
||||||
|
'url': playerdata_url,
|
||||||
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
find_xpath_attr,
|
find_xpath_attr,
|
||||||
@@ -10,9 +8,9 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class ClipsyndicateIE(InfoExtractor):
|
class ClipsyndicateIE(InfoExtractor):
|
||||||
_VALID_URL = r'http://www\.clipsyndicate\.com/video/play(list/\d+)?/(?P<id>\d+)'
|
_VALID_URL = r'http://(?:chic|www)\.clipsyndicate\.com/video/play(list/\d+)?/(?P<id>\d+)'
|
||||||
|
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
'url': 'http://www.clipsyndicate.com/video/play/4629301/brick_briscoe',
|
'url': 'http://www.clipsyndicate.com/video/play/4629301/brick_briscoe',
|
||||||
'md5': '4d7d549451bad625e0ff3d7bd56d776c',
|
'md5': '4d7d549451bad625e0ff3d7bd56d776c',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@@ -22,11 +20,13 @@ class ClipsyndicateIE(InfoExtractor):
|
|||||||
'duration': 612,
|
'duration': 612,
|
||||||
'thumbnail': 're:^https?://.+\.jpg',
|
'thumbnail': 're:^https?://.+\.jpg',
|
||||||
},
|
},
|
||||||
}
|
}, {
|
||||||
|
'url': 'http://chic.clipsyndicate.com/video/play/5844117/shark_attack',
|
||||||
|
'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')
|
|
||||||
js_player = self._download_webpage(
|
js_player = self._download_webpage(
|
||||||
'http://eplayer.clipsyndicate.com/embed/player.js?va_id=%s' % video_id,
|
'http://eplayer.clipsyndicate.com/embed/player.js?va_id=%s' % video_id,
|
||||||
video_id, 'Downlaoding player')
|
video_id, 'Downlaoding player')
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ class CloudyIE(InfoExtractor):
|
|||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
file_key = self._search_regex(
|
file_key = self._search_regex(
|
||||||
r'filekey\s*=\s*"([^"]+)"', webpage, 'file_key')
|
[r'key\s*:\s*"([^"]+)"', r'filekey\s*=\s*"([^"]+)"'],
|
||||||
|
webpage, 'file_key')
|
||||||
|
|
||||||
return self._extract_video(video_host, video_id, file_key)
|
return self._extract_video(video_host, video_id, file_key)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from ..utils import (
|
|||||||
|
|
||||||
class CNETIE(InfoExtractor):
|
class CNETIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?cnet\.com/videos/(?P<id>[^/]+)/'
|
_VALID_URL = r'https?://(?:www\.)?cnet\.com/videos/(?P<id>[^/]+)/'
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
'url': 'http://www.cnet.com/videos/hands-on-with-microsofts-windows-8-1-update/',
|
'url': 'http://www.cnet.com/videos/hands-on-with-microsofts-windows-8-1-update/',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '56f4ea68-bd21-4852-b08c-4de5b8354c60',
|
'id': '56f4ea68-bd21-4852-b08c-4de5b8354c60',
|
||||||
@@ -25,7 +25,20 @@ class CNETIE(InfoExtractor):
|
|||||||
'params': {
|
'params': {
|
||||||
'skip_download': 'requires rtmpdump',
|
'skip_download': 'requires rtmpdump',
|
||||||
}
|
}
|
||||||
}
|
}, {
|
||||||
|
'url': 'http://www.cnet.com/videos/whiny-pothole-tweets-at-local-government-when-hit-by-cars-tomorrow-daily-187/',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '56527b93-d25d-44e3-b738-f989ce2e49ba',
|
||||||
|
'ext': 'flv',
|
||||||
|
'description': 'Khail and Ashley wonder what other civic woes can be solved by self-tweeting objects, investigate a new kind of VR camera and watch an origami robot self-assemble, walk, climb, dig and dissolve. #TDPothole',
|
||||||
|
'uploader_id': 'b163284d-6b73-44fc-b3e6-3da66c392d40',
|
||||||
|
'uploader': 'Ashley Esqueda',
|
||||||
|
'title': 'Whiny potholes tweet at local government when hit by cars (Tomorrow Daily 187)',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True, # requires rtmpdump
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
display_id = self._match_id(url)
|
display_id = self._match_id(url)
|
||||||
@@ -42,7 +55,7 @@ class CNETIE(InfoExtractor):
|
|||||||
raise ExtractorError('Cannot find video data')
|
raise ExtractorError('Cannot find video data')
|
||||||
|
|
||||||
mpx_account = data['config']['players']['default']['mpx_account']
|
mpx_account = data['config']['players']['default']['mpx_account']
|
||||||
vid = vdata['files']['rtmp']
|
vid = vdata['files'].get('rtmp', vdata['files']['hds'])
|
||||||
tp_link = 'http://link.theplatform.com/s/%s/%s' % (mpx_account, vid)
|
tp_link = 'http://link.theplatform.com/s/%s/%s' % (mpx_account, vid)
|
||||||
|
|
||||||
video_id = vdata['id']
|
video_id = vdata['id']
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from ..utils import (
|
|||||||
|
|
||||||
class CNNIE(InfoExtractor):
|
class CNNIE(InfoExtractor):
|
||||||
_VALID_URL = r'''(?x)https?://(?:(?:edition|www)\.)?cnn\.com/video/(?:data/.+?|\?)/
|
_VALID_URL = r'''(?x)https?://(?:(?:edition|www)\.)?cnn\.com/video/(?:data/.+?|\?)/
|
||||||
(?P<path>.+?/(?P<title>[^/]+?)(?:\.(?:cnn|hln)(?:-ap)?|(?=&)))'''
|
(?P<path>.+?/(?P<title>[^/]+?)(?:\.(?:[a-z\-]+)|(?=&)))'''
|
||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://edition.cnn.com/video/?/video/sports/2013/06/09/nadal-1-on-1.cnn',
|
'url': 'http://edition.cnn.com/video/?/video/sports/2013/06/09/nadal-1-on-1.cnn',
|
||||||
@@ -45,6 +45,12 @@ class CNNIE(InfoExtractor):
|
|||||||
'description': 'md5:e7223a503315c9f150acac52e76de086',
|
'description': 'md5:e7223a503315c9f150acac52e76de086',
|
||||||
'upload_date': '20141222',
|
'upload_date': '20141222',
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
'url': 'http://cnn.com/video/?/video/politics/2015/03/27/pkg-arizona-senator-church-attendance-mandatory.ktvk',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://cnn.com/video/?/video/us/2015/04/06/dnt-baker-refuses-anti-gay-order.wkmg',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ class ComedyCentralShowsIE(MTVServicesInfoExtractor):
|
|||||||
|
|
||||||
uri = mMovieParams[0][1]
|
uri = mMovieParams[0][1]
|
||||||
# Correct cc.com in uri
|
# Correct cc.com in uri
|
||||||
uri = re.sub(r'(episode:[^.]+)(\.cc)?\.com', r'\1.cc.com', uri)
|
uri = re.sub(r'(episode:[^.]+)(\.cc)?\.com', r'\1.com', uri)
|
||||||
|
|
||||||
index_url = 'http://%s.cc.com/feeds/mrss?%s' % (show_name, compat_urllib_parse.urlencode({'uri': uri}))
|
index_url = 'http://%s.cc.com/feeds/mrss?%s' % (show_name, compat_urllib_parse.urlencode({'uri': uri}))
|
||||||
idoc = self._download_xml(
|
idoc = self._download_xml(
|
||||||
|
|||||||
@@ -22,17 +22,20 @@ from ..compat import (
|
|||||||
compat_str,
|
compat_str,
|
||||||
)
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
|
NO_DEFAULT,
|
||||||
age_restricted,
|
age_restricted,
|
||||||
|
bug_reports_message,
|
||||||
clean_html,
|
clean_html,
|
||||||
compiled_regex_type,
|
compiled_regex_type,
|
||||||
|
determine_ext,
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
|
fix_xml_ampersands,
|
||||||
float_or_none,
|
float_or_none,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
RegexNotFoundError,
|
RegexNotFoundError,
|
||||||
sanitize_filename,
|
sanitize_filename,
|
||||||
unescapeHTML,
|
unescapeHTML,
|
||||||
)
|
)
|
||||||
_NO_DEFAULT = object()
|
|
||||||
|
|
||||||
|
|
||||||
class InfoExtractor(object):
|
class InfoExtractor(object):
|
||||||
@@ -46,7 +49,7 @@ class InfoExtractor(object):
|
|||||||
information possibly downloading the video to the file system, among
|
information possibly downloading the video to the file system, among
|
||||||
other possible outcomes.
|
other possible outcomes.
|
||||||
|
|
||||||
The type field determines the the type of the result.
|
The type field determines the type of the result.
|
||||||
By far the most common value (and the default if _type is missing) is
|
By far the most common value (and the default if _type is missing) is
|
||||||
"video", which indicates a single video.
|
"video", which indicates a single video.
|
||||||
|
|
||||||
@@ -110,11 +113,8 @@ class InfoExtractor(object):
|
|||||||
(quality takes higher priority)
|
(quality takes higher priority)
|
||||||
-1 for default (order by other properties),
|
-1 for default (order by other properties),
|
||||||
-2 or smaller for less than default.
|
-2 or smaller for less than default.
|
||||||
* http_method HTTP method to use for the download.
|
|
||||||
* http_headers A dictionary of additional HTTP headers
|
* http_headers A dictionary of additional HTTP headers
|
||||||
to add to the request.
|
to add to the request.
|
||||||
* http_post_data Additional data to send with a POST
|
|
||||||
request.
|
|
||||||
* stretched_ratio If given and not 1, indicates that the
|
* stretched_ratio If given and not 1, indicates that the
|
||||||
video's pixels are not square.
|
video's pixels are not square.
|
||||||
width : height ratio as float.
|
width : height ratio as float.
|
||||||
@@ -324,7 +324,7 @@ class InfoExtractor(object):
|
|||||||
self._downloader.report_warning(errmsg)
|
self._downloader.report_warning(errmsg)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _download_webpage_handle(self, url_or_request, video_id, note=None, errnote=None, fatal=True):
|
def _download_webpage_handle(self, url_or_request, video_id, note=None, errnote=None, fatal=True, encoding=None):
|
||||||
""" Returns a tuple (page content as string, URL handle) """
|
""" Returns a tuple (page content as string, URL handle) """
|
||||||
# Strip hashes from the URL (#1038)
|
# Strip hashes from the URL (#1038)
|
||||||
if isinstance(url_or_request, (compat_str, str)):
|
if isinstance(url_or_request, (compat_str, str)):
|
||||||
@@ -334,14 +334,11 @@ class InfoExtractor(object):
|
|||||||
if urlh is False:
|
if urlh is False:
|
||||||
assert not fatal
|
assert not fatal
|
||||||
return False
|
return False
|
||||||
content = self._webpage_read_content(urlh, url_or_request, video_id, note, errnote, fatal)
|
content = self._webpage_read_content(urlh, url_or_request, video_id, note, errnote, fatal, encoding=encoding)
|
||||||
return (content, urlh)
|
return (content, urlh)
|
||||||
|
|
||||||
def _webpage_read_content(self, urlh, url_or_request, video_id, note=None, errnote=None, fatal=True, prefix=None):
|
@staticmethod
|
||||||
content_type = urlh.headers.get('Content-Type', '')
|
def _guess_encoding_from_content(content_type, webpage_bytes):
|
||||||
webpage_bytes = urlh.read()
|
|
||||||
if prefix is not None:
|
|
||||||
webpage_bytes = prefix + webpage_bytes
|
|
||||||
m = re.match(r'[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+\s*;\s*charset=(.+)', content_type)
|
m = re.match(r'[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+\s*;\s*charset=(.+)', content_type)
|
||||||
if m:
|
if m:
|
||||||
encoding = m.group(1)
|
encoding = m.group(1)
|
||||||
@@ -354,6 +351,16 @@ class InfoExtractor(object):
|
|||||||
encoding = 'utf-16'
|
encoding = 'utf-16'
|
||||||
else:
|
else:
|
||||||
encoding = 'utf-8'
|
encoding = 'utf-8'
|
||||||
|
|
||||||
|
return encoding
|
||||||
|
|
||||||
|
def _webpage_read_content(self, urlh, url_or_request, video_id, note=None, errnote=None, fatal=True, prefix=None, encoding=None):
|
||||||
|
content_type = urlh.headers.get('Content-Type', '')
|
||||||
|
webpage_bytes = urlh.read()
|
||||||
|
if prefix is not None:
|
||||||
|
webpage_bytes = prefix + webpage_bytes
|
||||||
|
if not encoding:
|
||||||
|
encoding = self._guess_encoding_from_content(content_type, webpage_bytes)
|
||||||
if self._downloader.params.get('dump_intermediate_pages', False):
|
if self._downloader.params.get('dump_intermediate_pages', False):
|
||||||
try:
|
try:
|
||||||
url = url_or_request.get_full_url()
|
url = url_or_request.get_full_url()
|
||||||
@@ -410,13 +417,13 @@ class InfoExtractor(object):
|
|||||||
|
|
||||||
return content
|
return content
|
||||||
|
|
||||||
def _download_webpage(self, url_or_request, video_id, note=None, errnote=None, fatal=True, tries=1, timeout=5):
|
def _download_webpage(self, url_or_request, video_id, note=None, errnote=None, fatal=True, tries=1, timeout=5, encoding=None):
|
||||||
""" Returns the data of the page as a string """
|
""" Returns the data of the page as a string """
|
||||||
success = False
|
success = False
|
||||||
try_count = 0
|
try_count = 0
|
||||||
while success is False:
|
while success is False:
|
||||||
try:
|
try:
|
||||||
res = self._download_webpage_handle(url_or_request, video_id, note, errnote, fatal)
|
res = self._download_webpage_handle(url_or_request, video_id, note, errnote, fatal, encoding=encoding)
|
||||||
success = True
|
success = True
|
||||||
except compat_http_client.IncompleteRead as e:
|
except compat_http_client.IncompleteRead as e:
|
||||||
try_count += 1
|
try_count += 1
|
||||||
@@ -431,10 +438,10 @@ class InfoExtractor(object):
|
|||||||
|
|
||||||
def _download_xml(self, url_or_request, video_id,
|
def _download_xml(self, url_or_request, video_id,
|
||||||
note='Downloading XML', errnote='Unable to download XML',
|
note='Downloading XML', errnote='Unable to download XML',
|
||||||
transform_source=None, fatal=True):
|
transform_source=None, fatal=True, encoding=None):
|
||||||
"""Return the xml as an xml.etree.ElementTree.Element"""
|
"""Return the xml as an xml.etree.ElementTree.Element"""
|
||||||
xml_string = self._download_webpage(
|
xml_string = self._download_webpage(
|
||||||
url_or_request, video_id, note, errnote, fatal=fatal)
|
url_or_request, video_id, note, errnote, fatal=fatal, encoding=encoding)
|
||||||
if xml_string is False:
|
if xml_string is False:
|
||||||
return xml_string
|
return xml_string
|
||||||
if transform_source:
|
if transform_source:
|
||||||
@@ -445,9 +452,10 @@ class InfoExtractor(object):
|
|||||||
note='Downloading JSON metadata',
|
note='Downloading JSON metadata',
|
||||||
errnote='Unable to download JSON metadata',
|
errnote='Unable to download JSON metadata',
|
||||||
transform_source=None,
|
transform_source=None,
|
||||||
fatal=True):
|
fatal=True, encoding=None):
|
||||||
json_string = self._download_webpage(
|
json_string = self._download_webpage(
|
||||||
url_or_request, video_id, note, errnote, fatal=fatal)
|
url_or_request, video_id, note, errnote, fatal=fatal,
|
||||||
|
encoding=encoding)
|
||||||
if (not fatal) and json_string is False:
|
if (not fatal) and json_string is False:
|
||||||
return None
|
return None
|
||||||
return self._parse_json(
|
return self._parse_json(
|
||||||
@@ -492,7 +500,7 @@ class InfoExtractor(object):
|
|||||||
|
|
||||||
# Methods for following #608
|
# Methods for following #608
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def url_result(url, ie=None, video_id=None):
|
def url_result(url, ie=None, video_id=None, video_title=None):
|
||||||
"""Returns a url that points to a page that should be processed"""
|
"""Returns a url that points to a page that should be processed"""
|
||||||
# TODO: ie should be the class used for getting the info
|
# TODO: ie should be the class used for getting the info
|
||||||
video_info = {'_type': 'url',
|
video_info = {'_type': 'url',
|
||||||
@@ -500,6 +508,8 @@ class InfoExtractor(object):
|
|||||||
'ie_key': ie}
|
'ie_key': ie}
|
||||||
if video_id is not None:
|
if video_id is not None:
|
||||||
video_info['id'] = video_id
|
video_info['id'] = video_id
|
||||||
|
if video_title is not None:
|
||||||
|
video_info['title'] = video_title
|
||||||
return video_info
|
return video_info
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -515,7 +525,7 @@ class InfoExtractor(object):
|
|||||||
video_info['description'] = playlist_description
|
video_info['description'] = playlist_description
|
||||||
return video_info
|
return video_info
|
||||||
|
|
||||||
def _search_regex(self, pattern, string, name, default=_NO_DEFAULT, fatal=True, flags=0, group=None):
|
def _search_regex(self, pattern, string, name, default=NO_DEFAULT, fatal=True, flags=0, group=None):
|
||||||
"""
|
"""
|
||||||
Perform a regex search on the given string, using a single or a list of
|
Perform a regex search on the given string, using a single or a list of
|
||||||
patterns returning the first matching group.
|
patterns returning the first matching group.
|
||||||
@@ -541,16 +551,15 @@ class InfoExtractor(object):
|
|||||||
return next(g for g in mobj.groups() if g is not None)
|
return next(g for g in mobj.groups() if g is not None)
|
||||||
else:
|
else:
|
||||||
return mobj.group(group)
|
return mobj.group(group)
|
||||||
elif default is not _NO_DEFAULT:
|
elif default is not NO_DEFAULT:
|
||||||
return default
|
return default
|
||||||
elif fatal:
|
elif fatal:
|
||||||
raise RegexNotFoundError('Unable to extract %s' % _name)
|
raise RegexNotFoundError('Unable to extract %s' % _name)
|
||||||
else:
|
else:
|
||||||
self._downloader.report_warning('unable to extract %s; '
|
self._downloader.report_warning('unable to extract %s' % _name + bug_reports_message())
|
||||||
'please report this issue on http://yt-dl.org/bug' % _name)
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _html_search_regex(self, pattern, string, name, default=_NO_DEFAULT, fatal=True, flags=0, group=None):
|
def _html_search_regex(self, pattern, string, name, default=NO_DEFAULT, fatal=True, flags=0, group=None):
|
||||||
"""
|
"""
|
||||||
Like _search_regex, but strips HTML tags and unescapes entities.
|
Like _search_regex, but strips HTML tags and unescapes entities.
|
||||||
"""
|
"""
|
||||||
@@ -562,7 +571,7 @@ class InfoExtractor(object):
|
|||||||
|
|
||||||
def _get_login_info(self):
|
def _get_login_info(self):
|
||||||
"""
|
"""
|
||||||
Get the the login info as (username, password)
|
Get the login info as (username, password)
|
||||||
It will look in the netrc file using the _NETRC_MACHINE value
|
It will look in the netrc file using the _NETRC_MACHINE value
|
||||||
If there's no info available, return (None, None)
|
If there's no info available, return (None, None)
|
||||||
"""
|
"""
|
||||||
@@ -698,7 +707,26 @@ class InfoExtractor(object):
|
|||||||
return self._html_search_meta('twitter:player', html,
|
return self._html_search_meta('twitter:player', html,
|
||||||
'twitter card player')
|
'twitter card player')
|
||||||
|
|
||||||
def _sort_formats(self, formats):
|
@staticmethod
|
||||||
|
def _hidden_inputs(html):
|
||||||
|
return dict([
|
||||||
|
(input.group('name'), input.group('value')) for input in re.finditer(
|
||||||
|
r'''(?x)
|
||||||
|
<input\s+
|
||||||
|
type=(?P<q_hidden>["\'])hidden(?P=q_hidden)\s+
|
||||||
|
name=(?P<q_name>["\'])(?P<name>.+?)(?P=q_name)\s+
|
||||||
|
(?:id=(?P<q_id>["\']).+?(?P=q_id)\s+)?
|
||||||
|
value=(?P<q_value>["\'])(?P<value>.*?)(?P=q_value)
|
||||||
|
''', html)
|
||||||
|
])
|
||||||
|
|
||||||
|
def _form_hidden_inputs(self, form_id, html):
|
||||||
|
form = self._search_regex(
|
||||||
|
r'(?s)<form[^>]+?id=(["\'])%s\1[^>]*>(?P<form>.+?)</form>' % form_id,
|
||||||
|
html, '%s form' % form_id, group='form')
|
||||||
|
return self._hidden_inputs(form)
|
||||||
|
|
||||||
|
def _sort_formats(self, formats, field_preference=None):
|
||||||
if not formats:
|
if not formats:
|
||||||
raise ExtractorError('No video formats found')
|
raise ExtractorError('No video formats found')
|
||||||
|
|
||||||
@@ -708,6 +736,9 @@ class InfoExtractor(object):
|
|||||||
if not f.get('ext') and 'url' in f:
|
if not f.get('ext') and 'url' in f:
|
||||||
f['ext'] = determine_ext(f['url'])
|
f['ext'] = determine_ext(f['url'])
|
||||||
|
|
||||||
|
if isinstance(field_preference, (list, tuple)):
|
||||||
|
return tuple(f.get(field) if f.get(field) is not None else -1 for field in field_preference)
|
||||||
|
|
||||||
preference = f.get('preference')
|
preference = f.get('preference')
|
||||||
if preference is None:
|
if preference is None:
|
||||||
proto = f.get('protocol')
|
proto = f.get('protocol')
|
||||||
@@ -754,7 +785,7 @@ class InfoExtractor(object):
|
|||||||
f.get('fps') if f.get('fps') is not None else -1,
|
f.get('fps') if f.get('fps') is not None else -1,
|
||||||
f.get('filesize_approx') if f.get('filesize_approx') is not None else -1,
|
f.get('filesize_approx') if f.get('filesize_approx') is not None else -1,
|
||||||
f.get('source_preference') if f.get('source_preference') is not None else -1,
|
f.get('source_preference') if f.get('source_preference') is not None else -1,
|
||||||
f.get('format_id'),
|
f.get('format_id') if f.get('format_id') is not None else '',
|
||||||
)
|
)
|
||||||
formats.sort(key=_formats_key)
|
formats.sort(key=_formats_key)
|
||||||
|
|
||||||
@@ -776,8 +807,8 @@ class InfoExtractor(object):
|
|||||||
return True
|
return True
|
||||||
except ExtractorError as e:
|
except ExtractorError as e:
|
||||||
if isinstance(e.cause, compat_HTTPError):
|
if isinstance(e.cause, compat_HTTPError):
|
||||||
self.report_warning(
|
self.to_screen(
|
||||||
'%s URL is invalid, skipping' % item, video_id)
|
'%s: %s URL is invalid, skipping' % (video_id, item))
|
||||||
return False
|
return False
|
||||||
raise
|
raise
|
||||||
|
|
||||||
@@ -805,10 +836,14 @@ class InfoExtractor(object):
|
|||||||
self.to_screen(msg)
|
self.to_screen(msg)
|
||||||
time.sleep(timeout)
|
time.sleep(timeout)
|
||||||
|
|
||||||
def _extract_f4m_formats(self, manifest_url, video_id, preference=None, f4m_id=None):
|
def _extract_f4m_formats(self, manifest_url, video_id, preference=None, f4m_id=None,
|
||||||
|
transform_source=lambda s: fix_xml_ampersands(s).strip()):
|
||||||
manifest = self._download_xml(
|
manifest = self._download_xml(
|
||||||
manifest_url, video_id, 'Downloading f4m manifest',
|
manifest_url, video_id, 'Downloading f4m manifest',
|
||||||
'Unable to download f4m manifest')
|
'Unable to download f4m manifest',
|
||||||
|
# Some manifests may be malformed, e.g. prosiebensat1 generated manifests
|
||||||
|
# (see https://github.com/rg3/youtube-dl/issues/6215#issuecomment-121704244)
|
||||||
|
transform_source=transform_source)
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
manifest_version = '1.0'
|
manifest_version = '1.0'
|
||||||
@@ -818,11 +853,22 @@ class InfoExtractor(object):
|
|||||||
media_nodes = manifest.findall('{http://ns.adobe.com/f4m/2.0}media')
|
media_nodes = manifest.findall('{http://ns.adobe.com/f4m/2.0}media')
|
||||||
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':
|
||||||
manifest_url = ('/'.join(manifest_url.split('/')[:-1]) + '/' +
|
media_url = media_el.attrib.get('href') or media_el.attrib.get('url')
|
||||||
(media_el.attrib.get('href') or media_el.attrib.get('url')))
|
if not media_url:
|
||||||
|
continue
|
||||||
|
manifest_url = (
|
||||||
|
media_url if media_url.startswith('http://') or media_url.startswith('https://')
|
||||||
|
else ('/'.join(manifest_url.split('/')[:-1]) + '/' + media_url))
|
||||||
|
# If media_url is itself a f4m manifest do the recursive extraction
|
||||||
|
# since bitrates in parent manifest (this one) and media_url manifest
|
||||||
|
# may differ leading to inability to resolve the format by requested
|
||||||
|
# bitrate in f4m downloader
|
||||||
|
if determine_ext(manifest_url) == 'f4m':
|
||||||
|
formats.extend(self._extract_f4m_formats(manifest_url, video_id, preference, f4m_id))
|
||||||
|
continue
|
||||||
tbr = int_or_none(media_el.attrib.get('bitrate'))
|
tbr = int_or_none(media_el.attrib.get('bitrate'))
|
||||||
formats.append({
|
formats.append({
|
||||||
'format_id': '-'.join(filter(None, [f4m_id, 'f4m-%d' % (i if tbr is None else tbr)])),
|
'format_id': '-'.join(filter(None, [f4m_id, compat_str(i if tbr is None else tbr)])),
|
||||||
'url': manifest_url,
|
'url': manifest_url,
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'tbr': tbr,
|
'tbr': tbr,
|
||||||
@@ -836,10 +882,11 @@ class InfoExtractor(object):
|
|||||||
|
|
||||||
def _extract_m3u8_formats(self, m3u8_url, video_id, ext=None,
|
def _extract_m3u8_formats(self, m3u8_url, video_id, ext=None,
|
||||||
entry_protocol='m3u8', preference=None,
|
entry_protocol='m3u8', preference=None,
|
||||||
m3u8_id=None):
|
m3u8_id=None, note=None, errnote=None,
|
||||||
|
fatal=True):
|
||||||
|
|
||||||
formats = [{
|
formats = [{
|
||||||
'format_id': '-'.join(filter(None, [m3u8_id, 'm3u8-meta'])),
|
'format_id': '-'.join(filter(None, [m3u8_id, 'meta'])),
|
||||||
'url': m3u8_url,
|
'url': m3u8_url,
|
||||||
'ext': ext,
|
'ext': ext,
|
||||||
'protocol': 'm3u8',
|
'protocol': 'm3u8',
|
||||||
@@ -855,8 +902,11 @@ class InfoExtractor(object):
|
|||||||
|
|
||||||
m3u8_doc = self._download_webpage(
|
m3u8_doc = self._download_webpage(
|
||||||
m3u8_url, video_id,
|
m3u8_url, video_id,
|
||||||
note='Downloading m3u8 information',
|
note=note or 'Downloading m3u8 information',
|
||||||
errnote='Failed to download m3u8 information')
|
errnote=errnote or 'Failed to download m3u8 information',
|
||||||
|
fatal=fatal)
|
||||||
|
if m3u8_doc is False:
|
||||||
|
return m3u8_doc
|
||||||
last_info = None
|
last_info = None
|
||||||
last_media = None
|
last_media = None
|
||||||
kv_rex = re.compile(
|
kv_rex = re.compile(
|
||||||
@@ -883,8 +933,13 @@ class InfoExtractor(object):
|
|||||||
formats.append({'url': format_url(line)})
|
formats.append({'url': format_url(line)})
|
||||||
continue
|
continue
|
||||||
tbr = int_or_none(last_info.get('BANDWIDTH'), scale=1000)
|
tbr = int_or_none(last_info.get('BANDWIDTH'), scale=1000)
|
||||||
|
format_id = []
|
||||||
|
if m3u8_id:
|
||||||
|
format_id.append(m3u8_id)
|
||||||
|
last_media_name = last_media.get('NAME') if last_media and last_media.get('TYPE') != 'SUBTITLES' else None
|
||||||
|
format_id.append(last_media_name if last_media_name else '%d' % (tbr if tbr else len(formats)))
|
||||||
f = {
|
f = {
|
||||||
'format_id': '-'.join(filter(None, [m3u8_id, 'm3u8-%d' % (tbr if tbr else len(formats))])),
|
'format_id': '-'.join(format_id),
|
||||||
'url': format_url(line.strip()),
|
'url': format_url(line.strip()),
|
||||||
'tbr': tbr,
|
'tbr': tbr,
|
||||||
'ext': ext,
|
'ext': ext,
|
||||||
|
|||||||
@@ -11,39 +11,65 @@ from ..utils import (
|
|||||||
|
|
||||||
class CrackedIE(InfoExtractor):
|
class CrackedIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?cracked\.com/video_(?P<id>\d+)_[\da-z-]+\.html'
|
_VALID_URL = r'https?://(?:www\.)?cracked\.com/video_(?P<id>\d+)_[\da-z-]+\.html'
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
'url': 'http://www.cracked.com/video_19006_4-plot-holes-you-didnt-notice-in-your-favorite-movies.html',
|
'url': 'http://www.cracked.com/video_19070_if-animal-actors-got-e21-true-hollywood-stories.html',
|
||||||
'md5': '4b29a5eeec292cd5eca6388c7558db9e',
|
'md5': '89b90b9824e3806ca95072c4d78f13f7',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '19006',
|
'id': '19070',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': '4 Plot Holes You Didn\'t Notice in Your Favorite Movies',
|
'title': 'If Animal Actors Got E! True Hollywood Stories',
|
||||||
'description': 'md5:3b909e752661db86007d10e5ec2df769',
|
'timestamp': 1404954000,
|
||||||
'timestamp': 1405659600,
|
'upload_date': '20140710',
|
||||||
'upload_date': '20140718',
|
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
# youtube embed
|
||||||
|
'url': 'http://www.cracked.com/video_19006_4-plot-holes-you-didnt-notice-in-your-favorite-movies.html',
|
||||||
|
'md5': 'ccd52866b50bde63a6ef3b35016ba8c7',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'EjI00A3rZD0',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': "4 Plot Holes You Didn't Notice in Your Favorite Movies - The Spit Take",
|
||||||
|
'description': 'md5:c603708c718b796fe6079e2b3351ffc7',
|
||||||
|
'upload_date': '20140725',
|
||||||
|
'uploader_id': 'Cracked',
|
||||||
|
'uploader': 'Cracked',
|
||||||
}
|
}
|
||||||
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = mobj.group('id')
|
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
youtube_url = self._search_regex(
|
||||||
|
r'<iframe[^>]+src="((?:https?:)?//www\.youtube\.com/embed/[^"]+)"',
|
||||||
|
webpage, 'youtube url', default=None)
|
||||||
|
if youtube_url:
|
||||||
|
return self.url_result(youtube_url, 'Youtube')
|
||||||
|
|
||||||
video_url = self._html_search_regex(
|
video_url = self._html_search_regex(
|
||||||
[r'var\s+CK_vidSrc\s*=\s*"([^"]+)"', r'<video\s+src="([^"]+)"'], webpage, 'video URL')
|
[r'var\s+CK_vidSrc\s*=\s*"([^"]+)"', r'<video\s+src="([^"]+)"'],
|
||||||
|
webpage, 'video URL')
|
||||||
|
|
||||||
title = self._og_search_title(webpage)
|
title = self._search_regex(
|
||||||
description = self._og_search_description(webpage)
|
[r'property="?og:title"?\s+content="([^"]+)"', r'class="?title"?>([^<]+)'],
|
||||||
|
webpage, 'title')
|
||||||
|
|
||||||
timestamp = self._html_search_regex(r'<time datetime="([^"]+)"', webpage, 'upload date', fatal=False)
|
description = self._search_regex(
|
||||||
|
r'name="?(?:og:)?description"?\s+content="([^"]+)"',
|
||||||
|
webpage, 'description', default=None)
|
||||||
|
|
||||||
|
timestamp = self._html_search_regex(
|
||||||
|
r'"date"\s*:\s*"([^"]+)"', webpage, 'upload date', fatal=False)
|
||||||
if timestamp:
|
if timestamp:
|
||||||
timestamp = parse_iso8601(timestamp[:-6])
|
timestamp = parse_iso8601(timestamp[:-6])
|
||||||
|
|
||||||
view_count = str_to_int(self._html_search_regex(
|
view_count = str_to_int(self._html_search_regex(
|
||||||
r'<span class="views" id="viewCounts">([\d,\.]+) Views</span>', webpage, 'view count', fatal=False))
|
r'<span\s+class="?views"? id="?viewCounts"?>([\d,\.]+) Views</span>',
|
||||||
|
webpage, 'view count', fatal=False))
|
||||||
comment_count = str_to_int(self._html_search_regex(
|
comment_count = str_to_int(self._html_search_regex(
|
||||||
r'<span id="commentCounts">([\d,\.]+)</span>', webpage, 'comment count', fatal=False))
|
r'<span\s+id="?commentCounts"?>([\d,\.]+)</span>',
|
||||||
|
webpage, 'comment count', fatal=False))
|
||||||
|
|
||||||
m = re.search(r'_(?P<width>\d+)X(?P<height>\d+)\.mp4$', video_url)
|
m = re.search(r'_(?P<width>\d+)X(?P<height>\d+)\.mp4$', video_url)
|
||||||
if m:
|
if m:
|
||||||
|
|||||||
60
youtube_dl/extractor/crooksandliars.py
Normal file
60
youtube_dl/extractor/crooksandliars.py
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
int_or_none,
|
||||||
|
qualities,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CrooksAndLiarsIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://embed\.crooksandliars\.com/(?:embed|v)/(?P<id>[A-Za-z0-9]+)'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://embed.crooksandliars.com/embed/8RUoRhRi',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '8RUoRhRi',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Fox & Friends Says Protecting Atheists From Discrimination Is Anti-Christian!',
|
||||||
|
'description': 'md5:e1a46ad1650e3a5ec7196d432799127f',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg',
|
||||||
|
'timestamp': 1428207000,
|
||||||
|
'upload_date': '20150405',
|
||||||
|
'uploader': 'Heather',
|
||||||
|
'duration': 236,
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'url': 'http://embed.crooksandliars.com/v/MTE3MjUtMzQ2MzA',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
|
webpage = self._download_webpage(
|
||||||
|
'http://embed.crooksandliars.com/embed/%s' % video_id, video_id)
|
||||||
|
|
||||||
|
manifest = self._parse_json(
|
||||||
|
self._search_regex(
|
||||||
|
r'var\s+manifest\s*=\s*({.+?})\n', webpage, 'manifest JSON'),
|
||||||
|
video_id)
|
||||||
|
|
||||||
|
quality = qualities(('webm_low', 'mp4_low', 'webm_high', 'mp4_high'))
|
||||||
|
|
||||||
|
formats = [{
|
||||||
|
'url': item['url'],
|
||||||
|
'format_id': item['type'],
|
||||||
|
'quality': quality(item['type']),
|
||||||
|
} for item in manifest['flavors'] if item['mime'].startswith('video/')]
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'url': url,
|
||||||
|
'id': video_id,
|
||||||
|
'title': manifest['title'],
|
||||||
|
'description': manifest.get('description'),
|
||||||
|
'thumbnail': self._proto_relative_url(manifest.get('poster')),
|
||||||
|
'timestamp': int_or_none(manifest.get('created')),
|
||||||
|
'uploader': manifest.get('author'),
|
||||||
|
'duration': int_or_none(manifest.get('duration')),
|
||||||
|
'formats': formats,
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ from math import pow, sqrt, floor
|
|||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import (
|
||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
|
compat_urllib_parse_unquote,
|
||||||
compat_urllib_request,
|
compat_urllib_request,
|
||||||
)
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
@@ -23,12 +24,11 @@ from ..utils import (
|
|||||||
)
|
)
|
||||||
from ..aes import (
|
from ..aes import (
|
||||||
aes_cbc_decrypt,
|
aes_cbc_decrypt,
|
||||||
inc,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class CrunchyrollIE(InfoExtractor):
|
class CrunchyrollIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:(?P<prefix>www|m)\.)?(?P<url>crunchyroll\.(?:com|fr)/(?:[^/]*/[^/?&]*?|media/\?id=)(?P<video_id>[0-9]+))(?:[/?&]|$)'
|
_VALID_URL = r'https?://(?:(?P<prefix>www|m)\.)?(?P<url>crunchyroll\.(?:com|fr)/(?:media(?:-|/\?id=)|[^/]*/[^/?&]*?)(?P<video_id>[0-9]+))(?:[/?&]|$)'
|
||||||
_NETRC_MACHINE = 'crunchyroll'
|
_NETRC_MACHINE = 'crunchyroll'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.crunchyroll.com/wanna-be-the-strongest-in-the-world/episode-1-an-idol-wrestler-is-born-645513',
|
'url': 'http://www.crunchyroll.com/wanna-be-the-strongest-in-the-world/episode-1-an-idol-wrestler-is-born-645513',
|
||||||
@@ -46,6 +46,22 @@ class CrunchyrollIE(InfoExtractor):
|
|||||||
# rtmp
|
# rtmp
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
},
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.crunchyroll.com/media-589804/culture-japan-1',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '589804',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Culture Japan Episode 1 – Rebuilding Japan after the 3.11',
|
||||||
|
'description': 'md5:fe2743efedb49d279552926d0bd0cd9e',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
'uploader': 'Danny Choo Network',
|
||||||
|
'upload_date': '20120213',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# rtmp
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.crunchyroll.fr/girl-friend-beta/episode-11-goodbye-la-mode-661697',
|
'url': 'http://www.crunchyroll.fr/girl-friend-beta/episode-11-goodbye-la-mode-661697',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
@@ -77,8 +93,8 @@ class CrunchyrollIE(InfoExtractor):
|
|||||||
self._login()
|
self._login()
|
||||||
|
|
||||||
def _decrypt_subtitles(self, data, iv, id):
|
def _decrypt_subtitles(self, data, iv, id):
|
||||||
data = bytes_to_intlist(data)
|
data = bytes_to_intlist(base64.b64decode(data.encode('utf-8')))
|
||||||
iv = bytes_to_intlist(iv)
|
iv = bytes_to_intlist(base64.b64decode(iv.encode('utf-8')))
|
||||||
id = int(id)
|
id = int(id)
|
||||||
|
|
||||||
def obfuscate_key_aux(count, modulo, start):
|
def obfuscate_key_aux(count, modulo, start):
|
||||||
@@ -102,13 +118,6 @@ class CrunchyrollIE(InfoExtractor):
|
|||||||
|
|
||||||
key = obfuscate_key(id)
|
key = obfuscate_key(id)
|
||||||
|
|
||||||
class Counter:
|
|
||||||
__value = iv
|
|
||||||
|
|
||||||
def next_value(self):
|
|
||||||
temp = self.__value
|
|
||||||
self.__value = inc(self.__value)
|
|
||||||
return temp
|
|
||||||
decrypted_data = intlist_to_bytes(aes_cbc_decrypt(data, key, iv))
|
decrypted_data = intlist_to_bytes(aes_cbc_decrypt(data, key, iv))
|
||||||
return zlib.decompress(decrypted_data)
|
return zlib.decompress(decrypted_data)
|
||||||
|
|
||||||
@@ -187,6 +196,16 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
|||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
def _extract_subtitles(self, subtitle):
|
||||||
|
sub_root = xml.etree.ElementTree.fromstring(subtitle)
|
||||||
|
return [{
|
||||||
|
'ext': 'srt',
|
||||||
|
'data': self._convert_subtitles_to_srt(sub_root),
|
||||||
|
}, {
|
||||||
|
'ext': 'ass',
|
||||||
|
'data': self._convert_subtitles_to_ass(sub_root),
|
||||||
|
}]
|
||||||
|
|
||||||
def _get_subtitles(self, video_id, webpage):
|
def _get_subtitles(self, video_id, webpage):
|
||||||
subtitles = {}
|
subtitles = {}
|
||||||
for sub_id, sub_name in re.findall(r'\?ssid=([0-9]+)" title="([^"]+)', webpage):
|
for sub_id, sub_name in re.findall(r'\?ssid=([0-9]+)" title="([^"]+)', webpage):
|
||||||
@@ -198,25 +217,11 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
|||||||
data = self._search_regex(r'<data>([^<]+)', sub_page, 'subtitle_data', fatal=False)
|
data = self._search_regex(r'<data>([^<]+)', sub_page, 'subtitle_data', fatal=False)
|
||||||
if not id or not iv or not data:
|
if not id or not iv or not data:
|
||||||
continue
|
continue
|
||||||
id = int(id)
|
|
||||||
iv = base64.b64decode(iv)
|
|
||||||
data = base64.b64decode(data)
|
|
||||||
|
|
||||||
subtitle = self._decrypt_subtitles(data, iv, id).decode('utf-8')
|
subtitle = self._decrypt_subtitles(data, iv, id).decode('utf-8')
|
||||||
lang_code = self._search_regex(r'lang_code=["\']([^"\']+)', subtitle, 'subtitle_lang_code', fatal=False)
|
lang_code = self._search_regex(r'lang_code=["\']([^"\']+)', subtitle, 'subtitle_lang_code', fatal=False)
|
||||||
if not lang_code:
|
if not lang_code:
|
||||||
continue
|
continue
|
||||||
sub_root = xml.etree.ElementTree.fromstring(subtitle)
|
subtitles[lang_code] = self._extract_subtitles(subtitle)
|
||||||
subtitles[lang_code] = [
|
|
||||||
{
|
|
||||||
'ext': 'srt',
|
|
||||||
'data': self._convert_subtitles_to_srt(sub_root),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'ext': 'ass',
|
|
||||||
'data': self._convert_subtitles_to_ass(sub_root),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
return subtitles
|
return subtitles
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
@@ -250,7 +255,7 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
|||||||
video_upload_date = unified_strdate(video_upload_date)
|
video_upload_date = unified_strdate(video_upload_date)
|
||||||
video_uploader = self._html_search_regex(r'<div>\s*Publisher:(.+?)</div>', webpage, 'video_uploader', fatal=False, flags=re.DOTALL)
|
video_uploader = self._html_search_regex(r'<div>\s*Publisher:(.+?)</div>', webpage, 'video_uploader', fatal=False, flags=re.DOTALL)
|
||||||
|
|
||||||
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 = compat_urllib_request.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')
|
||||||
@@ -263,16 +268,17 @@ 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('http://www.crunchyroll.com/xml/')
|
streamdata_req = compat_urllib_request.Request(
|
||||||
# urlencode doesn't work!
|
'http://www.crunchyroll.com/xml/?req=RpcApiVideoPlayer_GetStandardConfig&media_id=%s&video_format=%s&video_quality=%s'
|
||||||
streamdata_req.data = 'req=RpcApiVideoEncode%5FGetStreamInfo&video%5Fencode%5Fquality=' + stream_quality + '&media%5Fid=' + stream_id + '&video%5Fformat=' + stream_format
|
% (stream_id, stream_format, stream_quality),
|
||||||
|
compat_urllib_parse.urlencode({'current_page': url}).encode('utf-8'))
|
||||||
streamdata_req.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
streamdata_req.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
||||||
streamdata_req.add_header('Content-Length', str(len(streamdata_req.data)))
|
|
||||||
streamdata = self._download_xml(
|
streamdata = self._download_xml(
|
||||||
streamdata_req, video_id,
|
streamdata_req, video_id,
|
||||||
note='Downloading media info for %s' % video_format)
|
note='Downloading media info for %s' % video_format)
|
||||||
video_url = streamdata.find('.//host').text
|
stream_info = streamdata.find('./{default}preload/stream_info')
|
||||||
video_play_path = streamdata.find('.//file').text
|
video_url = stream_info.find('./host').text
|
||||||
|
video_play_path = stream_info.find('./file').text
|
||||||
formats.append({
|
formats.append({
|
||||||
'url': video_url,
|
'url': video_url,
|
||||||
'play_path': video_play_path,
|
'play_path': video_play_path,
|
||||||
|
|||||||
@@ -7,7 +7,10 @@ from ..utils import (
|
|||||||
int_or_none,
|
int_or_none,
|
||||||
unescapeHTML,
|
unescapeHTML,
|
||||||
find_xpath_attr,
|
find_xpath_attr,
|
||||||
|
smuggle_url,
|
||||||
|
determine_ext,
|
||||||
)
|
)
|
||||||
|
from .senateisvp import SenateISVPIE
|
||||||
|
|
||||||
|
|
||||||
class CSpanIE(InfoExtractor):
|
class CSpanIE(InfoExtractor):
|
||||||
@@ -35,11 +38,22 @@ class CSpanIE(InfoExtractor):
|
|||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
'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',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '342759',
|
'id': '342759',
|
||||||
|
'ext': 'mp4',
|
||||||
'title': 'General Motors Ignition Switch Recall',
|
'title': 'General Motors Ignition Switch Recall',
|
||||||
|
'duration': 14848,
|
||||||
|
'description': 'md5:70c7c3b8fa63fa60d42772440596034c'
|
||||||
},
|
},
|
||||||
'playlist_duration_sum': 14855,
|
}, {
|
||||||
|
# Video from senate.gov
|
||||||
|
'url': 'http://www.c-span.org/video/?104517-1/immigration-reforms-needed-protect-skilled-american-workers',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'judiciary031715',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Immigration Reforms Needed to Protect Skilled American Workers',
|
||||||
|
}
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
@@ -56,7 +70,7 @@ class CSpanIE(InfoExtractor):
|
|||||||
# present, otherwise this is a stripped version
|
# present, otherwise this is a stripped version
|
||||||
r'<p class=\'initial\'>(.*?)</p>'
|
r'<p class=\'initial\'>(.*?)</p>'
|
||||||
],
|
],
|
||||||
webpage, 'description', flags=re.DOTALL)
|
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
|
info_url = 'http://c-spanvideo.org/videoLibrary/assets/player/ajax-player.php?os=android&html5=program&id=' + video_id
|
||||||
data = self._download_json(info_url, video_id)
|
data = self._download_json(info_url, video_id)
|
||||||
@@ -68,7 +82,16 @@ class CSpanIE(InfoExtractor):
|
|||||||
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)
|
||||||
|
if senate_isvp_url:
|
||||||
|
surl = smuggle_url(senate_isvp_url, {'force_title': title})
|
||||||
|
return self.url_result(surl, 'SenateISVP', video_id, title)
|
||||||
|
|
||||||
files = data['video']['files']
|
files = data['video']['files']
|
||||||
|
try:
|
||||||
|
capfile = data['video']['capfile']['#text']
|
||||||
|
except KeyError:
|
||||||
|
capfile = None
|
||||||
|
|
||||||
entries = [{
|
entries = [{
|
||||||
'id': '%s_%d' % (video_id, partnum + 1),
|
'id': '%s_%d' % (video_id, partnum + 1),
|
||||||
@@ -79,8 +102,19 @@ class CSpanIE(InfoExtractor):
|
|||||||
'description': description,
|
'description': description,
|
||||||
'thumbnail': thumbnail,
|
'thumbnail': thumbnail,
|
||||||
'duration': int_or_none(f.get('length', {}).get('#text')),
|
'duration': int_or_none(f.get('length', {}).get('#text')),
|
||||||
|
'subtitles': {
|
||||||
|
'en': [{
|
||||||
|
'url': capfile,
|
||||||
|
'ext': determine_ext(capfile, 'dfxp')
|
||||||
|
}],
|
||||||
|
} if capfile else None,
|
||||||
} for partnum, f in enumerate(files)]
|
} for partnum, f in enumerate(files)]
|
||||||
|
|
||||||
|
if len(entries) == 1:
|
||||||
|
entry = dict(entries[0])
|
||||||
|
entry['id'] = video_id
|
||||||
|
return entry
|
||||||
|
else:
|
||||||
return {
|
return {
|
||||||
'_type': 'playlist',
|
'_type': 'playlist',
|
||||||
'entries': entries,
|
'entries': entries,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from ..utils import parse_iso8601, ExtractorError
|
|||||||
|
|
||||||
|
|
||||||
class CtsNewsIE(InfoExtractor):
|
class CtsNewsIE(InfoExtractor):
|
||||||
|
IE_DESC = '華視新聞'
|
||||||
# https connection failed (Connection reset)
|
# https connection failed (Connection reset)
|
||||||
_VALID_URL = r'http://news\.cts\.com\.tw/[a-z]+/[a-z]+/\d+/(?P<id>\d+)\.html'
|
_VALID_URL = r'http://news\.cts\.com\.tw/[a-z]+/[a-z]+/\d+/(?P<id>\d+)\.html'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
|
|||||||
@@ -25,8 +25,7 @@ class DailymotionBaseInfoExtractor(InfoExtractor):
|
|||||||
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 = compat_urllib_request.Request(url)
|
||||||
request.add_header('Cookie', 'family_filter=off')
|
request.add_header('Cookie', 'family_filter=off; ff=off')
|
||||||
request.add_header('Cookie', 'ff=off')
|
|
||||||
return request
|
return request
|
||||||
|
|
||||||
|
|
||||||
@@ -46,13 +45,15 @@ class DailymotionIE(DailymotionBaseInfoExtractor):
|
|||||||
|
|
||||||
_TESTS = [
|
_TESTS = [
|
||||||
{
|
{
|
||||||
'url': 'http://www.dailymotion.com/video/x33vw9_tutoriel-de-youtubeur-dl-des-video_tech',
|
'url': 'https://www.dailymotion.com/video/x2iuewm_steam-machine-models-pricing-listed-on-steam-store-ign-news_videogames',
|
||||||
'md5': '392c4b85a60a90dc4792da41ce3144eb',
|
'md5': '2137c41a8e78554bb09225b8eb322406',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'x33vw9',
|
'id': 'x2iuewm',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'uploader': 'Amphora Alex and Van .',
|
'uploader': 'IGN',
|
||||||
'title': 'Tutoriel de Youtubeur"DL DES VIDEO DE YOUTUBE"',
|
'title': 'Steam Machine Models, Pricing Listed on Steam Store - IGN News',
|
||||||
|
'upload_date': '20150306',
|
||||||
|
'duration': 74,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
# Vevo video
|
# Vevo video
|
||||||
@@ -86,7 +87,7 @@ class DailymotionIE(DailymotionBaseInfoExtractor):
|
|||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
url = 'http://www.dailymotion.com/video/%s' % video_id
|
url = 'https://www.dailymotion.com/video/%s' % video_id
|
||||||
|
|
||||||
# Retrieve video webpage to extract further information
|
# Retrieve video webpage to extract further information
|
||||||
request = self._build_request(url)
|
request = self._build_request(url)
|
||||||
@@ -107,13 +108,14 @@ class DailymotionIE(DailymotionBaseInfoExtractor):
|
|||||||
age_limit = self._rta_search(webpage)
|
age_limit = self._rta_search(webpage)
|
||||||
|
|
||||||
video_upload_date = None
|
video_upload_date = None
|
||||||
mobj = re.search(r'<div class="[^"]*uploaded_cont[^"]*" title="[^"]*">([0-9]{2})-([0-9]{2})-([0-9]{4})</div>', webpage)
|
mobj = re.search(r'<meta property="video:release_date" content="([0-9]{4})-([0-9]{2})-([0-9]{2}).+?"/>', webpage)
|
||||||
if mobj is not None:
|
if mobj is not None:
|
||||||
video_upload_date = mobj.group(3) + mobj.group(2) + mobj.group(1)
|
video_upload_date = mobj.group(1) + mobj.group(2) + mobj.group(3)
|
||||||
|
|
||||||
embed_url = 'http://www.dailymotion.com/embed/video/%s' % video_id
|
embed_url = 'https://www.dailymotion.com/embed/video/%s' % video_id
|
||||||
embed_page = self._download_webpage(embed_url, video_id,
|
embed_request = self._build_request(embed_url)
|
||||||
'Downloading embed page')
|
embed_page = self._download_webpage(
|
||||||
|
embed_request, video_id, 'Downloading embed page')
|
||||||
info = self._search_regex(r'var info = ({.*?}),$', embed_page,
|
info = self._search_regex(r'var info = ({.*?}),$', embed_page,
|
||||||
'video info', flags=re.MULTILINE)
|
'video info', flags=re.MULTILINE)
|
||||||
info = json.loads(info)
|
info = json.loads(info)
|
||||||
@@ -163,6 +165,7 @@ class DailymotionIE(DailymotionBaseInfoExtractor):
|
|||||||
'thumbnail': info['thumbnail_url'],
|
'thumbnail': info['thumbnail_url'],
|
||||||
'age_limit': age_limit,
|
'age_limit': age_limit,
|
||||||
'view_count': view_count,
|
'view_count': view_count,
|
||||||
|
'duration': info['duration']
|
||||||
}
|
}
|
||||||
|
|
||||||
def _get_subtitles(self, video_id, webpage):
|
def _get_subtitles(self, video_id, webpage):
|
||||||
@@ -224,7 +227,7 @@ class DailymotionPlaylistIE(DailymotionBaseInfoExtractor):
|
|||||||
|
|
||||||
class DailymotionUserIE(DailymotionPlaylistIE):
|
class DailymotionUserIE(DailymotionPlaylistIE):
|
||||||
IE_NAME = 'dailymotion:user'
|
IE_NAME = 'dailymotion:user'
|
||||||
_VALID_URL = r'https?://(?:www\.)?dailymotion\.[a-z]{2,3}/user/(?P<user>[^/]+)'
|
_VALID_URL = r'https?://(?:www\.)?dailymotion\.[a-z]{2,3}/(?:(?:old/)?user/)?(?P<user>[^/]+)$'
|
||||||
_PAGE_TEMPLATE = 'http://www.dailymotion.com/user/%s/%s'
|
_PAGE_TEMPLATE = 'http://www.dailymotion.com/user/%s/%s'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'https://www.dailymotion.com/user/nqtv',
|
'url': 'https://www.dailymotion.com/user/nqtv',
|
||||||
@@ -238,7 +241,8 @@ class DailymotionUserIE(DailymotionPlaylistIE):
|
|||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
user = mobj.group('user')
|
user = mobj.group('user')
|
||||||
webpage = self._download_webpage(url, user)
|
webpage = self._download_webpage(
|
||||||
|
'https://www.dailymotion.com/user/%s' % user, user)
|
||||||
full_user = unescapeHTML(self._html_search_regex(
|
full_user = unescapeHTML(self._html_search_regex(
|
||||||
r'<a class="nav-image" title="([^"]+)" href="/%s">' % re.escape(user),
|
r'<a class="nav-image" title="([^"]+)" href="/%s">' % re.escape(user),
|
||||||
webpage, 'user'))
|
webpage, 'user'))
|
||||||
@@ -249,3 +253,53 @@ class DailymotionUserIE(DailymotionPlaylistIE):
|
|||||||
'title': full_user,
|
'title': full_user,
|
||||||
'entries': self._extract_entries(user),
|
'entries': self._extract_entries(user),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class DailymotionCloudIE(DailymotionBaseInfoExtractor):
|
||||||
|
_VALID_URL_PREFIX = r'http://api\.dmcloud\.net/(?:player/)?embed/'
|
||||||
|
_VALID_URL = r'%s[^/]+/(?P<id>[^/?]+)' % _VALID_URL_PREFIX
|
||||||
|
_VALID_EMBED_URL = r'%s[^/]+/[^\'"]+' % _VALID_URL_PREFIX
|
||||||
|
|
||||||
|
_TESTS = [{
|
||||||
|
# From http://www.francetvinfo.fr/economie/entreprises/les-entreprises-familiales-le-secret-de-la-reussite_933271.html
|
||||||
|
# Tested at FranceTvInfo_2
|
||||||
|
'url': 'http://api.dmcloud.net/embed/4e7343f894a6f677b10006b4/556e03339473995ee145930c?auth=1464865870-0-jyhsm84b-ead4c701fb750cf9367bf4447167a3db&autoplay=1',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
# http://www.francetvinfo.fr/societe/larguez-les-amarres-le-cobaturage-se-developpe_980101.html
|
||||||
|
'url': 'http://api.dmcloud.net/player/embed/4e7343f894a6f677b10006b4/559545469473996d31429f06?auth=1467430263-0-90tglw2l-a3a4b64ed41efe48d7fccad85b8b8fda&autoplay=1',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _extract_dmcloud_url(self, webpage):
|
||||||
|
mobj = re.search(r'<iframe[^>]+src=[\'"](%s)[\'"]' % self._VALID_EMBED_URL, webpage)
|
||||||
|
if mobj:
|
||||||
|
return mobj.group(1)
|
||||||
|
|
||||||
|
mobj = re.search(
|
||||||
|
r'<input[^>]+id=[\'"]dmcloudUrlEmissionSelect[\'"][^>]+value=[\'"](%s)[\'"]' % self._VALID_EMBED_URL,
|
||||||
|
webpage)
|
||||||
|
if mobj:
|
||||||
|
return mobj.group(1)
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
|
request = self._build_request(url)
|
||||||
|
webpage = self._download_webpage(request, video_id)
|
||||||
|
|
||||||
|
title = self._html_search_regex(r'<title>([^>]+)</title>', webpage, 'title')
|
||||||
|
|
||||||
|
video_info = self._parse_json(self._search_regex(
|
||||||
|
r'var\s+info\s*=\s*([^;]+);', webpage, 'video info'), video_id)
|
||||||
|
|
||||||
|
# TODO: parse ios_url, which is in fact a manifest
|
||||||
|
video_url = video_info['mp4_url']
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'url': video_url,
|
||||||
|
'title': title,
|
||||||
|
'thumbnail': video_info.get('thumbnail_url'),
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,42 +3,47 @@ from __future__ import unicode_literals
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from ..utils import unified_strdate
|
||||||
|
|
||||||
|
|
||||||
class DFBIE(InfoExtractor):
|
class DFBIE(InfoExtractor):
|
||||||
IE_NAME = 'tv.dfb.de'
|
IE_NAME = 'tv.dfb.de'
|
||||||
_VALID_URL = r'https?://tv\.dfb\.de/video/[^/]+/(?P<id>\d+)'
|
_VALID_URL = r'https?://tv\.dfb\.de/video/(?P<display_id>[^/]+)/(?P<id>\d+)'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://tv.dfb.de/video/highlights-des-empfangs-in-berlin/9070/',
|
'url': 'http://tv.dfb.de/video/u-19-em-stimmen-zum-spiel-gegen-russland/11633/',
|
||||||
# The md5 is different each time
|
# The md5 is different each time
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '9070',
|
'id': '11633',
|
||||||
|
'display_id': 'u-19-em-stimmen-zum-spiel-gegen-russland',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'title': 'Highlights des Empfangs in Berlin',
|
'title': 'U 19-EM: Stimmen zum Spiel gegen Russland',
|
||||||
'upload_date': '20140716',
|
'upload_date': '20150714',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
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')
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, display_id)
|
||||||
player_info = self._download_xml(
|
player_info = self._download_xml(
|
||||||
'http://tv.dfb.de/server/hd_video.php?play=%s' % video_id,
|
'http://tv.dfb.de/server/hd_video.php?play=%s' % video_id,
|
||||||
video_id)
|
display_id)
|
||||||
video_info = player_info.find('video')
|
video_info = player_info.find('video')
|
||||||
|
|
||||||
f4m_info = self._download_xml(self._proto_relative_url(video_info.find('url').text.strip()), video_id)
|
f4m_info = self._download_xml(
|
||||||
|
self._proto_relative_url(video_info.find('url').text.strip()), display_id)
|
||||||
token_el = f4m_info.find('token')
|
token_el = f4m_info.find('token')
|
||||||
manifest_url = token_el.attrib['url'] + '?' + 'hdnea=' + token_el.attrib['auth'] + '&hdcore=3.2.0'
|
manifest_url = token_el.attrib['url'] + '?' + 'hdnea=' + token_el.attrib['auth'] + '&hdcore=3.2.0'
|
||||||
|
formats = self._extract_f4m_formats(manifest_url, display_id)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
|
'display_id': display_id,
|
||||||
'title': video_info.find('title').text,
|
'title': video_info.find('title').text,
|
||||||
'url': manifest_url,
|
|
||||||
'ext': 'flv',
|
|
||||||
'thumbnail': self._og_search_thumbnail(webpage),
|
'thumbnail': self._og_search_thumbnail(webpage),
|
||||||
'upload_date': ''.join(video_info.find('time_date').text.split('.')[::-1]),
|
'upload_date': unified_strdate(video_info.find('time_date').text),
|
||||||
|
'formats': formats,
|
||||||
}
|
}
|
||||||
|
|||||||
73
youtube_dl/extractor/dhm.py
Normal file
73
youtube_dl/extractor/dhm.py
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
xpath_text,
|
||||||
|
parse_duration,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DHMIE(InfoExtractor):
|
||||||
|
IE_DESC = 'Filmarchiv - Deutsches Historisches Museum'
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?dhm\.de/filmarchiv/(?:[^/]+/)+(?P<id>[^/]+)'
|
||||||
|
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://www.dhm.de/filmarchiv/die-filme/the-marshallplan-at-work-in-west-germany/',
|
||||||
|
'md5': '11c475f670209bf6acca0b2b7ef51827',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'the-marshallplan-at-work-in-west-germany',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'MARSHALL PLAN AT WORK IN WESTERN GERMANY, THE',
|
||||||
|
'description': 'md5:1fabd480c153f97b07add61c44407c82',
|
||||||
|
'duration': 660,
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.dhm.de/filmarchiv/02-mapping-the-wall/peter-g/rolle-1/',
|
||||||
|
'md5': '09890226332476a3e3f6f2cb74734aa5',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'rolle-1',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'ROLLE 1',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
playlist_url = self._search_regex(
|
||||||
|
r"file\s*:\s*'([^']+)'", webpage, 'playlist url')
|
||||||
|
|
||||||
|
playlist = self._download_xml(playlist_url, video_id)
|
||||||
|
|
||||||
|
track = playlist.find(
|
||||||
|
'./{http://xspf.org/ns/0/}trackList/{http://xspf.org/ns/0/}track')
|
||||||
|
|
||||||
|
video_url = xpath_text(
|
||||||
|
track, './{http://xspf.org/ns/0/}location',
|
||||||
|
'video url', fatal=True)
|
||||||
|
thumbnail = xpath_text(
|
||||||
|
track, './{http://xspf.org/ns/0/}image',
|
||||||
|
'thumbnail')
|
||||||
|
|
||||||
|
title = self._search_regex(
|
||||||
|
[r'dc:title="([^"]+)"', r'<title> »([^<]+)</title>'],
|
||||||
|
webpage, 'title').strip()
|
||||||
|
description = self._html_search_regex(
|
||||||
|
r'<p><strong>Description:</strong>(.+?)</p>',
|
||||||
|
webpage, 'description', default=None)
|
||||||
|
duration = parse_duration(self._search_regex(
|
||||||
|
r'<em>Length\s*</em>\s*:\s*</strong>([^<]+)',
|
||||||
|
webpage, 'duration', default=None))
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'url': video_url,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'duration': duration,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
}
|
||||||
@@ -2,19 +2,19 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
|
parse_duration,
|
||||||
parse_iso8601,
|
parse_iso8601,
|
||||||
int_or_none,
|
|
||||||
)
|
)
|
||||||
|
from ..compat import compat_str
|
||||||
|
|
||||||
|
|
||||||
class DiscoveryIE(InfoExtractor):
|
class DiscoveryIE(InfoExtractor):
|
||||||
_VALID_URL = r'http://www\.discovery\.com\/[a-zA-Z0-9\-]*/[a-zA-Z0-9\-]*/videos/(?P<id>[a-zA-Z0-9_\-]*)(?:\.htm)?'
|
_VALID_URL = r'http://www\.discovery\.com\/[a-zA-Z0-9\-]*/[a-zA-Z0-9\-]*/videos/(?P<id>[a-zA-Z0-9_\-]*)(?:\.htm)?'
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
'url': 'http://www.discovery.com/tv-shows/mythbusters/videos/mission-impossible-outtakes.htm',
|
'url': 'http://www.discovery.com/tv-shows/mythbusters/videos/mission-impossible-outtakes.htm',
|
||||||
'md5': '3c69d77d9b0d82bfd5e5932a60f26504',
|
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'mission-impossible-outtakes',
|
'id': '20769',
|
||||||
'ext': 'flv',
|
'ext': 'mp4',
|
||||||
'title': 'Mission Impossible Outtakes',
|
'title': 'Mission Impossible Outtakes',
|
||||||
'description': ('Watch Jamie Hyneman and Adam Savage practice being'
|
'description': ('Watch Jamie Hyneman and Adam Savage practice being'
|
||||||
' each other -- to the point of confusing Jamie\'s dog -- and '
|
' each other -- to the point of confusing Jamie\'s dog -- and '
|
||||||
@@ -24,22 +24,36 @@ class DiscoveryIE(InfoExtractor):
|
|||||||
'timestamp': 1303099200,
|
'timestamp': 1303099200,
|
||||||
'upload_date': '20110418',
|
'upload_date': '20110418',
|
||||||
},
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True, # requires ffmpeg
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.discovery.com/tv-shows/mythbusters/videos/mythbusters-the-simpsons',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'mythbusters-the-simpsons',
|
||||||
|
'title': 'MythBusters: The Simpsons',
|
||||||
|
},
|
||||||
|
'playlist_count': 9,
|
||||||
|
}]
|
||||||
|
|
||||||
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)
|
info = self._download_json(url + '?flat=1', video_id)
|
||||||
|
|
||||||
info = self._parse_json(self._search_regex(
|
video_title = info.get('playlist_title') or info.get('video_title')
|
||||||
r'(?s)<script type="application/ld\+json">(.*?)</script>',
|
|
||||||
webpage, 'video info'), video_id)
|
|
||||||
|
|
||||||
return {
|
entries = [{
|
||||||
'id': video_id,
|
'id': compat_str(video_info['id']),
|
||||||
'title': info['name'],
|
'formats': self._extract_m3u8_formats(
|
||||||
'url': info['contentURL'],
|
video_info['src'], video_id, ext='mp4',
|
||||||
'description': info.get('description'),
|
note='Download m3u8 information for video %d' % (idx + 1)),
|
||||||
'thumbnail': info.get('thumbnailUrl'),
|
'title': video_info['title'],
|
||||||
'timestamp': parse_iso8601(info.get('uploadDate')),
|
'description': video_info.get('description'),
|
||||||
'duration': int_or_none(info.get('duration')),
|
'duration': parse_duration(video_info.get('video_length')),
|
||||||
}
|
'webpage_url': video_info.get('href'),
|
||||||
|
'thumbnail': video_info.get('thumbnailURL'),
|
||||||
|
'alt_title': video_info.get('secondary_title'),
|
||||||
|
'timestamp': parse_iso8601(video_info.get('publishedDate')),
|
||||||
|
} for idx, video_info in enumerate(info['playlist'])]
|
||||||
|
|
||||||
|
return self.playlist_result(entries, video_id, video_title)
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ class DotsubIE(InfoExtractor):
|
|||||||
if not video_url:
|
if not video_url:
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
video_url = self._search_regex(
|
video_url = self._search_regex(
|
||||||
r'"file"\s*:\s*\'([^\']+)', webpage, 'video url')
|
[r'<source[^>]+src="([^"]+)"', r'"file"\s*:\s*\'([^\']+)'],
|
||||||
|
webpage, 'video url')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
|
|||||||
113
youtube_dl/extractor/douyutv.py
Normal file
113
youtube_dl/extractor/douyutv.py
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import time
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (ExtractorError, unescapeHTML)
|
||||||
|
from ..compat import (compat_str, compat_basestring)
|
||||||
|
|
||||||
|
|
||||||
|
class DouyuTVIE(InfoExtractor):
|
||||||
|
IE_DESC = '斗鱼'
|
||||||
|
_VALID_URL = r'http://(?:www\.)?douyutv\.com/(?P<id>[A-Za-z0-9]+)'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://www.douyutv.com/iseven',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '17732',
|
||||||
|
'display_id': 'iseven',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 're:^清晨醒脑!T-ara根本停不下来! [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$',
|
||||||
|
'description': 'md5:c93d6692dde6fe33809a46edcbecca44',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
'uploader': '7师傅',
|
||||||
|
'uploader_id': '431925',
|
||||||
|
'is_live': True,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True,
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.douyutv.com/85982',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '85982',
|
||||||
|
'display_id': '85982',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 're:^小漠从零单排记!——CSOL2躲猫猫 [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$',
|
||||||
|
'description': 'md5:746a2f7a253966a06755a912f0acc0d2',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
'uploader': 'douyu小漠',
|
||||||
|
'uploader_id': '3769985',
|
||||||
|
'is_live': True,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True,
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
|
if video_id.isdigit():
|
||||||
|
room_id = video_id
|
||||||
|
else:
|
||||||
|
page = self._download_webpage(url, video_id)
|
||||||
|
room_id = self._html_search_regex(
|
||||||
|
r'"room_id"\s*:\s*(\d+),', page, 'room id')
|
||||||
|
|
||||||
|
prefix = 'room/%s?aid=android&client_sys=android&time=%d' % (
|
||||||
|
room_id, int(time.time()))
|
||||||
|
|
||||||
|
auth = hashlib.md5((prefix + '1231').encode('ascii')).hexdigest()
|
||||||
|
config = self._download_json(
|
||||||
|
'http://www.douyutv.com/api/v1/%s&auth=%s' % (prefix, auth),
|
||||||
|
video_id)
|
||||||
|
|
||||||
|
data = config['data']
|
||||||
|
|
||||||
|
error_code = config.get('error', 0)
|
||||||
|
if error_code is not 0:
|
||||||
|
error_desc = 'Server reported error %i' % error_code
|
||||||
|
if isinstance(data, (compat_str, compat_basestring)):
|
||||||
|
error_desc += ': ' + data
|
||||||
|
raise ExtractorError(error_desc, expected=True)
|
||||||
|
|
||||||
|
show_status = data.get('show_status')
|
||||||
|
# 1 = live, 2 = offline
|
||||||
|
if show_status == '2':
|
||||||
|
raise ExtractorError(
|
||||||
|
'Live stream is offline', expected=True)
|
||||||
|
|
||||||
|
base_url = data['rtmp_url']
|
||||||
|
live_path = data['rtmp_live']
|
||||||
|
|
||||||
|
title = self._live_title(unescapeHTML(data['room_name']))
|
||||||
|
description = data.get('show_details')
|
||||||
|
thumbnail = data.get('room_src')
|
||||||
|
|
||||||
|
uploader = data.get('nickname')
|
||||||
|
uploader_id = data.get('owner_uid')
|
||||||
|
|
||||||
|
multi_formats = data.get('rtmp_multi_bitrate')
|
||||||
|
if not isinstance(multi_formats, dict):
|
||||||
|
multi_formats = {}
|
||||||
|
multi_formats['live'] = live_path
|
||||||
|
|
||||||
|
formats = [{
|
||||||
|
'url': '%s/%s' % (base_url, format_path),
|
||||||
|
'format_id': format_id,
|
||||||
|
'preference': 1 if format_id == 'live' else 0,
|
||||||
|
} for format_id, format_path in multi_formats.items()]
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': room_id,
|
||||||
|
'display_id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'uploader': uploader,
|
||||||
|
'uploader_id': uploader_id,
|
||||||
|
'formats': formats,
|
||||||
|
'is_live': True,
|
||||||
|
}
|
||||||
216
youtube_dl/extractor/dramafever.py
Normal file
216
youtube_dl/extractor/dramafever.py
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
# encoding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import itertools
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..compat import (
|
||||||
|
compat_HTTPError,
|
||||||
|
compat_urllib_parse,
|
||||||
|
compat_urllib_request,
|
||||||
|
compat_urlparse,
|
||||||
|
)
|
||||||
|
from ..utils import (
|
||||||
|
ExtractorError,
|
||||||
|
clean_html,
|
||||||
|
determine_ext,
|
||||||
|
int_or_none,
|
||||||
|
parse_iso8601,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DramaFeverBaseIE(InfoExtractor):
|
||||||
|
_LOGIN_URL = 'https://www.dramafever.com/accounts/login/'
|
||||||
|
_NETRC_MACHINE = 'dramafever'
|
||||||
|
|
||||||
|
_CONSUMER_SECRET = 'DA59dtVXYLxajktV'
|
||||||
|
|
||||||
|
_consumer_secret = None
|
||||||
|
|
||||||
|
def _get_consumer_secret(self):
|
||||||
|
mainjs = self._download_webpage(
|
||||||
|
'http://www.dramafever.com/static/51afe95/df2014/scripts/main.js',
|
||||||
|
None, 'Downloading main.js', fatal=False)
|
||||||
|
if not mainjs:
|
||||||
|
return self._CONSUMER_SECRET
|
||||||
|
return self._search_regex(
|
||||||
|
r"var\s+cs\s*=\s*'([^']+)'", mainjs,
|
||||||
|
'consumer secret', default=self._CONSUMER_SECRET)
|
||||||
|
|
||||||
|
def _real_initialize(self):
|
||||||
|
self._login()
|
||||||
|
self._consumer_secret = self._get_consumer_secret()
|
||||||
|
|
||||||
|
def _login(self):
|
||||||
|
(username, password) = self._get_login_info()
|
||||||
|
if username is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
login_form = {
|
||||||
|
'username': username,
|
||||||
|
'password': password,
|
||||||
|
}
|
||||||
|
|
||||||
|
request = compat_urllib_request.Request(
|
||||||
|
self._LOGIN_URL, compat_urllib_parse.urlencode(login_form).encode('utf-8'))
|
||||||
|
response = self._download_webpage(
|
||||||
|
request, None, 'Logging in as %s' % username)
|
||||||
|
|
||||||
|
if all(logout_pattern not in response
|
||||||
|
for logout_pattern in ['href="/accounts/logout/"', '>Log out<']):
|
||||||
|
error = self._html_search_regex(
|
||||||
|
r'(?s)class="hidden-xs prompt"[^>]*>(.+?)<',
|
||||||
|
response, 'error message', default=None)
|
||||||
|
if error:
|
||||||
|
raise ExtractorError('Unable to login: %s' % error, expected=True)
|
||||||
|
raise ExtractorError('Unable to log in')
|
||||||
|
|
||||||
|
|
||||||
|
class DramaFeverIE(DramaFeverBaseIE):
|
||||||
|
IE_NAME = 'dramafever'
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?dramafever\.com/drama/(?P<id>[0-9]+/[0-9]+)(?:/|$)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.dramafever.com/drama/4512/1/Cooking_with_Shin/',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '4512.1',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Cooking with Shin 4512.1',
|
||||||
|
'description': 'md5:a8eec7942e1664a6896fcd5e1287bfd0',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg',
|
||||||
|
'timestamp': 1404336058,
|
||||||
|
'upload_date': '20140702',
|
||||||
|
'duration': 343,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url).replace('/', '.')
|
||||||
|
|
||||||
|
try:
|
||||||
|
feed = self._download_json(
|
||||||
|
'http://www.dramafever.com/amp/episode/feed.json?guid=%s' % video_id,
|
||||||
|
video_id, 'Downloading episode JSON')['channel']['item']
|
||||||
|
except ExtractorError as e:
|
||||||
|
if isinstance(e.cause, compat_HTTPError):
|
||||||
|
raise ExtractorError(
|
||||||
|
'Currently unavailable in your country.', expected=True)
|
||||||
|
raise
|
||||||
|
|
||||||
|
media_group = feed.get('media-group', {})
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
for media_content in media_group['media-content']:
|
||||||
|
src = media_content.get('@attributes', {}).get('url')
|
||||||
|
if not src:
|
||||||
|
continue
|
||||||
|
ext = determine_ext(src)
|
||||||
|
if ext == 'f4m':
|
||||||
|
formats.extend(self._extract_f4m_formats(
|
||||||
|
src, video_id, f4m_id='hds'))
|
||||||
|
elif ext == 'm3u8':
|
||||||
|
formats.extend(self._extract_m3u8_formats(
|
||||||
|
src, video_id, 'mp4', m3u8_id='hls'))
|
||||||
|
else:
|
||||||
|
formats.append({
|
||||||
|
'url': src,
|
||||||
|
})
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
title = media_group.get('media-title')
|
||||||
|
description = media_group.get('media-description')
|
||||||
|
duration = int_or_none(media_group['media-content'][0].get('@attributes', {}).get('duration'))
|
||||||
|
thumbnail = self._proto_relative_url(
|
||||||
|
media_group.get('media-thumbnail', {}).get('@attributes', {}).get('url'))
|
||||||
|
timestamp = parse_iso8601(feed.get('pubDate'), ' ')
|
||||||
|
|
||||||
|
subtitles = {}
|
||||||
|
for media_subtitle in media_group.get('media-subTitle', []):
|
||||||
|
lang = media_subtitle.get('@attributes', {}).get('lang')
|
||||||
|
href = media_subtitle.get('@attributes', {}).get('href')
|
||||||
|
if not lang or not href:
|
||||||
|
continue
|
||||||
|
subtitles[lang] = [{
|
||||||
|
'ext': 'ttml',
|
||||||
|
'url': href,
|
||||||
|
}]
|
||||||
|
|
||||||
|
series_id, episode_number = video_id.split('.')
|
||||||
|
episode_info = self._download_json(
|
||||||
|
# We only need a single episode info, so restricting page size to one episode
|
||||||
|
# and dealing with page number as with episode number
|
||||||
|
r'http://www.dramafever.com/api/4/episode/series/?cs=%s&series_id=%s&page_number=%s&page_size=1'
|
||||||
|
% (self._consumer_secret, series_id, episode_number),
|
||||||
|
video_id, 'Downloading episode info JSON', fatal=False)
|
||||||
|
if episode_info:
|
||||||
|
value = episode_info.get('value')
|
||||||
|
if value:
|
||||||
|
subfile = value[0].get('subfile') or value[0].get('new_subfile')
|
||||||
|
if subfile and subfile != 'http://www.dramafever.com/st/':
|
||||||
|
subtitles.setdefault('English', []).append({
|
||||||
|
'ext': 'srt',
|
||||||
|
'url': subfile,
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'timestamp': timestamp,
|
||||||
|
'duration': duration,
|
||||||
|
'formats': formats,
|
||||||
|
'subtitles': subtitles,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class DramaFeverSeriesIE(DramaFeverBaseIE):
|
||||||
|
IE_NAME = 'dramafever:series'
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?dramafever\.com/drama/(?P<id>[0-9]+)(?:/(?:(?!\d+(?:/|$)).+)?)?$'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://www.dramafever.com/drama/4512/Cooking_with_Shin/',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '4512',
|
||||||
|
'title': 'Cooking with Shin',
|
||||||
|
'description': 'md5:84a3f26e3cdc3fb7f500211b3593b5c1',
|
||||||
|
},
|
||||||
|
'playlist_count': 4,
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.dramafever.com/drama/124/IRIS/',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '124',
|
||||||
|
'title': 'IRIS',
|
||||||
|
'description': 'md5:b3a30e587cf20c59bd1c01ec0ee1b862',
|
||||||
|
},
|
||||||
|
'playlist_count': 20,
|
||||||
|
}]
|
||||||
|
|
||||||
|
_PAGE_SIZE = 60 # max is 60 (see http://api.drama9.com/#get--api-4-episode-series-)
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
series_id = self._match_id(url)
|
||||||
|
|
||||||
|
series = self._download_json(
|
||||||
|
'http://www.dramafever.com/api/4/series/query/?cs=%s&series_id=%s'
|
||||||
|
% (self._consumer_secret, series_id),
|
||||||
|
series_id, 'Downloading series JSON')['series'][series_id]
|
||||||
|
|
||||||
|
title = clean_html(series['name'])
|
||||||
|
description = clean_html(series.get('description') or series.get('description_short'))
|
||||||
|
|
||||||
|
entries = []
|
||||||
|
for page_num in itertools.count(1):
|
||||||
|
episodes = self._download_json(
|
||||||
|
'http://www.dramafever.com/api/4/episode/series/?cs=%s&series_id=%s&page_size=%d&page_number=%d'
|
||||||
|
% (self._consumer_secret, series_id, self._PAGE_SIZE, page_num),
|
||||||
|
series_id, 'Downloading episodes JSON page #%d' % page_num)
|
||||||
|
for episode in episodes.get('value', []):
|
||||||
|
episode_url = episode.get('episode_url')
|
||||||
|
if not episode_url:
|
||||||
|
continue
|
||||||
|
entries.append(self.url_result(
|
||||||
|
compat_urlparse.urljoin(url, episode_url),
|
||||||
|
'DramaFever', episode.get('guid')))
|
||||||
|
if page_num == episodes['num_pages']:
|
||||||
|
break
|
||||||
|
|
||||||
|
return self.playlist_result(entries, series_id, title, description)
|
||||||
@@ -15,7 +15,6 @@ class DRBonanzaIE(InfoExtractor):
|
|||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.dr.dk/bonanza/serie/portraetter/Talkshowet.htm?assetId=65517',
|
'url': 'http://www.dr.dk/bonanza/serie/portraetter/Talkshowet.htm?assetId=65517',
|
||||||
'md5': 'fe330252ddea607635cf2eb2c99a0af3',
|
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '65517',
|
'id': '65517',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
@@ -26,6 +25,9 @@ class DRBonanzaIE(InfoExtractor):
|
|||||||
'upload_date': '20110120',
|
'upload_date': '20110120',
|
||||||
'duration': 3664,
|
'duration': 3664,
|
||||||
},
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True, # requires rtmp
|
||||||
|
},
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.dr.dk/bonanza/radio/serie/sport/fodbold.htm?assetId=59410',
|
'url': 'http://www.dr.dk/bonanza/radio/serie/sport/fodbold.htm?assetId=59410',
|
||||||
'md5': '6dfe039417e76795fb783c52da3de11d',
|
'md5': '6dfe039417e76795fb783c52da3de11d',
|
||||||
@@ -93,6 +95,11 @@ class DRBonanzaIE(InfoExtractor):
|
|||||||
'format_id': file['Type'].replace('Video', ''),
|
'format_id': file['Type'].replace('Video', ''),
|
||||||
'preference': preferencemap.get(file['Type'], -10),
|
'preference': preferencemap.get(file['Type'], -10),
|
||||||
})
|
})
|
||||||
|
if format['url'].startswith('rtmp'):
|
||||||
|
rtmp_url = format['url']
|
||||||
|
format['rtmp_live'] = True # --resume does not work
|
||||||
|
if '/bonanza/' in rtmp_url:
|
||||||
|
format['play_path'] = rtmp_url.split('/bonanza/')[1]
|
||||||
formats.append(format)
|
formats.append(format)
|
||||||
elif file['Type'] == "Thumb":
|
elif file['Type'] == "Thumb":
|
||||||
thumbnail = file['Location']
|
thumbnail = file['Location']
|
||||||
@@ -111,9 +118,6 @@ class DRBonanzaIE(InfoExtractor):
|
|||||||
description = '%s\n%s\n%s\n' % (
|
description = '%s\n%s\n%s\n' % (
|
||||||
info['Description'], info['Actors'], info['Colophon'])
|
info['Description'], info['Actors'], info['Colophon'])
|
||||||
|
|
||||||
for f in formats:
|
|
||||||
f['url'] = f['url'].replace('rtmp://vod-bonanza.gss.dr.dk/bonanza/', 'http://vodfiles.dr.dk/')
|
|
||||||
f['url'] = f['url'].replace('mp4:bonanza', 'bonanza')
|
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
display_id = re.sub(r'[^\w\d-]', '', re.sub(r' ', '-', title.lower())) + '-' + asset_id
|
display_id = re.sub(r'[^\w\d-]', '', re.sub(r' ', '-', title.lower())) + '-' + asset_id
|
||||||
|
|||||||
@@ -3,24 +3,33 @@ from __future__ import unicode_literals
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import unified_strdate
|
from ..utils import (
|
||||||
|
ExtractorError,
|
||||||
|
unified_strdate,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class DreiSatIE(InfoExtractor):
|
class DreiSatIE(InfoExtractor):
|
||||||
IE_NAME = '3sat'
|
IE_NAME = '3sat'
|
||||||
_VALID_URL = r'(?:http://)?(?:www\.)?3sat\.de/mediathek/(?:index\.php)?\?(?:(?:mode|display)=[^&]+&)*obj=(?P<id>[0-9]+)$'
|
_VALID_URL = r'(?:http://)?(?:www\.)?3sat\.de/mediathek/(?:index\.php|mediathek\.php)?\?(?:(?:mode|display)=[^&]+&)*obj=(?P<id>[0-9]+)$'
|
||||||
_TEST = {
|
_TESTS = [
|
||||||
'url': 'http://www.3sat.de/mediathek/index.php?obj=36983',
|
{
|
||||||
'md5': '9dcfe344732808dbfcc901537973c922',
|
'url': 'http://www.3sat.de/mediathek/index.php?mode=play&obj=45918',
|
||||||
|
'md5': 'be37228896d30a88f315b638900a026e',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '36983',
|
'id': '45918',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Kaffeeland Schweiz',
|
'title': 'Waidmannsheil',
|
||||||
'description': 'md5:cc4424b18b75ae9948b13929a0814033',
|
'description': 'md5:cce00ca1d70e21425e72c86a98a56817',
|
||||||
'uploader': '3sat',
|
'uploader': '3sat',
|
||||||
'upload_date': '20130622'
|
'upload_date': '20140913'
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://www.3sat.de/mediathek/mediathek.php?mode=play&obj=51066',
|
||||||
|
'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)
|
||||||
@@ -28,6 +37,15 @@ class DreiSatIE(InfoExtractor):
|
|||||||
details_url = 'http://www.3sat.de/mediathek/xmlservice/web/beitragsDetails?ak=web&id=%s' % video_id
|
details_url = 'http://www.3sat.de/mediathek/xmlservice/web/beitragsDetails?ak=web&id=%s' % video_id
|
||||||
details_doc = self._download_xml(details_url, video_id, 'Downloading video details')
|
details_doc = self._download_xml(details_url, video_id, 'Downloading video details')
|
||||||
|
|
||||||
|
status_code = details_doc.find('./status/statuscode')
|
||||||
|
if status_code is not None and status_code.text != 'ok':
|
||||||
|
code = status_code.text
|
||||||
|
if code == 'notVisibleAnymore':
|
||||||
|
message = 'Video %s is not available' % video_id
|
||||||
|
else:
|
||||||
|
message = '%s returned error: %s' % (self.IE_NAME, code)
|
||||||
|
raise ExtractorError(message, expected=True)
|
||||||
|
|
||||||
thumbnail_els = details_doc.findall('.//teaserimage')
|
thumbnail_els = details_doc.findall('.//teaserimage')
|
||||||
thumbnails = [{
|
thumbnails = [{
|
||||||
'width': int(te.attrib['key'].partition('x')[0]),
|
'width': int(te.attrib['key'].partition('x')[0]),
|
||||||
|
|||||||
@@ -36,25 +36,24 @@ class DrTuberIE(InfoExtractor):
|
|||||||
r'<source src="([^"]+)"', webpage, 'video URL')
|
r'<source src="([^"]+)"', webpage, 'video URL')
|
||||||
|
|
||||||
title = self._html_search_regex(
|
title = self._html_search_regex(
|
||||||
[r'class="hd_title" style="[^"]+">([^<]+)</h1>', r'<title>([^<]+) - \d+'],
|
[r'<p[^>]+class="title_substrate">([^<]+)</p>', r'<title>([^<]+) - \d+'],
|
||||||
webpage, 'title')
|
webpage, 'title')
|
||||||
|
|
||||||
thumbnail = self._html_search_regex(
|
thumbnail = self._html_search_regex(
|
||||||
r'poster="([^"]+)"',
|
r'poster="([^"]+)"',
|
||||||
webpage, 'thumbnail', fatal=False)
|
webpage, 'thumbnail', fatal=False)
|
||||||
|
|
||||||
like_count = str_to_int(self._html_search_regex(
|
def extract_count(id_, name):
|
||||||
r'<span id="rate_likes">\s*<img[^>]+>\s*<span>([\d,\.]+)</span>',
|
return str_to_int(self._html_search_regex(
|
||||||
webpage, 'like count', fatal=False))
|
r'<span[^>]+(?:class|id)="%s"[^>]*>([\d,\.]+)</span>' % id_,
|
||||||
dislike_count = str_to_int(self._html_search_regex(
|
webpage, '%s count' % name, fatal=False))
|
||||||
r'<span id="rate_dislikes">\s*<img[^>]+>\s*<span>([\d,\.]+)</span>',
|
|
||||||
webpage, 'like count', fatal=False))
|
like_count = extract_count('rate_likes', 'like')
|
||||||
comment_count = str_to_int(self._html_search_regex(
|
dislike_count = extract_count('rate_dislikes', 'dislike')
|
||||||
r'<span class="comments_count">([\d,\.]+)</span>',
|
comment_count = extract_count('comments_count', 'comment')
|
||||||
webpage, 'comment count', fatal=False))
|
|
||||||
|
|
||||||
cats_str = self._search_regex(
|
cats_str = self._search_regex(
|
||||||
r'<span>Categories:</span><div>(.+?)</div>', webpage, 'categories', fatal=False)
|
r'<div[^>]+class="categories_list">(.+?)</div>', webpage, 'categories', fatal=False)
|
||||||
categories = [] if not cats_str else re.findall(r'<a title="([^"]+)"', cats_str)
|
categories = [] if not cats_str else re.findall(r'<a title="([^"]+)"', cats_str)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,23 +1,27 @@
|
|||||||
|
# coding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .common import InfoExtractor, ExtractorError
|
from .common import InfoExtractor
|
||||||
from ..utils import parse_iso8601
|
from ..utils import (
|
||||||
|
ExtractorError,
|
||||||
|
parse_iso8601,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class DRTVIE(InfoExtractor):
|
class DRTVIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?dr\.dk/tv/se/(?:[^/]+/)*(?P<id>[\da-z-]+)(?:[/#?]|$)'
|
_VALID_URL = r'https?://(?:www\.)?dr\.dk/tv/se/(?:[^/]+/)*(?P<id>[\da-z-]+)(?:[/#?]|$)'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.dr.dk/tv/se/partiets-mand/partiets-mand-7-8',
|
'url': 'https://www.dr.dk/tv/se/boern/ultra/panisk-paske/panisk-paske-5',
|
||||||
'md5': '4a7e1dd65cdb2643500a3f753c942f25',
|
'md5': 'dc515a9ab50577fa14cc4e4b0265168f',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'partiets-mand-7-8',
|
'id': 'panisk-paske-5',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Partiets mand (7:8)',
|
'title': 'Panisk Påske (5)',
|
||||||
'description': 'md5:a684b90a8f9336cd4aab94b7647d7862',
|
'description': 'md5:ca14173c5ab24cd26b0fcc074dff391c',
|
||||||
'timestamp': 1403047940,
|
'timestamp': 1426984612,
|
||||||
'upload_date': '20140617',
|
'upload_date': '20150322',
|
||||||
'duration': 1299.040,
|
'duration': 1455,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,6 +30,10 @@ class DRTVIE(InfoExtractor):
|
|||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
if '>Programmet er ikke længere tilgængeligt' in webpage:
|
||||||
|
raise ExtractorError(
|
||||||
|
'Video %s is not available' % video_id, expected=True)
|
||||||
|
|
||||||
video_id = self._search_regex(
|
video_id = self._search_regex(
|
||||||
r'data-(?:material-identifier|episode-slug)="([^"]+)"',
|
r'data-(?:material-identifier|episode-slug)="([^"]+)"',
|
||||||
webpage, 'video id')
|
webpage, 'video id')
|
||||||
@@ -55,18 +63,30 @@ class DRTVIE(InfoExtractor):
|
|||||||
restricted_to_denmark = asset['RestrictedToDenmark']
|
restricted_to_denmark = asset['RestrictedToDenmark']
|
||||||
spoken_subtitles = asset['Target'] == 'SpokenSubtitles'
|
spoken_subtitles = asset['Target'] == 'SpokenSubtitles'
|
||||||
for link in asset['Links']:
|
for link in asset['Links']:
|
||||||
target = link['Target']
|
|
||||||
uri = link['Uri']
|
uri = link['Uri']
|
||||||
|
target = link['Target']
|
||||||
format_id = target
|
format_id = target
|
||||||
preference = -1 if target == 'HDS' else -2
|
preference = None
|
||||||
if spoken_subtitles:
|
if spoken_subtitles:
|
||||||
preference -= 2
|
preference = -1
|
||||||
format_id += '-spoken-subtitles'
|
format_id += '-spoken-subtitles'
|
||||||
|
if target == 'HDS':
|
||||||
|
formats.extend(self._extract_f4m_formats(
|
||||||
|
uri + '?hdcore=3.3.0&plugin=aasp-3.3.0.99.43',
|
||||||
|
video_id, preference, f4m_id=format_id))
|
||||||
|
elif target == 'HLS':
|
||||||
|
formats.extend(self._extract_m3u8_formats(
|
||||||
|
uri, video_id, 'mp4', preference=preference,
|
||||||
|
m3u8_id=format_id))
|
||||||
|
else:
|
||||||
|
bitrate = link.get('Bitrate')
|
||||||
|
if bitrate:
|
||||||
|
format_id += '-%s' % bitrate
|
||||||
formats.append({
|
formats.append({
|
||||||
'url': uri + '?hdcore=3.3.0&plugin=aasp-3.3.0.99.43' if target == 'HDS' else uri,
|
'url': uri,
|
||||||
'format_id': format_id,
|
'format_id': format_id,
|
||||||
'ext': link['FileFormat'],
|
'tbr': bitrate,
|
||||||
'preference': preference,
|
'ext': link.get('FileFormat'),
|
||||||
})
|
})
|
||||||
subtitles_list = asset.get('SubtitlesList')
|
subtitles_list = asset.get('SubtitlesList')
|
||||||
if isinstance(subtitles_list, list):
|
if isinstance(subtitles_list, list):
|
||||||
|
|||||||
@@ -28,12 +28,12 @@ class DumpIE(InfoExtractor):
|
|||||||
video_url = self._search_regex(
|
video_url = self._search_regex(
|
||||||
r's1.addVariable\("file",\s*"([^"]+)"', webpage, 'video URL')
|
r's1.addVariable\("file",\s*"([^"]+)"', webpage, 'video URL')
|
||||||
|
|
||||||
thumb = self._og_search_thumbnail(webpage)
|
title = self._og_search_title(webpage)
|
||||||
title = self._search_regex(r'<b>([^"]+)</b>', webpage, 'title')
|
thumbnail = self._og_search_thumbnail(webpage)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': title,
|
'title': title,
|
||||||
'url': video_url,
|
'url': video_url,
|
||||||
'thumbnail': thumb,
|
'thumbnail': thumbnail,
|
||||||
}
|
}
|
||||||
|
|||||||
60
youtube_dl/extractor/dumpert.py
Normal file
60
youtube_dl/extractor/dumpert.py
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import base64
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..compat import compat_urllib_request
|
||||||
|
from ..utils import qualities
|
||||||
|
|
||||||
|
|
||||||
|
class DumpertIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?dumpert\.nl/mediabase/(?P<id>[0-9]+/[0-9a-zA-Z]+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.dumpert.nl/mediabase/6646981/951bc60f/',
|
||||||
|
'md5': '1b9318d7d5054e7dcb9dc7654f21d643',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '6646981/951bc60f',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Ik heb nieuws voor je',
|
||||||
|
'description': 'Niet schrikken hoor',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
|
req = compat_urllib_request.Request(url)
|
||||||
|
req.add_header('Cookie', 'nsfw=1; cpc=10')
|
||||||
|
webpage = self._download_webpage(req, video_id)
|
||||||
|
|
||||||
|
files_base64 = self._search_regex(
|
||||||
|
r'data-files="([^"]+)"', webpage, 'data files')
|
||||||
|
|
||||||
|
files = self._parse_json(
|
||||||
|
base64.b64decode(files_base64.encode('utf-8')).decode('utf-8'),
|
||||||
|
video_id)
|
||||||
|
|
||||||
|
quality = qualities(['flv', 'mobile', 'tablet', '720p'])
|
||||||
|
|
||||||
|
formats = [{
|
||||||
|
'url': video_url,
|
||||||
|
'format_id': format_id,
|
||||||
|
'quality': quality(format_id),
|
||||||
|
} for format_id, video_url in files.items() if format_id != 'still']
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
title = self._html_search_meta(
|
||||||
|
'title', webpage) or self._og_search_title(webpage)
|
||||||
|
description = self._html_search_meta(
|
||||||
|
'description', webpage) or self._og_search_description(webpage)
|
||||||
|
thumbnail = files.get('still') or self._og_search_thumbnail(webpage)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'formats': formats
|
||||||
|
}
|
||||||
99
youtube_dl/extractor/eagleplatform.py
Normal file
99
youtube_dl/extractor/eagleplatform.py
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
ExtractorError,
|
||||||
|
int_or_none,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class EaglePlatformIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'''(?x)
|
||||||
|
(?:
|
||||||
|
eagleplatform:(?P<custom_host>[^/]+):|
|
||||||
|
https?://(?P<host>.+?\.media\.eagleplatform\.com)/index/player\?.*\brecord_id=
|
||||||
|
)
|
||||||
|
(?P<id>\d+)
|
||||||
|
'''
|
||||||
|
_TESTS = [{
|
||||||
|
# http://lenta.ru/news/2015/03/06/navalny/
|
||||||
|
'url': 'http://lentaru.media.eagleplatform.com/index/player?player=new&record_id=227304&player_template_id=5201',
|
||||||
|
'md5': '0b7994faa2bd5c0f69a3db6db28d078d',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '227304',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Навальный вышел на свободу',
|
||||||
|
'description': 'md5:d97861ac9ae77377f3f20eaf9d04b4f5',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
'duration': 87,
|
||||||
|
'view_count': int,
|
||||||
|
'age_limit': 0,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
# http://muz-tv.ru/play/7129/
|
||||||
|
# http://media.clipyou.ru/index/player?record_id=12820&width=730&height=415&autoplay=true
|
||||||
|
'url': 'eagleplatform:media.clipyou.ru:12820',
|
||||||
|
'md5': '6c2ebeab03b739597ce8d86339d5a905',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '12820',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': "'O Sole Mio",
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
'duration': 216,
|
||||||
|
'view_count': int,
|
||||||
|
},
|
||||||
|
'skip': 'Georestricted',
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _handle_error(self, response):
|
||||||
|
status = int_or_none(response.get('status', 200))
|
||||||
|
if status != 200:
|
||||||
|
raise ExtractorError(' '.join(response['errors']), expected=True)
|
||||||
|
|
||||||
|
def _download_json(self, url_or_request, video_id, note='Downloading JSON metadata'):
|
||||||
|
response = super(EaglePlatformIE, self)._download_json(url_or_request, video_id, note)
|
||||||
|
self._handle_error(response)
|
||||||
|
return response
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
host, video_id = mobj.group('custom_host') or mobj.group('host'), mobj.group('id')
|
||||||
|
|
||||||
|
player_data = self._download_json(
|
||||||
|
'http://%s/api/player_data?id=%s' % (host, video_id), video_id)
|
||||||
|
|
||||||
|
media = player_data['data']['playlist']['viewports'][0]['medialist'][0]
|
||||||
|
|
||||||
|
title = media['title']
|
||||||
|
description = media.get('description')
|
||||||
|
thumbnail = media.get('snapshot')
|
||||||
|
duration = int_or_none(media.get('duration'))
|
||||||
|
view_count = int_or_none(media.get('views'))
|
||||||
|
|
||||||
|
age_restriction = media.get('age_restriction')
|
||||||
|
age_limit = None
|
||||||
|
if age_restriction:
|
||||||
|
age_limit = 0 if age_restriction == 'allow_all' else 18
|
||||||
|
|
||||||
|
m3u8_data = self._download_json(
|
||||||
|
media['sources']['secure_m3u8']['auto'],
|
||||||
|
video_id, 'Downloading m3u8 JSON')
|
||||||
|
|
||||||
|
formats = self._extract_m3u8_formats(
|
||||||
|
m3u8_data['data'][0], video_id,
|
||||||
|
'mp4', entry_protocol='m3u8_native')
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'duration': duration,
|
||||||
|
'view_count': view_count,
|
||||||
|
'age_limit': age_limit,
|
||||||
|
'formats': formats,
|
||||||
|
}
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from ..compat import (
|
|
||||||
compat_urllib_parse,
|
|
||||||
)
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from ..compat import compat_urllib_parse_unquote
|
||||||
|
|
||||||
|
|
||||||
class EHowIE(InfoExtractor):
|
class EHowIE(InfoExtractor):
|
||||||
@@ -26,7 +24,7 @@ class EHowIE(InfoExtractor):
|
|||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
video_url = self._search_regex(
|
video_url = self._search_regex(
|
||||||
r'(?:file|source)=(http[^\'"&]*)', webpage, 'video URL')
|
r'(?:file|source)=(http[^\'"&]*)', webpage, 'video URL')
|
||||||
final_url = compat_urllib_parse.unquote(video_url)
|
final_url = compat_urllib_parse_unquote(video_url)
|
||||||
uploader = self._html_search_meta('uploader', webpage)
|
uploader = self._html_search_meta('uploader', webpage)
|
||||||
title = self._og_search_title(webpage).replace(' | eHow', '')
|
title = self._og_search_title(webpage).replace(' | eHow', '')
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
import random
|
import random
|
||||||
import re
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import (
|
||||||
@@ -103,20 +102,23 @@ class EightTracksIE(InfoExtractor):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
playlist_id = self._match_id(url)
|
||||||
playlist_id = mobj.group('id')
|
|
||||||
|
|
||||||
webpage = self._download_webpage(url, playlist_id)
|
webpage = self._download_webpage(url, playlist_id)
|
||||||
|
|
||||||
json_like = self._search_regex(
|
data = self._parse_json(
|
||||||
r"(?s)PAGE.mix = (.*?);\n", webpage, 'trax information')
|
self._search_regex(
|
||||||
data = json.loads(json_like)
|
r"(?s)PAGE\.mix\s*=\s*({.+?});\n", webpage, 'trax information'),
|
||||||
|
playlist_id)
|
||||||
|
|
||||||
session = str(random.randint(0, 1000000000))
|
session = str(random.randint(0, 1000000000))
|
||||||
mix_id = data['id']
|
mix_id = data['id']
|
||||||
track_count = data['tracks_count']
|
track_count = data['tracks_count']
|
||||||
duration = data['duration']
|
duration = data['duration']
|
||||||
avg_song_duration = float(duration) / track_count
|
avg_song_duration = float(duration) / track_count
|
||||||
|
# duration is sometimes negative, use predefined avg duration
|
||||||
|
if avg_song_duration <= 0:
|
||||||
|
avg_song_duration = 300
|
||||||
first_url = 'http://8tracks.com/sets/%s/play?player=sm&mix_id=%s&format=jsonh' % (session, mix_id)
|
first_url = 'http://8tracks.com/sets/%s/play?player=sm&mix_id=%s&format=jsonh' % (session, mix_id)
|
||||||
next_url = first_url
|
next_url = first_url
|
||||||
entries = []
|
entries = []
|
||||||
|
|||||||
@@ -6,56 +6,42 @@ import json
|
|||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
parse_iso8601,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class EllenTVIE(InfoExtractor):
|
class EllenTVIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?(?:ellentv|ellentube)\.com/videos/(?P<id>[a-z0-9_-]+)'
|
_VALID_URL = r'https?://(?:www\.)?(?:ellentv|ellentube)\.com/videos/(?P<id>[a-z0-9_-]+)'
|
||||||
_TESTS = [{
|
_TEST = {
|
||||||
'url': 'http://www.ellentv.com/videos/0-7jqrsr18/',
|
'url': 'http://www.ellentv.com/videos/0-ipq1gsai/',
|
||||||
'md5': 'e4af06f3bf0d5f471921a18db5764642',
|
'md5': '8e3c576bf2e9bfff4d76565f56f94c9c',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '0-7jqrsr18',
|
'id': '0_ipq1gsai',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'What\'s Wrong with These Photos? A Whole Lot',
|
'title': 'Fast Fingers of Fate',
|
||||||
'description': 'md5:35f152dc66b587cf13e6d2cf4fa467f6',
|
'description': 'md5:587e79fbbd0d73b148bc596d99ce48e6',
|
||||||
'timestamp': 1406876400,
|
'timestamp': 1428035648,
|
||||||
'upload_date': '20140801',
|
'upload_date': '20150403',
|
||||||
|
'uploader_id': 'batchUser',
|
||||||
}
|
}
|
||||||
}, {
|
|
||||||
'url': 'http://ellentube.com/videos/0-dvzmabd5/',
|
|
||||||
'md5': '98238118eaa2bbdf6ad7f708e3e4f4eb',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '0-dvzmabd5',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': '1 year old twin sister makes her brother laugh',
|
|
||||||
'description': '1 year old twin sister makes her brother laugh',
|
|
||||||
'timestamp': 1419542075,
|
|
||||||
'upload_date': '20141225',
|
|
||||||
}
|
}
|
||||||
}]
|
|
||||||
|
|
||||||
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)
|
webpage = self._download_webpage(
|
||||||
video_url = self._html_search_meta('VideoURL', webpage, 'url')
|
'http://widgets.ellentube.com/videos/%s' % video_id,
|
||||||
title = self._og_search_title(webpage, default=None) or self._search_regex(
|
video_id)
|
||||||
r'pageName\s*=\s*"([^"]+)"', webpage, 'title')
|
|
||||||
description = self._html_search_meta(
|
|
||||||
'description', webpage, 'description') or self._og_search_description(webpage)
|
|
||||||
timestamp = parse_iso8601(self._search_regex(
|
|
||||||
r'<span class="publish-date"><time datetime="([^"]+)">',
|
|
||||||
webpage, 'timestamp'))
|
|
||||||
|
|
||||||
return {
|
partner_id = self._search_regex(
|
||||||
'id': video_id,
|
r"var\s+partnerId\s*=\s*'([^']+)", webpage, 'partner id')
|
||||||
'url': video_url,
|
|
||||||
'title': title,
|
kaltura_id = self._search_regex(
|
||||||
'description': description,
|
[r'id="kaltura_player_([^"]+)"',
|
||||||
'timestamp': timestamp,
|
r"_wb_entry_id\s*:\s*'([^']+)",
|
||||||
}
|
r'data-kaltura-entry-id="([^"]+)'],
|
||||||
|
webpage, 'kaltura id')
|
||||||
|
|
||||||
|
return self.url_result('kaltura:%s:%s' % (partner_id, kaltura_id), 'Kaltura')
|
||||||
|
|
||||||
|
|
||||||
class EllenTVClipsIE(InfoExtractor):
|
class EllenTVClipsIE(InfoExtractor):
|
||||||
@@ -67,7 +53,7 @@ class EllenTVClipsIE(InfoExtractor):
|
|||||||
'id': 'meryl-streep-vanessa-hudgens',
|
'id': 'meryl-streep-vanessa-hudgens',
|
||||||
'title': 'Meryl Streep, Vanessa Hudgens',
|
'title': 'Meryl Streep, Vanessa Hudgens',
|
||||||
},
|
},
|
||||||
'playlist_mincount': 9,
|
'playlist_mincount': 7,
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
@@ -91,4 +77,8 @@ class EllenTVClipsIE(InfoExtractor):
|
|||||||
raise ExtractorError('Failed to download JSON', cause=ve)
|
raise ExtractorError('Failed to download JSON', cause=ve)
|
||||||
|
|
||||||
def _extract_entries(self, playlist):
|
def _extract_entries(self, playlist):
|
||||||
return [self.url_result(item['url'], 'EllenTV') for item in playlist]
|
return [
|
||||||
|
self.url_result(
|
||||||
|
'kaltura:%s:%s' % (item['kaltura_partner_id'], item['kaltura_entry_id']),
|
||||||
|
'Kaltura')
|
||||||
|
for item in playlist]
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from .tnaflix import TNAFlixIE
|
|
||||||
|
|
||||||
|
|
||||||
class EMPFlixIE(TNAFlixIE):
|
|
||||||
_VALID_URL = r'^https?://www\.empflix\.com/videos/(?P<display_id>[0-9a-zA-Z-]+)-(?P<id>[0-9]+)\.html'
|
|
||||||
|
|
||||||
_TITLE_REGEX = r'name="title" value="(?P<title>[^"]*)"'
|
|
||||||
_DESCRIPTION_REGEX = r'name="description" value="([^"]*)"'
|
|
||||||
_CONFIG_REGEX = r'flashvars\.config\s*=\s*escape\("([^"]+)"'
|
|
||||||
|
|
||||||
_TEST = {
|
|
||||||
'url': 'http://www.empflix.com/videos/Amateur-Finger-Fuck-33051.html',
|
|
||||||
'md5': 'b1bc15b6412d33902d6e5952035fcabc',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '33051',
|
|
||||||
'display_id': 'Amateur-Finger-Fuck',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': 'Amateur Finger Fuck',
|
|
||||||
'description': 'Amateur solo finger fucking.',
|
|
||||||
'thumbnail': 're:https?://.*\.jpg$',
|
|
||||||
'age_limit': 18,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +1,20 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from ..compat import compat_urllib_parse
|
||||||
|
from ..utils import (
|
||||||
|
ExtractorError,
|
||||||
|
unescapeHTML
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class EroProfileIE(InfoExtractor):
|
class EroProfileIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?eroprofile\.com/m/videos/view/(?P<id>[^/]+)'
|
_VALID_URL = r'https?://(?:www\.)?eroprofile\.com/m/videos/view/(?P<id>[^/]+)'
|
||||||
_TEST = {
|
_LOGIN_URL = 'http://www.eroprofile.com/auth/auth.php?'
|
||||||
|
_NETRC_MACHINE = 'eroprofile'
|
||||||
|
_TESTS = [{
|
||||||
'url': 'http://www.eroprofile.com/m/videos/view/sexy-babe-softcore',
|
'url': 'http://www.eroprofile.com/m/videos/view/sexy-babe-softcore',
|
||||||
'md5': 'c26f351332edf23e1ea28ce9ec9de32f',
|
'md5': 'c26f351332edf23e1ea28ce9ec9de32f',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@@ -16,19 +25,61 @@ class EroProfileIE(InfoExtractor):
|
|||||||
'thumbnail': 're:https?://.*\.jpg',
|
'thumbnail': 're:https?://.*\.jpg',
|
||||||
'age_limit': 18,
|
'age_limit': 18,
|
||||||
}
|
}
|
||||||
}
|
}, {
|
||||||
|
'url': 'http://www.eroprofile.com/m/videos/view/Try-It-On-Pee_cut_2-wmv-4shared-com-file-sharing-download-movie-file',
|
||||||
|
'md5': '1baa9602ede46ce904c431f5418d8916',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '1133519',
|
||||||
|
'ext': 'm4v',
|
||||||
|
'title': 'Try It On Pee_cut_2.wmv - 4shared.com - file sharing - download movie file',
|
||||||
|
'thumbnail': 're:https?://.*\.jpg',
|
||||||
|
'age_limit': 18,
|
||||||
|
},
|
||||||
|
'skip': 'Requires login',
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _login(self):
|
||||||
|
(username, password) = self._get_login_info()
|
||||||
|
if username is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
query = compat_urllib_parse.urlencode({
|
||||||
|
'username': username,
|
||||||
|
'password': password,
|
||||||
|
'url': 'http://www.eroprofile.com/',
|
||||||
|
})
|
||||||
|
login_url = self._LOGIN_URL + query
|
||||||
|
login_page = self._download_webpage(login_url, None, False)
|
||||||
|
|
||||||
|
m = re.search(r'Your username or password was incorrect\.', login_page)
|
||||||
|
if m:
|
||||||
|
raise ExtractorError(
|
||||||
|
'Wrong username and/or password.', expected=True)
|
||||||
|
|
||||||
|
self.report_login()
|
||||||
|
redirect_url = self._search_regex(
|
||||||
|
r'<script[^>]+?src="([^"]+)"', login_page, 'login redirect url')
|
||||||
|
self._download_webpage(redirect_url, None, False)
|
||||||
|
|
||||||
|
def _real_initialize(self):
|
||||||
|
self._login()
|
||||||
|
|
||||||
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)
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
|
||||||
|
m = re.search(r'You must be logged in to view this video\.', webpage)
|
||||||
|
if m:
|
||||||
|
raise ExtractorError(
|
||||||
|
'This video requires login. Please specify a username and password and try again.', expected=True)
|
||||||
|
|
||||||
video_id = self._search_regex(
|
video_id = self._search_regex(
|
||||||
[r"glbUpdViews\s*\('\d*','(\d+)'", r'p/report/video/(\d+)'],
|
[r"glbUpdViews\s*\('\d*','(\d+)'", r'p/report/video/(\d+)'],
|
||||||
webpage, 'video id', default=None)
|
webpage, 'video id', default=None)
|
||||||
|
|
||||||
video_url = self._search_regex(
|
video_url = unescapeHTML(self._search_regex(
|
||||||
r'<source src="([^"]+)', webpage, 'video url')
|
r'<source src="([^"]+)', webpage, 'video url'))
|
||||||
title = self._html_search_regex(
|
title = self._html_search_regex(
|
||||||
r'Title:</th><td>([^<]+)</td>', webpage, 'title')
|
r'Title:</th><td>([^<]+)</td>', webpage, 'title')
|
||||||
thumbnail = self._search_regex(
|
thumbnail = self._search_regex(
|
||||||
|
|||||||
@@ -1,128 +1,107 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import compat_urllib_request
|
||||||
compat_urllib_parse,
|
|
||||||
compat_urllib_request,
|
|
||||||
)
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
determine_ext,
|
||||||
js_to_json,
|
clean_html,
|
||||||
parse_duration,
|
int_or_none,
|
||||||
|
float_or_none,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _decrypt_config(key, string):
|
||||||
|
a = ''
|
||||||
|
i = ''
|
||||||
|
r = ''
|
||||||
|
|
||||||
|
while len(a) < (len(string) / 2):
|
||||||
|
a += key
|
||||||
|
|
||||||
|
a = a[0:int(len(string) / 2)]
|
||||||
|
|
||||||
|
t = 0
|
||||||
|
while t < len(string):
|
||||||
|
i += chr(int(string[t] + string[t + 1], 16))
|
||||||
|
t += 2
|
||||||
|
|
||||||
|
icko = [s for s in i]
|
||||||
|
|
||||||
|
for t, c in enumerate(a):
|
||||||
|
r += chr(ord(c) ^ ord(icko[t]))
|
||||||
|
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
class EscapistIE(InfoExtractor):
|
class EscapistIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://?(www\.)?escapistmagazine\.com/videos/view/[^/?#]+/(?P<id>[0-9]+)-[^/?#]*(?:$|[?#])'
|
_VALID_URL = r'https?://?(?:www\.)?escapistmagazine\.com/videos/view/[^/?#]+/(?P<id>[0-9]+)-[^/?#]*(?:$|[?#])'
|
||||||
_USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko'
|
_TESTS = [{
|
||||||
_TEST = {
|
|
||||||
'url': 'http://www.escapistmagazine.com/videos/view/the-escapist-presents/6618-Breaking-Down-Baldurs-Gate',
|
'url': 'http://www.escapistmagazine.com/videos/view/the-escapist-presents/6618-Breaking-Down-Baldurs-Gate',
|
||||||
'md5': 'ab3a706c681efca53f0a35f1415cf0d1',
|
'md5': 'ab3a706c681efca53f0a35f1415cf0d1',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '6618',
|
'id': '6618',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'description': "Baldur's Gate: Original, Modded or Enhanced Edition? I'll break down what you can expect from the new Baldur's Gate: Enhanced Edition.",
|
'description': "Baldur's Gate: Original, Modded or Enhanced Edition? I'll break down what you can expect from the new Baldur's Gate: Enhanced Edition.",
|
||||||
'uploader_id': 'the-escapist-presents',
|
|
||||||
'uploader': 'The Escapist Presents',
|
|
||||||
'title': "Breaking Down Baldur's Gate",
|
'title': "Breaking Down Baldur's Gate",
|
||||||
'thumbnail': 're:^https?://.*\.jpg$',
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
'duration': 264,
|
'duration': 264,
|
||||||
|
'uploader': 'The Escapist',
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.escapistmagazine.com/videos/view/zero-punctuation/10044-Evolve-One-vs-Multiplayer',
|
||||||
|
'md5': '9e8c437b0dbb0387d3bd3255ca77f6bf',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '10044',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'description': 'This week, Zero Punctuation reviews Evolve.',
|
||||||
|
'title': 'Evolve - One vs Multiplayer',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
'duration': 304,
|
||||||
|
'uploader': 'The Escapist',
|
||||||
}
|
}
|
||||||
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
webpage_req = compat_urllib_request.Request(url)
|
webpage = self._download_webpage(url, video_id)
|
||||||
webpage_req.add_header('User-Agent', self._USER_AGENT)
|
|
||||||
webpage = self._download_webpage(webpage_req, video_id)
|
|
||||||
|
|
||||||
uploader_id = self._html_search_regex(
|
ims_video = self._parse_json(
|
||||||
r"<h1\s+class='headline'>\s*<a\s+href='/videos/view/(.*?)'",
|
self._search_regex(
|
||||||
webpage, 'uploader ID', fatal=False)
|
r'imsVideo\.play\(({.+?})\);', webpage, 'imsVideo'),
|
||||||
uploader = self._html_search_regex(
|
video_id)
|
||||||
r"<h1\s+class='headline'>(.*?)</a>",
|
video_id = ims_video['videoID']
|
||||||
webpage, 'uploader', fatal=False)
|
key = ims_video['hash']
|
||||||
description = self._html_search_meta('description', webpage)
|
|
||||||
duration = parse_duration(self._html_search_meta('duration', webpage))
|
|
||||||
|
|
||||||
raw_title = self._html_search_meta('title', webpage, fatal=True)
|
config_req = compat_urllib_request.Request(
|
||||||
title = raw_title.partition(' : ')[2]
|
'http://www.escapistmagazine.com/videos/'
|
||||||
|
'vidconfig.php?videoID=%s&hash=%s' % (video_id, key))
|
||||||
|
config_req.add_header('Referer', url)
|
||||||
|
config = self._download_webpage(config_req, video_id, 'Downloading video config')
|
||||||
|
|
||||||
config_url = compat_urllib_parse.unquote(self._html_search_regex(
|
data = json.loads(_decrypt_config(key, config))
|
||||||
r'''(?x)
|
|
||||||
(?:
|
|
||||||
<param\s+name="flashvars".*?\s+value="config=|
|
|
||||||
flashvars="config=
|
|
||||||
)
|
|
||||||
(https?://[^"&]+)
|
|
||||||
''',
|
|
||||||
webpage, 'config URL'))
|
|
||||||
|
|
||||||
formats = []
|
video_data = data['videoData']
|
||||||
ad_formats = []
|
|
||||||
|
|
||||||
def _add_format(name, cfg_url, quality):
|
title = clean_html(video_data['title'])
|
||||||
cfg_req = compat_urllib_request.Request(cfg_url)
|
duration = float_or_none(video_data.get('duration'), 1000)
|
||||||
cfg_req.add_header('User-Agent', self._USER_AGENT)
|
uploader = video_data.get('publisher')
|
||||||
config = self._download_json(
|
|
||||||
cfg_req, video_id,
|
|
||||||
'Downloading ' + name + ' configuration',
|
|
||||||
'Unable to download ' + name + ' configuration',
|
|
||||||
transform_source=js_to_json)
|
|
||||||
|
|
||||||
playlist = config['playlist']
|
formats = [{
|
||||||
for p in playlist:
|
'url': video['src'],
|
||||||
if p.get('eventCategory') == 'Video':
|
'format_id': '%s-%sp' % (determine_ext(video['src']), video['res']),
|
||||||
ar = formats
|
'height': int_or_none(video.get('res')),
|
||||||
elif p.get('eventCategory') == 'Video Postroll':
|
} for video in data['files']['videos']]
|
||||||
ar = ad_formats
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
|
|
||||||
ar.append({
|
|
||||||
'url': p['url'],
|
|
||||||
'format_id': name,
|
|
||||||
'quality': quality,
|
|
||||||
'http_headers': {
|
|
||||||
'User-Agent': self._USER_AGENT,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
_add_format('normal', config_url, quality=0)
|
|
||||||
hq_url = (config_url +
|
|
||||||
('&hq=1' if '?' in config_url else config_url + '?hq=1'))
|
|
||||||
try:
|
|
||||||
_add_format('hq', hq_url, quality=1)
|
|
||||||
except ExtractorError:
|
|
||||||
pass # That's fine, we'll just use normal quality
|
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
if '/escapist/sales-marketing/' in formats[-1]['url']:
|
return {
|
||||||
raise ExtractorError('This IP address has been blocked by The Escapist', expected=True)
|
|
||||||
|
|
||||||
res = {
|
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
'uploader': uploader,
|
|
||||||
'uploader_id': uploader_id,
|
|
||||||
'title': title,
|
'title': title,
|
||||||
'thumbnail': self._og_search_thumbnail(webpage),
|
'thumbnail': self._og_search_thumbnail(webpage),
|
||||||
'description': description,
|
'description': self._og_search_description(webpage),
|
||||||
'duration': duration,
|
'duration': duration,
|
||||||
|
'uploader': uploader,
|
||||||
}
|
}
|
||||||
|
|
||||||
if self._downloader.params.get('include_ads') and ad_formats:
|
|
||||||
self._sort_formats(ad_formats)
|
|
||||||
ad_res = {
|
|
||||||
'id': '%s-ad' % video_id,
|
|
||||||
'title': '%s (Postroll)' % title,
|
|
||||||
'formats': ad_formats,
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
'_type': 'playlist',
|
|
||||||
'entries': [res, ad_res],
|
|
||||||
'title': title,
|
|
||||||
'id': video_id,
|
|
||||||
}
|
|
||||||
|
|
||||||
return res
|
|
||||||
|
|||||||
55
youtube_dl/extractor/espn.py
Normal file
55
youtube_dl/extractor/espn.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
|
class ESPNIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://espn\.go\.com/(?:[^/]+/)*(?P<id>[^/]+)'
|
||||||
|
_WORKING = False
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://espn.go.com/video/clip?id=10365079',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'FkYWtmazr6Ed8xmvILvKLWjd4QvYZpzG',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'dm_140128_30for30Shorts___JudgingJewellv2',
|
||||||
|
'description': '',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# m3u8 download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'https://espn.go.com/video/iframe/twitter/?cms=espn&id=10365079',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://espn.go.com/nba/recap?gameId=400793786',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://espn.go.com/blog/golden-state-warriors/post/_/id/593/how-warriors-rapidly-regained-a-winning-edge',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://espn.go.com/sports/endurance/story/_/id/12893522/dzhokhar-tsarnaev-sentenced-role-boston-marathon-bombings',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://espn.go.com/nba/playoffs/2015/story/_/id/12887571/john-wall-washington-wizards-no-swelling-left-hand-wrist-game-5-return',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
video_id = self._search_regex(
|
||||||
|
r'class="video-play-button"[^>]+data-id="(\d+)',
|
||||||
|
webpage, 'video id')
|
||||||
|
|
||||||
|
player = self._download_webpage(
|
||||||
|
'https://espn.go.com/video/iframe/twitter/?id=%s' % video_id, video_id)
|
||||||
|
|
||||||
|
pcode = self._search_regex(
|
||||||
|
r'["\']pcode=([^"\']+)["\']', player, 'pcode')
|
||||||
|
|
||||||
|
return self.url_result(
|
||||||
|
'ooyalaexternal:espn:%s:%s' % (video_id, pcode),
|
||||||
|
'OoyalaExternal')
|
||||||
@@ -4,11 +4,11 @@ import re
|
|||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import (
|
||||||
compat_urllib_parse_urlparse,
|
compat_parse_qs,
|
||||||
compat_urllib_request,
|
compat_urllib_request,
|
||||||
compat_urllib_parse,
|
|
||||||
)
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
|
qualities,
|
||||||
str_to_int,
|
str_to_int,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ class ExtremeTubeIE(InfoExtractor):
|
|||||||
_VALID_URL = r'https?://(?:www\.)?(?P<url>extremetube\.com/.*?video/.+?(?P<id>[0-9]+))(?:[/?&]|$)'
|
_VALID_URL = r'https?://(?:www\.)?(?P<url>extremetube\.com/.*?video/.+?(?P<id>[0-9]+))(?:[/?&]|$)'
|
||||||
_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': '1fb9228f5e3332ec8c057d6ac36f33e0',
|
'md5': '344d0c6d50e2f16b06e49ca011d8ac69',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '652431',
|
'id': '652431',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
@@ -49,19 +49,27 @@ 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))
|
||||||
|
|
||||||
video_url = compat_urllib_parse.unquote(self._html_search_regex(
|
flash_vars = compat_parse_qs(self._search_regex(
|
||||||
r'video_url=(.+?)&', webpage, 'video_url'))
|
r'<param[^>]+?name="flashvars"[^>]+?value="([^"]+)"', webpage, 'flash vars'))
|
||||||
path = compat_urllib_parse_urlparse(video_url).path
|
|
||||||
format = path.split('/')[5].split('_')[:2]
|
formats = []
|
||||||
format = "-".join(format)
|
quality = qualities(['180p', '240p', '360p', '480p', '720p', '1080p'])
|
||||||
|
for k, vals in flash_vars.items():
|
||||||
|
m = re.match(r'quality_(?P<quality>[0-9]+p)$', k)
|
||||||
|
if m is not None:
|
||||||
|
formats.append({
|
||||||
|
'format_id': m.group('quality'),
|
||||||
|
'quality': quality(m.group('quality')),
|
||||||
|
'url': vals[0],
|
||||||
|
})
|
||||||
|
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': video_title,
|
'title': video_title,
|
||||||
|
'formats': formats,
|
||||||
'uploader': uploader,
|
'uploader': uploader,
|
||||||
'view_count': view_count,
|
'view_count': view_count,
|
||||||
'url': video_url,
|
|
||||||
'format': format,
|
|
||||||
'format_id': format,
|
|
||||||
'age_limit': 18,
|
'age_limit': 18,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from ..compat import (
|
|||||||
compat_http_client,
|
compat_http_client,
|
||||||
compat_str,
|
compat_str,
|
||||||
compat_urllib_error,
|
compat_urllib_error,
|
||||||
compat_urllib_parse,
|
compat_urllib_parse_unquote,
|
||||||
compat_urllib_request,
|
compat_urllib_request,
|
||||||
)
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
@@ -24,8 +24,12 @@ class FacebookIE(InfoExtractor):
|
|||||||
_VALID_URL = r'''(?x)
|
_VALID_URL = r'''(?x)
|
||||||
https?://(?:\w+\.)?facebook\.com/
|
https?://(?:\w+\.)?facebook\.com/
|
||||||
(?:[^#]*?\#!/)?
|
(?:[^#]*?\#!/)?
|
||||||
|
(?:
|
||||||
(?:video/video\.php|photo\.php|video\.php|video/embed)\?(?:.*?)
|
(?:video/video\.php|photo\.php|video\.php|video/embed)\?(?:.*?)
|
||||||
(?:v|video_id)=(?P<id>[0-9]+)
|
(?:v|video_id)=|
|
||||||
|
[^/]+/videos/(?:[^/]+/)?
|
||||||
|
)
|
||||||
|
(?P<id>[0-9]+)
|
||||||
(?:.*)'''
|
(?:.*)'''
|
||||||
_LOGIN_URL = 'https://www.facebook.com/login.php?next=http%3A%2F%2Ffacebook.com%2Fhome.php&login_attempt=1'
|
_LOGIN_URL = 'https://www.facebook.com/login.php?next=http%3A%2F%2Ffacebook.com%2Fhome.php&login_attempt=1'
|
||||||
_CHECKPOINT_URL = 'https://www.facebook.com/checkpoint/?next=http%3A%2F%2Ffacebook.com%2Fhome.php&_fb_noscript=1'
|
_CHECKPOINT_URL = 'https://www.facebook.com/checkpoint/?next=http%3A%2F%2Ffacebook.com%2Fhome.php&_fb_noscript=1'
|
||||||
@@ -46,10 +50,19 @@ class FacebookIE(InfoExtractor):
|
|||||||
'id': '274175099429670',
|
'id': '274175099429670',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Facebook video #274175099429670',
|
'title': 'Facebook video #274175099429670',
|
||||||
}
|
},
|
||||||
|
'expected_warnings': [
|
||||||
|
'title'
|
||||||
|
]
|
||||||
}, {
|
}, {
|
||||||
'url': 'https://www.facebook.com/video.php?v=10204634152394104',
|
'url': 'https://www.facebook.com/video.php?v=10204634152394104',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.facebook.com/amogood/videos/1618742068337349/?fref=nf',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.facebook.com/ChristyClarkForBC/videos/vb.22819070941/10153870694020942/?type=2&theater',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _login(self):
|
def _login(self):
|
||||||
@@ -123,7 +136,7 @@ class FacebookIE(InfoExtractor):
|
|||||||
else:
|
else:
|
||||||
raise ExtractorError('Cannot parse data')
|
raise ExtractorError('Cannot parse data')
|
||||||
data = dict(json.loads(m.group(1)))
|
data = dict(json.loads(m.group(1)))
|
||||||
params_raw = compat_urllib_parse.unquote(data['params'])
|
params_raw = compat_urllib_parse_unquote(data['params'])
|
||||||
params = json.loads(params_raw)
|
params = json.loads(params_raw)
|
||||||
video_data = params['video_data'][0]
|
video_data = params['video_data'][0]
|
||||||
|
|
||||||
@@ -139,12 +152,12 @@ class FacebookIE(InfoExtractor):
|
|||||||
raise ExtractorError('Cannot find video formats')
|
raise ExtractorError('Cannot find video formats')
|
||||||
|
|
||||||
video_title = self._html_search_regex(
|
video_title = self._html_search_regex(
|
||||||
r'<h2 class="uiHeaderTitle">([^<]*)</h2>', webpage, 'title',
|
r'<h2\s+[^>]*class="uiHeaderTitle"[^>]*>([^<]*)</h2>', webpage, 'title',
|
||||||
fatal=False)
|
default=None)
|
||||||
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', default=None)
|
webpage, 'alternative title', fatal=False)
|
||||||
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
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ from .common import InfoExtractor
|
|||||||
|
|
||||||
class FazIE(InfoExtractor):
|
class FazIE(InfoExtractor):
|
||||||
IE_NAME = 'faz.net'
|
IE_NAME = 'faz.net'
|
||||||
_VALID_URL = r'https?://www\.faz\.net/multimedia/videos/.*?-(?P<id>\d+)\.html'
|
_VALID_URL = r'https?://(?:www\.)?faz\.net/(?:[^/]+/)*.*?-(?P<id>\d+)\.html'
|
||||||
|
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
'url': 'http://www.faz.net/multimedia/videos/stockholm-chemie-nobelpreis-fuer-drei-amerikanische-forscher-12610585.html',
|
'url': 'http://www.faz.net/multimedia/videos/stockholm-chemie-nobelpreis-fuer-drei-amerikanische-forscher-12610585.html',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '12610585',
|
'id': '12610585',
|
||||||
@@ -16,7 +16,22 @@ class FazIE(InfoExtractor):
|
|||||||
'title': 'Stockholm: Chemie-Nobelpreis für drei amerikanische Forscher',
|
'title': 'Stockholm: Chemie-Nobelpreis für drei amerikanische Forscher',
|
||||||
'description': 'md5:1453fbf9a0d041d985a47306192ea253',
|
'description': 'md5:1453fbf9a0d041d985a47306192ea253',
|
||||||
},
|
},
|
||||||
}
|
}, {
|
||||||
|
'url': 'http://www.faz.net/aktuell/politik/berlin-gabriel-besteht-zerreissprobe-ueber-datenspeicherung-13659345.html',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.faz.net/berlin-gabriel-besteht-zerreissprobe-ueber-datenspeicherung-13659345.html',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.faz.net/-13659345.html',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.faz.net/aktuell/politik/-13659345.html',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.faz.net/foobarblafasel-13659345.html',
|
||||||
|
'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)
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
# coding: utf-8
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
|
||||||
from ..compat import (
|
|
||||||
compat_urllib_parse,
|
|
||||||
compat_urllib_request,
|
|
||||||
)
|
|
||||||
from ..utils import (
|
|
||||||
ExtractorError,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class FiredriveIE(InfoExtractor):
|
|
||||||
_VALID_URL = r'https?://(?:www\.)?firedrive\.com/' + \
|
|
||||||
'(?:file|embed)/(?P<id>[0-9a-zA-Z]+)'
|
|
||||||
_FILE_DELETED_REGEX = r'<div class="removed_file_image">'
|
|
||||||
|
|
||||||
_TESTS = [{
|
|
||||||
'url': 'https://www.firedrive.com/file/FEB892FA160EBD01',
|
|
||||||
'md5': 'd5d4252f80ebeab4dc2d5ceaed1b7970',
|
|
||||||
'info_dict': {
|
|
||||||
'id': 'FEB892FA160EBD01',
|
|
||||||
'ext': 'flv',
|
|
||||||
'title': 'bbb_theora_486kbit.flv',
|
|
||||||
'thumbnail': 're:^http://.*\.jpg$',
|
|
||||||
},
|
|
||||||
}]
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
|
||||||
video_id = self._match_id(url)
|
|
||||||
url = 'http://firedrive.com/file/%s' % video_id
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
|
||||||
|
|
||||||
if re.search(self._FILE_DELETED_REGEX, webpage) is not None:
|
|
||||||
raise ExtractorError('Video %s does not exist' % video_id,
|
|
||||||
expected=True)
|
|
||||||
|
|
||||||
fields = dict(re.findall(r'''(?x)<input\s+
|
|
||||||
type="hidden"\s+
|
|
||||||
name="([^"]+)"\s+
|
|
||||||
value="([^"]*)"
|
|
||||||
''', webpage))
|
|
||||||
|
|
||||||
post = compat_urllib_parse.urlencode(fields)
|
|
||||||
req = compat_urllib_request.Request(url, post)
|
|
||||||
req.add_header('Content-type', 'application/x-www-form-urlencoded')
|
|
||||||
|
|
||||||
# Apparently, this header is required for confirmation to work.
|
|
||||||
req.add_header('Host', 'www.firedrive.com')
|
|
||||||
|
|
||||||
webpage = self._download_webpage(req, video_id,
|
|
||||||
'Downloading video page')
|
|
||||||
|
|
||||||
title = self._search_regex(r'class="external_title_left">(.+)</div>',
|
|
||||||
webpage, 'title')
|
|
||||||
thumbnail = self._search_regex(r'image:\s?"(//[^\"]+)', webpage,
|
|
||||||
'thumbnail', fatal=False)
|
|
||||||
if thumbnail is not None:
|
|
||||||
thumbnail = 'http:' + thumbnail
|
|
||||||
|
|
||||||
ext = self._search_regex(r'type:\s?\'([^\']+)\',',
|
|
||||||
webpage, 'extension', fatal=False)
|
|
||||||
video_url = self._search_regex(
|
|
||||||
r'file:\s?loadURL\(\'(http[^\']+)\'\),', webpage, 'file url')
|
|
||||||
|
|
||||||
formats = [{
|
|
||||||
'format_id': 'sd',
|
|
||||||
'url': video_url,
|
|
||||||
'ext': ext,
|
|
||||||
}]
|
|
||||||
|
|
||||||
return {
|
|
||||||
'id': video_id,
|
|
||||||
'title': title,
|
|
||||||
'thumbnail': thumbnail,
|
|
||||||
'formats': formats,
|
|
||||||
}
|
|
||||||
88
youtube_dl/extractor/fivetv.py
Normal file
88
youtube_dl/extractor/fivetv.py
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import int_or_none
|
||||||
|
|
||||||
|
|
||||||
|
class FiveTVIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'''(?x)
|
||||||
|
http://
|
||||||
|
(?:www\.)?5-tv\.ru/
|
||||||
|
(?:
|
||||||
|
(?:[^/]+/)+(?P<id>\d+)|
|
||||||
|
(?P<path>[^/?#]+)(?:[/?#])?
|
||||||
|
)
|
||||||
|
'''
|
||||||
|
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://5-tv.ru/news/96814/',
|
||||||
|
'md5': 'bbff554ad415ecf5416a2f48c22d9283',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '96814',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Россияне выбрали имя для общенациональной платежной системы',
|
||||||
|
'description': 'md5:a8aa13e2b7ad36789e9f77a74b6de660',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
'duration': 180,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://5-tv.ru/video/1021729/',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '1021729',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': '3D принтер',
|
||||||
|
'description': 'md5:d76c736d29ef7ec5c0cf7d7c65ffcb41',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
'duration': 180,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.5-tv.ru/glavnoe/#itemDetails',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'glavnoe',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Итоги недели с 8 по 14 июня 2015 года',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.5-tv.ru/glavnoe/broadcasts/508645/',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://5-tv.ru/films/1507502/',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://5-tv.ru/programs/broadcast/508713/',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://5-tv.ru/angel/',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.5-tv.ru/schedule/?iframe=true&width=900&height=450',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id') or mobj.group('path')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
video_url = self._search_regex(
|
||||||
|
r'<a[^>]+?href="([^"]+)"[^>]+?class="videoplayer"',
|
||||||
|
webpage, 'video url')
|
||||||
|
|
||||||
|
title = self._og_search_title(webpage, default=None) or self._search_regex(
|
||||||
|
r'<title>([^<]+)</title>', webpage, 'title')
|
||||||
|
duration = int_or_none(self._og_search_property(
|
||||||
|
'video:duration', webpage, 'duration', default=None))
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'url': video_url,
|
||||||
|
'title': title,
|
||||||
|
'description': self._og_search_description(webpage, default=None),
|
||||||
|
'thumbnail': self._og_search_thumbnail(webpage, default=None),
|
||||||
|
'duration': duration,
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user