Compare commits
	
		
			872 Commits
		
	
	
		
			2013.07.24
			...
			2013.11.17
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 73c566695f | ||
|   | 63b7b7224a | ||
|   | ce80c8b8ee | ||
|   | 749febf4d1 | ||
|   | bdde425cbe | ||
|   | 746f491f82 | ||
|   | 1672647ade | ||
|   | 90b6bbc38c | ||
|   | ce02ed60f2 | ||
|   | 1e5b9a95fd | ||
|   | 1d699755e0 | ||
|   | ddf49c6344 | ||
|   | d1c252048b | ||
|   | eab2724138 | ||
|   | 21ea3e06c9 | ||
|   | 52d703d3d1 | ||
|   | ce152341a1 | ||
|   | f058e34011 | ||
|   | b5349e8721 | ||
|   | 7150858d49 | ||
|   | 91c7271aab | ||
|   | aa13b2dffd | ||
|   | fc2ef392be | ||
|   | 463a908705 | ||
|   | d24ffe1cfa | ||
|   | 78fb87b283 | ||
|   | ab2d524780 | ||
|   | 85d61685f1 | ||
|   | b9643eed7c | ||
|   | feee2ecfa9 | ||
|   | a25a5cfeec | ||
|   | 0e145dd541 | ||
|   | 9f9be844fc | ||
|   | e3b9ab5e18 | ||
|   | c66d2baa9c | ||
|   | ca715127a2 | ||
|   | ea7a7af1d4 | ||
|   | 80b9bbce86 | ||
|   | d37936386f | ||
|   | c3a3028f9f | ||
|   | 6c5ad80cdc | ||
|   | b5bdc2699a | ||
|   | 384b98cd8f | ||
|   | eb9b5bffef | ||
|   | 8b8cbd8f6d | ||
|   | 72b18c5d34 | ||
|   | eb0a839866 | ||
|   | 1777d5a952 | ||
|   | d4b7da84c3 | ||
|   | 801dbbdffd | ||
|   | 0ed05a1d2d | ||
|   | 1008bebade | ||
|   | ae84f879d7 | ||
|   | be6dfd1b49 | ||
|   | 231516b6c9 | ||
|   | fb53d58dcf | ||
|   | 2a9e9b210b | ||
|   | 897d6cc43a | ||
|   | f470c6c812 | ||
|   | 566d4e0425 | ||
|   | 81be02d2f9 | ||
|   | c2b6a482d5 | ||
|   | 12c167c881 | ||
|   | 20aafee7fa | ||
|   | be07375b66 | ||
|   | dd5bcdc4c9 | ||
|   | 6161d17579 | ||
|   | 4ac5306ae7 | ||
|   | b1a80ec1a9 | ||
|   | 672fe94dcb | ||
|   | 51040b72ed | ||
|   | 4f045eef8f | ||
|   | 5d7b253ea0 | ||
|   | b0759f0c19 | ||
|   | 065472936a | ||
|   | fc4a0c2aec | ||
|   | eeb165e674 | ||
|   | 9ee2b5f6f2 | ||
|   | da54be877a | ||
|   | 50a886b7ab | ||
|   | 76e67c2cb6 | ||
|   | 5137ebac0b | ||
|   | a8eeb0597b | ||
|   | 4ed3e51080 | ||
|   | 7f34001d57 | ||
|   | 2dcf7d8f99 | ||
|   | 19b0668251 | ||
|   | e7e6b54d8a | ||
|   | 2a1a8ffe41 | ||
|   | 08fb86c49b | ||
|   | 3633d77c0f | ||
|   | 165e179764 | ||
|   | 12ebdd1506 | ||
|   | 1baf9a5938 | ||
|   | a56f9de156 | ||
|   | fa5d47af4b | ||
|   | d607038753 | ||
|   | 9ac6a01aaf | ||
|   | be97abc247 | ||
|   | 9103bbc5cd | ||
|   | b6c45014ae | ||
|   | a3dd924871 | ||
|   | 137bbb3e37 | ||
|   | 86ad94bb2e | ||
|   | 3e56add7c9 | ||
|   | f52f01b5d2 | ||
|   | 98d7efb537 | ||
|   | cf51923545 | ||
|   | 38fcd4597a | ||
|   | 165e3bb67a | ||
|   | 38db46794f | ||
|   | a9a3876d55 | ||
|   | 1f343eaabb | ||
|   | 72a5b4f702 | ||
|   | 0a43ddf320 | ||
|   | 31366066bd | ||
|   | aa2484e390 | ||
|   | 8eddf3e91d | ||
|   | 60d142aa8d | ||
|   | 66cf3ac342 | ||
|   | ab4e151347 | ||
|   | ac2547f5ff | ||
|   | 5f1ea943ab | ||
|   | 0ef7ad5cd4 | ||
|   | 9f1109a564 | ||
|   | 33b1d9595d | ||
|   | 7193498811 | ||
|   | 72321ead7b | ||
|   | b5d0d817bc | ||
|   | 94badb2599 | ||
|   | b9a836515f | ||
|   | 21c924f406 | ||
|   | e54fd4b23b | ||
|   | 57dd9a8f2f | ||
|   | 912cbf5d4e | ||
|   | 43d7895ea0 | ||
|   | f7ff55aa78 | ||
|   | 795f28f871 | ||
|   | f6cc16f5d8 | ||
|   | 321a01f971 | ||
|   | 646e17a53d | ||
|   | dd508b7c4f | ||
|   | 2563bcc85c | ||
|   | 702665c085 | ||
|   | dcc2a706ef | ||
|   | 2bc67c35ac | ||
|   | 77ae65877e | ||
|   | 32a35e4418 | ||
|   | 369a759acc | ||
|   | 79b3f61228 | ||
|   | 216d71d001 | ||
|   | 78a3a9f89e | ||
|   | a7685f3bf4 | ||
|   | f088ea5486 | ||
|   | 1003d108d5 | ||
|   | 8abeeb9449 | ||
|   | c1002e96e9 | ||
|   | 77d0a82fef | ||
|   | ebc14f251c | ||
|   | d41e6efc85 | ||
|   | 8ffa13e03e | ||
|   | db477d3a37 | ||
|   | 750e9833b8 | ||
|   | 82f0ac657c | ||
|   | eb6a2277a2 | ||
|   | f8778fb0fa | ||
|   | e2f9de207c | ||
|   | a93cc0d943 | ||
|   | 7d8c2e07f2 | ||
|   | efb4c36b18 | ||
|   | 29526d0d2b | ||
|   | 198e370f23 | ||
|   | c19f7764a5 | ||
|   | bc63d9d329 | ||
|   | aa929c37d5 | ||
|   | af4d506eb3 | ||
|   | 5da0549581 | ||
|   | 749a4fd2fd | ||
|   | 6f71ef580c | ||
|   | 67874aeffa | ||
|   | 3e6a330d38 | ||
|   | aee5e18c8f | ||
|   | 5b11143d05 | ||
|   | 7b2212e954 | ||
|   | 71865091ab | ||
|   | 125cfd78e8 | ||
|   | 8cb57d9b91 | ||
|   | 14e10b2b6e | ||
|   | 6e76104d66 | ||
|   | 1d45a23b74 | ||
|   | 7df286540f | ||
|   | 5d0c97541a | ||
|   | 49a25557b0 | ||
|   | b5936c0059 | ||
|   | 600cc1a4f0 | ||
|   | ea32fbacc8 | ||
|   | 00fe14fc75 | ||
|   | fcc28edb2f | ||
|   | fac6be2dd5 | ||
|   | 1cf64ee468 | ||
|   | cdec0190c4 | ||
|   | 2450bcb28b | ||
|   | 3126050c0f | ||
|   | 93b22c7828 | ||
|   | 0a89b2852e | ||
|   | 55b3e45bba | ||
|   | 365bcf6d97 | ||
|   | 71907db3ba | ||
|   | 6803655ced | ||
|   | df1c39ec5c | ||
|   | 80f55a9511 | ||
|   | 7853cc5ae1 | ||
|   | 586a91b67f | ||
|   | b028e96144 | ||
|   | ce68b5907c | ||
|   | fe7e0c9825 | ||
|   | 12893efe01 | ||
|   | a6387bfd3c | ||
|   | f6a54188c2 | ||
|   | cbbd9a9c69 | ||
|   | 685a9cd2f1 | ||
|   | 182a107877 | ||
|   | 8c51aa6506 | ||
|   | 3fd39e37f2 | ||
|   | 49e86983e7 | ||
|   | a9c58ad945 | ||
|   | f8b45beacc | ||
|   | 9d92015d43 | ||
|   | 50a6150ed9 | ||
|   | b0505eb611 | ||
|   | 284acd57d6 | ||
|   | 8ed6b34477 | ||
|   | f6f1fc9286 | ||
|   | 8e590a117f | ||
|   | d5594202aa | ||
|   | b186d949cf | ||
|   | 3d2986063c | ||
|   | 41fd7c7e60 | ||
|   | fdefe96bf2 | ||
|   | 16f36a6fc9 | ||
|   | f44415360e | ||
|   | cce722b79c | ||
|   | 82697fb2ab | ||
|   | 53c1d3ef49 | ||
|   | 8e55e9abfc | ||
|   | 7c58ef3275 | ||
|   | 416a5efce7 | ||
|   | f4d96df0f1 | ||
|   | 5d254f776a | ||
|   | 1c1218fefc | ||
|   | d21ab29200 | ||
|   | 54ed626cf8 | ||
|   | a733eb6c53 | ||
|   | 591454798d | ||
|   | 38604f1a4f | ||
|   | 2d0efe70a6 | ||
|   | bfd14b1b2f | ||
|   | 76965512da | ||
|   | 996d1c3242 | ||
|   | 8abbf43f21 | ||
|   | 10eaae48ff | ||
|   | 9d4660cab1 | ||
|   | 9d74e308f7 | ||
|   | e772692ffd | ||
|   | 8381a92120 | ||
|   | cd054fc491 | ||
|   | f219743e33 | ||
|   | 4f41664de8 | ||
|   | a4fd04158e | ||
|   | 44a5f1718a | ||
|   | a623df4c7b | ||
|   | 7cf67fbe29 | ||
|   | 3ddf1a6d01 | ||
|   | 850555c484 | ||
|   | 9ed3bdc64d | ||
|   | c45aa56080 | ||
|   | 7394b8db3b | ||
|   | f9b3d7af47 | ||
|   | ea62a2da46 | ||
|   | 7468b6b71d | ||
|   | 1fb07d10a3 | ||
|   | 9378ae6e1d | ||
|   | 06723d47c4 | ||
|   | 69a0c470b5 | ||
|   | c40f5cf45c | ||
|   | 4b7b839f24 | ||
|   | 3d60d33773 | ||
|   | d7e66d39a0 | ||
|   | d3f46b9aa5 | ||
|   | f5e54a1fda | ||
|   | 4eb7f1d12e | ||
|   | 0f6d12e43c | ||
|   | b4cdc245cf | ||
|   | 3283533149 | ||
|   | 8032e31f2d | ||
|   | d2f9cdb205 | ||
|   | 8016c92297 | ||
|   | e028d0d1e3 | ||
|   | 79819f58f2 | ||
|   | 6ff000b888 | ||
|   | 99e206d508 | ||
|   | dd82ffea0c | ||
|   | 3823342d9d | ||
|   | 91dbaef406 | ||
|   | 9026dd3858 | ||
|   | 81d7f1928c | ||
|   | bc4f29170f | ||
|   | cb354c8f62 | ||
|   | 1cbb27b151 | ||
|   | 0ab4ff6378 | ||
|   | 63da13e829 | ||
|   | 4193a453c2 | ||
|   | 2e1fa03bf5 | ||
|   | 8f1ae18a18 | ||
|   | 57da92b7df | ||
|   | df4f632dbc | ||
|   | a34c2faae4 | ||
|   | 1d368c7589 | ||
|   | 88bd97e34c | ||
|   | 2ae3edb1cf | ||
|   | b2ad967e45 | ||
|   | a27b9e8bd5 | ||
|   | 4481a754e4 | ||
|   | faa6ef6bc8 | ||
|   | 15870e90b0 | ||
|   | 8e4f824365 | ||
|   | 387ae5f30b | ||
|   | ad7a071ab6 | ||
|   | 1310bf2474 | ||
|   | b24f347190 | ||
|   | ee6c9f95e1 | ||
|   | 2a69c6b879 | ||
|   | cfadd183c4 | ||
|   | e484c81f0c | ||
|   | 7e5e8306fd | ||
|   | 41e8bca4d0 | ||
|   | 8dbe9899a9 | ||
|   | f4aac741d5 | ||
|   | c1c9a79c49 | ||
|   | 226113c880 | ||
|   | 8932a66e49 | ||
|   | 79cfb46d42 | ||
|   | 00fcc17aee | ||
|   | e94b783c74 | ||
|   | 97dae9ae07 | ||
|   | ca215e0a4f | ||
|   | 91a26ca559 | ||
|   | 1ece880d7c | ||
|   | 400afddaf4 | ||
|   | c3fef636b5 | ||
|   | 46e28a84ca | ||
|   | 17ad2b3fb1 | ||
|   | 5e2a60db4a | ||
|   | cd214418f6 | ||
|   | ba2d9f213e | ||
|   | 7f8ae73a5d | ||
|   | 466880f531 | ||
|   | 9f1f6d2437 | ||
|   | 9e0f897f6b | ||
|   | c0f6aa876f | ||
|   | d93bdee9a6 | ||
|   | f13d09332d | ||
|   | 2f5865cc6d | ||
|   | deefc05b88 | ||
|   | 0d8cb1cc14 | ||
|   | a90b9fd209 | ||
|   | 829493439a | ||
|   | 73b4fafd82 | ||
|   | b039775057 | ||
|   | 5c1d63b737 | ||
|   | 3cd022f6e6 | ||
|   | abefd1f7c4 | ||
|   | c21315f273 | ||
|   | 9ab1018b1a | ||
|   | da0a5d2d6e | ||
|   | ee6adb166c | ||
|   | be8fe32c92 | ||
|   | c38b1e776d | ||
|   | 4f8bf17f23 | ||
|   | ca40186c75 | ||
|   | a8c6b24155 | ||
|   | bd8e5c7ca2 | ||
|   | 7c61bd36bb | ||
|   | c54283824c | ||
|   | 52f15da2ca | ||
|   | 44d466559e | ||
|   | 05751eb047 | ||
|   | f10503db67 | ||
|   | adfeafe9e1 | ||
|   | 4c62a16f4f | ||
|   | c0de39e6d4 | ||
|   | fa55675593 | ||
|   | d4d9920a26 | ||
|   | 47192f92d8 | ||
|   | 722076a123 | ||
|   | bb4aa62cf7 | ||
|   | 843530568f | ||
|   | 138a5454b5 | ||
|   | d279037036 | ||
|   | 46353f6783 | ||
|   | 70922df8b5 | ||
|   | 9c15e9de84 | ||
|   | 123c10608d | ||
|   | 0b7c2485b6 | ||
|   | 9abb32045a | ||
|   | f490e77e77 | ||
|   | 2dc592991a | ||
|   | 0a60edcfa9 | ||
|   | c53f9d30c8 | ||
|   | 509f398292 | ||
|   | 74bab3f0a4 | ||
|   | 8574862991 | ||
|   | 2de957c7e1 | ||
|   | 920de7a27d | ||
|   | 63efc427cd | ||
|   | ce65fb6c76 | ||
|   | 4de1994b6e | ||
|   | 592882aa9f | ||
|   | b98d6a1e19 | ||
|   | 29c7a63df8 | ||
|   | 8b25323ae2 | ||
|   | f426de8460 | ||
|   | 695dc094ab | ||
|   | e80d861064 | ||
|   | 2cdeb20135 | ||
|   | 7f74773254 | ||
|   | f2c327fd39 | ||
|   | e35e4ddc9a | ||
|   | c3c88a2664 | ||
|   | bb0eee71e7 | ||
|   | 6f56389b88 | ||
|   | 5b333c1ce6 | ||
|   | a825f33030 | ||
|   | 92f618f2e2 | ||
|   | 81ec7c7901 | ||
|   | dd5d2eb03c | ||
|   | 4ae720042c | ||
|   | c705320f48 | ||
|   | d2d8f89531 | ||
|   | bdde940e90 | ||
|   | 45f4a76dbc | ||
|   | 13dc64ce74 | ||
|   | c35f9e72ce | ||
|   | f8061589e6 | ||
|   | 0ca96d48c7 | ||
|   | 4ba146f35d | ||
|   | edf3e38ebd | ||
|   | c4417ddb61 | ||
|   | 4a2080e407 | ||
|   | 2f2ffea9ca | ||
|   | ba552f542f | ||
|   | 8379969834 | ||
|   | 95dbd2f990 | ||
|   | a7177865b1 | ||
|   | e0df6211cc | ||
|   | b00ca882a4 | ||
|   | 39baacc49f | ||
|   | 3a1d48d6de | ||
|   | 34308b30d6 | ||
|   | bc1506f8c0 | ||
|   | b61067fa4f | ||
|   | 69b227a9bc | ||
|   | 0fd49457f5 | ||
|   | 58f289d013 | ||
|   | 3d60bb96e1 | ||
|   | 38d025b3f0 | ||
|   | c40c6aaaaa | ||
|   | 1a810f0d4e | ||
|   | 63037593c0 | ||
|   | 7a878d47fa | ||
|   | bc4b900898 | ||
|   | c5e743f66f | ||
|   | 6c36d8d6fb | ||
|   | 71c82637e7 | ||
|   | 2dad310e2c | ||
|   | d0ae9e3a8d | ||
|   | a19413c311 | ||
|   | 1ef80b55dd | ||
|   | eb03f4dad3 | ||
|   | 830dd1944a | ||
|   | cc6943e86a | ||
|   | 1237c9a3a5 | ||
|   | 8f77093262 | ||
|   | 5d13df79a5 | ||
|   | d79a0e233a | ||
|   | 6523223a4c | ||
|   | 4a67aafb7e | ||
|   | f3f34c5b0f | ||
|   | 6ae8ee3f54 | ||
|   | e8f8e80097 | ||
|   | 4dc0ff3ecf | ||
|   | 4b6462fc1e | ||
|   | c4ece78564 | ||
|   | 0761d02b0b | ||
|   | 71c107fc57 | ||
|   | 7459e3a290 | ||
|   | f9e66fb993 | ||
|   | 6c603ccce3 | ||
|   | ef66b0c6ef | ||
|   | 22b50ecb2f | ||
|   | 5a6fecc3de | ||
|   | cdbccafed9 | ||
|   | e69ae5b9e7 | ||
|   | 92790f4e54 | ||
|   | 471a5ee908 | ||
|   | 19e1d35989 | ||
|   | 0b7f31184d | ||
|   | fad84d50fe | ||
|   | 9a1c32dc54 | ||
|   | a921f40799 | ||
|   | 74ac9bdd82 | ||
|   | 94518f2087 | ||
|   | 535f59bbcf | ||
|   | 71cedb3c0c | ||
|   | dd01d6558a | ||
|   | ce85f022d2 | ||
|   | ad94a6fe44 | ||
|   | 353ba14060 | ||
|   | 83de794223 | ||
|   | bfd5c93af9 | ||
|   | c247d87ef3 | ||
|   | 07ac9e2cc2 | ||
|   | 6bc520c207 | ||
|   | f1d20fa39f | ||
|   | e3dc22ca3a | ||
|   | d665f8d3cb | ||
|   | 055e6f3657 | ||
|   | ac4f319ba1 | ||
|   | 542cca0e8c | ||
|   | 6a2449df3b | ||
|   | 7fad1c6328 | ||
|   | d82134c339 | ||
|   | 54d39d8b2f | ||
|   | de7f3446e0 | ||
|   | f8e52269c1 | ||
|   | cf1dd0c59e | ||
|   | 22c8b52545 | ||
|   | 1f7dc42cd0 | ||
|   | aa8f2641da | ||
|   | 648d25d43d | ||
|   | df3e61003a | ||
|   | 6b361ad5ee | ||
|   | 5d8afe69f7 | ||
|   | a1ab553858 | ||
|   | 07463ea162 | ||
|   | 6d2d21f713 | ||
|   | 061b2889a9 | ||
|   | 8963d9c266 | ||
|   | 890f62e868 | ||
|   | 8f362589a5 | ||
|   | a27a2470cd | ||
|   | 72836fcee4 | ||
|   | a7130543fa | ||
|   | a490fda746 | ||
|   | 7e77275293 | ||
|   | d6e203b3dc | ||
|   | e3ea479087 | ||
|   | faab1d3836 | ||
|   | 8851a574a3 | ||
|   | 59282080c8 | ||
|   | 98f3da4040 | ||
|   | 1d213233cd | ||
|   | fd9cf73836 | ||
|   | 0638ad9999 | ||
|   | 1eb527692a | ||
|   | 09bb17e108 | ||
|   | 1cf911bc82 | ||
|   | f4b052321b | ||
|   | a636203ea5 | ||
|   | c215217e39 | ||
|   | 08e291b54d | ||
|   | 6b95b065be | ||
|   | 9363169b67 | ||
|   | 085bea4513 | ||
|   | 150f20828b | ||
|   | 08523ee20a | ||
|   | 5d5171d26a | ||
|   | 96fb5605b2 | ||
|   | 7011de0bc2 | ||
|   | c3dd69eab4 | ||
|   | 025171c476 | ||
|   | c8dbccde30 | ||
|   | 4ff7a0f1f6 | ||
|   | 9c2ade40de | ||
|   | aa32314d09 | ||
|   | 52afe99665 | ||
|   | b0446d6a33 | ||
|   | 8e4e89f1c2 | ||
|   | 6c758d79de | ||
|   | 691008087b | ||
|   | 85f03346eb | ||
|   | bdc6b3fc64 | ||
|   | 847f582290 | ||
|   | 10f5c016ec | ||
|   | 2e756879f1 | ||
|   | c7a7750d3b | ||
|   | 9193c1eede | ||
|   | b3f0e53048 | ||
|   | 3243d0f7b6 | ||
|   | 23b00bc0e4 | ||
|   | 52e1eea18b | ||
|   | ee80d66727 | ||
|   | f1fb2d12b3 | ||
|   | deb2c73212 | ||
|   | 8928491074 | ||
|   | 545434670b | ||
|   | 54fda45bac | ||
|   | c7bf7366bc | ||
|   | b7052e5087 | ||
|   | 0d75ae2ce3 | ||
|   | b5ba7b9dcf | ||
|   | 483e0ddd4d | ||
|   | 2891932bf0 | ||
|   | 591078babf | ||
|   | 9868c781a1 | ||
|   | c257baff85 | ||
|   | 878e83c5a4 | ||
|   | 0012690aae | ||
|   | 6e74bc41ca | ||
|   | cba892fa1f | ||
|   | 550bfd4cbd | ||
|   | 920ef0779b | ||
|   | 48ea9cea77 | ||
|   | ccf4b799df | ||
|   | f143d86ad2 | ||
|   | 8ae97d76ee | ||
|   | f8b362739e | ||
|   | 6d69d03bac | ||
|   | 204da0d3e3 | ||
|   | c496ca96e7 | ||
|   | 67b22dd036 | ||
|   | ce6a696e4d | ||
|   | a5caba1eb0 | ||
|   | cd9c100963 | ||
|   | edde6c56ac | ||
|   | b7f89fe692 | ||
|   | ae3531adf9 | ||
|   | 8cf5ee7831 | ||
|   | aa3e950764 | ||
|   | 1301a0dd42 | ||
|   | af8bd6a82d | ||
|   | 6d38616e67 | ||
|   | 4f5f18acb9 | ||
|   | 3e223834d9 | ||
|   | a1bb0f8773 | ||
|   | 0e283428f7 | ||
|   | 2eabb80254 | ||
|   | 44586389e4 | ||
|   | 06a401c845 | ||
|   | 273f603efb | ||
|   | 1619e22f40 | ||
|   | 88a79ce6a6 | ||
|   | acebc9cd6b | ||
|   | 443c12a703 | ||
|   | 7f3c4f4f65 | ||
|   | 0bc56fa66a | ||
|   | 1a582dd49d | ||
|   | c5b921b597 | ||
|   | e86ea47c02 | ||
|   | aa5a63a5b5 | ||
|   | 2a7b4da9b2 | ||
|   | 069d098f84 | ||
|   | b3889f7023 | ||
|   | 65883c8dbd | ||
|   | 341ca8d74c | ||
|   | 99859d436c | ||
|   | 1b01e2b085 | ||
|   | 976fc7d137 | ||
|   | c3b7b29c23 | ||
|   | 627a91a9a8 | ||
|   | 6dc6302599 | ||
|   | 7a20e2e1f8 | ||
|   | 90648143c3 | ||
|   | 5c6658d4dd | ||
|   | 9585f890f8 | ||
|   | 0838239e8e | ||
|   | 36399e8576 | ||
|   | 9460db832c | ||
|   | d68730a56e | ||
|   | f2aeefe29c | ||
|   | 39c6f507df | ||
|   | d2d1eb5b0a | ||
|   | 8ae7be3ef4 | ||
|   | 306170518f | ||
|   | aa6a10c44a | ||
|   | 9af73dc4fc | ||
|   | fc483bb6af | ||
|   | 53b0f3e4e2 | ||
|   | 4353cf51a0 | ||
|   | ce34e9ce5e | ||
|   | d4051a8e05 | ||
|   | df3df7fb64 | ||
|   | 9e9c164052 | ||
|   | 066090dd3f | ||
|   | 614d9c19c1 | ||
|   | bd2dee6c67 | ||
|   | 74e6672beb | ||
|   | 02bcf0d389 | ||
|   | 18b4e04f1c | ||
|   | 10204dc898 | ||
|   | 1865ed31b9 | ||
|   | 3669cdba10 | ||
|   | 939fbd26ac | ||
|   | b4e60dac23 | ||
|   | e6ddb4e7af | ||
|   | 83390b83d9 | ||
|   | ff2424595a | ||
|   | adeb9c73d6 | ||
|   | cd0abcc0bb | ||
|   | 4a55479fa9 | ||
|   | f527115b5f | ||
|   | 75e1b46add | ||
|   | 05a2926c5c | ||
|   | 7070b83687 | ||
|   | 8d212e604a | ||
|   | 063fcc9676 | ||
|   | 8403612258 | ||
|   | 25b51c7816 | ||
|   | 9779b63bb6 | ||
|   | d81aef3adf | ||
|   | 5af7e056a7 | ||
|   | 45ed795cb0 | ||
|   | 683e98a8a4 | ||
|   | e0cfeb2ea7 | ||
|   | 75340ee383 | ||
|   | 668de34c6b | ||
|   | a91b954bb4 | ||
|   | a3f62b8255 | ||
|   | 37b6d5f684 | ||
|   | b7a6838407 | ||
|   | cde846b3d3 | ||
|   | 6c3e6e88d3 | ||
|   | 739674cd77 | ||
|   | 4b2d7cae11 | ||
|   | 7fea7156cb | ||
|   | 3093468977 | ||
|   | 79cb25776f | ||
|   | 87f78946a5 | ||
|   | 211fbc1328 | ||
|   | 836a086ce9 | ||
|   | 90d3989b99 | ||
|   | d741e55a42 | ||
|   | 17d3aaaf16 | ||
|   | ea55b2a4ca | ||
|   | 3f0537dd4a | ||
|   | 943f7f7a39 | ||
|   | 12e895fc5a | ||
|   | bda2c49d75 | ||
|   | 01b32990da | ||
|   | dbda1b5147 | ||
|   | ddf3bd328b | ||
|   | b9c37b92cf | ||
|   | 5a27ecdd2e | ||
|   | f9c3c90ca8 | ||
|   | 6daccbe317 | ||
|   | 71ea844c0e | ||
|   | 3a7256697e | ||
|   | d1ba998274 | ||
|   | 718ced8d8c | ||
|   | e1842025d0 | ||
|   | 2b9213cdc1 | ||
|   | e3a88568b0 | ||
|   | 0577177e3e | ||
|   | 298f833b16 | ||
|   | 97b3656c2e | ||
|   | f3bcebb1d2 | ||
|   | 0f399e6e5e | ||
|   | 5b075e27cb | ||
|   | 8a9d86a2a7 | ||
|   | d80a064eff | ||
|   | d468a09789 | ||
|   | 9f4ab73d7f | ||
|   | 02cf62e240 | ||
|   | d55de6eec2 | ||
|   | 69df680b97 | ||
|   | 447591e1ae | ||
|   | 33eb0ce4c4 | ||
|   | 505c28aac9 | ||
|   | 67fb0c5495 | ||
|   | 4efba05c56 | ||
|   | 8377574c9c | ||
|   | 0f90943e45 | ||
|   | 526e638c8a | ||
|   | 372297e713 | ||
|   | 356e067390 | ||
|   | e2f48f9643 | ||
|   | b513a251f8 | ||
|   | 953e32b2c1 | ||
|   | 5898e28272 | ||
|   | 67dfbc0cb9 | ||
|   | 36cb11f068 | ||
|   | 7a4c6cc92f | ||
|   | 7edcb8f39c | ||
|   | d5b00ee6e0 | ||
|   | 461cead4f7 | ||
|   | b5a6d40818 | ||
|   | 968b5e0112 | ||
|   | 39b782b390 | ||
|   | 577664c8e8 | ||
|   | bba12cec89 | ||
|   | 70c4c03cb8 | ||
|   | f5791ed136 | ||
|   | 4ec929dc9b | ||
|   | fbf189a6ee | ||
|   | 09825cb5c0 | ||
|   | ed27d35674 | ||
|   | fd5539eb41 | ||
|   | 04bca64bde | ||
|   | 03cc7c20c1 | ||
|   | 4075311d94 | ||
|   | 6624a2b07d | ||
|   | 6d3a7d03e1 | ||
|   | 95fdc7d69c | ||
|   | 86fe61c8f9 | ||
|   | 9bb6d2f21d | ||
|   | e3f4593e76 | ||
|   | 1d043b93cf | ||
|   | b15d4f624f | ||
|   | 4aa16a50f5 | ||
|   | bbcbf4d459 | ||
|   | 930ad9eecc | ||
|   | b072a9defd | ||
|   | 75952c6e3d | ||
|   | 05afc96b73 | ||
|   | fa80026915 | ||
|   | 2bc3de0f28 | ||
|   | 99c7bc94af | ||
|   | 152c8f349d | ||
|   | d75654c15e | ||
|   | 0725f584e1 | ||
|   | 8cda9241d1 | ||
|   | a3124ba49f | ||
|   | 579e2691fe | ||
|   | 63f05de10b | ||
|   | caeefc29eb | ||
|   | a3c736def2 | ||
|   | 58261235f0 | ||
|   | da70877a1b | ||
|   | 5c468ca8a8 | ||
|   | aedd6bb97d | ||
|   | 733d9cacb8 | ||
|   | 42f2805e48 | ||
|   | 0ffcb7c6fc | ||
|   | 27669bd11d | ||
|   | 6625f82940 | ||
|   | d0866f0bb4 | ||
|   | 09eeb75130 | ||
|   | 0a99956f71 | ||
|   | 12ef6aefa8 | ||
|   | e93aa81aa6 | ||
|   | 755eb0320e | ||
|   | 43ba5456b1 | ||
|   | 156d5ad6da | ||
|   | c626a3d9fa | ||
|   | b2e8bc1b20 | ||
|   | 771822ebb8 | ||
|   | eb6a41ba0f | ||
|   | 7d2392691c | ||
|   | c216c1894d | ||
|   | 3e1ad508eb | ||
|   | a052c1d785 | ||
|   | 16484d4923 | ||
|   | 32a09b4382 | ||
|   | b1ca5e3ffa | ||
|   | b9a1252c96 | ||
|   | 6804038d06 | ||
|   | 2f799533ae | ||
|   | 88ae5991cd | ||
|   | 5d51a883c2 | ||
|   | c4a91be726 | ||
|   | 0025da15cf | ||
|   | 56c7366547 | 
							
								
								
									
										10
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -17,4 +17,12 @@ youtube-dl.tar.gz | ||||
| .coverage | ||||
| cover/ | ||||
| updates_key.pem | ||||
| *.egg-info | ||||
| *.egg-info | ||||
| *.srt | ||||
| *.sbv | ||||
| *.vtt | ||||
| *.flv | ||||
| *.mp4 | ||||
| *.part | ||||
| test/testdata | ||||
| .tox | ||||
|   | ||||
							
								
								
									
										13
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								Makefile
									
									
									
									
									
								
							| @@ -13,13 +13,13 @@ PYTHON=/usr/bin/env python | ||||
|  | ||||
| # set SYSCONFDIR to /etc if PREFIX=/usr or PREFIX=/usr/local | ||||
| ifeq ($(PREFIX),/usr) | ||||
|     SYSCONFDIR=/etc | ||||
| 	SYSCONFDIR=/etc | ||||
| else | ||||
|     ifeq ($(PREFIX),/usr/local) | ||||
|         SYSCONFDIR=/etc | ||||
|     else | ||||
|         SYSCONFDIR=$(PREFIX)/etc | ||||
|     endif | ||||
| 	ifeq ($(PREFIX),/usr/local) | ||||
| 		SYSCONFDIR=/etc | ||||
| 	else | ||||
| 		SYSCONFDIR=$(PREFIX)/etc | ||||
| 	endif | ||||
| endif | ||||
|  | ||||
| install: youtube-dl youtube-dl.1 youtube-dl.bash-completion | ||||
| @@ -71,6 +71,7 @@ youtube-dl.tar.gz: youtube-dl README.md README.txt youtube-dl.1 youtube-dl.bash- | ||||
| 		--exclude '*~' \ | ||||
| 		--exclude '__pycache' \ | ||||
| 		--exclude '.git' \ | ||||
| 		--exclude 'testdata' \ | ||||
| 		-- \ | ||||
| 		bin devscripts test youtube_dl \ | ||||
| 		CHANGELOG LICENSE README.md README.txt \ | ||||
|   | ||||
							
								
								
									
										59
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										59
									
								
								README.md
									
									
									
									
									
								
							| @@ -19,7 +19,10 @@ which means you can modify it, redistribute it or use it however you like. | ||||
|     -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 | ||||
|     -i, --ignore-errors        continue on download errors, for example to 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 | ||||
|     --dump-user-agent          display the current browser identification | ||||
|     --user-agent UA            specify a custom user agent | ||||
|     --referer REF              specify a custom referer, use if the video access | ||||
| @@ -29,6 +32,11 @@ which means you can modify it, redistribute it or use it however you like. | ||||
|     --extractor-descriptions   Output descriptions of all supported extractors | ||||
|     --proxy URL                Use the specified HTTP/HTTPS proxy | ||||
|     --no-check-certificate     Suppress HTTPS certificate validation. | ||||
|     --cache-dir DIR            Location in the filesystem where youtube-dl can | ||||
|                                store downloaded information permanently. By | ||||
|                                default $XDG_CACHE_HOME/youtube-dl or ~/.cache | ||||
|                                /youtube-dl . | ||||
|     --no-cache-dir             Disable filesystem caching | ||||
|  | ||||
| ## Video Selection: | ||||
|     --playlist-start NUMBER    playlist video to start at (default is 1) | ||||
| @@ -45,11 +53,16 @@ which means you can modify it, redistribute it or use it however you like. | ||||
|     --date DATE                download only videos uploaded in this date | ||||
|     --datebefore DATE          download only videos uploaded before this date | ||||
|     --dateafter DATE           download only videos uploaded after this date | ||||
|     --no-playlist              download only the currently playing video | ||||
|     --age-limit YEARS          download only videos suitable for the given age | ||||
|     --download-archive FILE    Download only videos not present in the archive | ||||
|                                file. Record all downloaded videos in it. | ||||
|  | ||||
| ## Download Options: | ||||
|     -r, --rate-limit LIMIT     maximum download rate (e.g. 50k or 44.6m) | ||||
|     -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) | ||||
|     --buffer-size SIZE         size of download buffer (e.g. 1024 or 16k) | ||||
|     --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 | ||||
| @@ -65,7 +78,10 @@ which means you can modify it, redistribute it or use it however you like. | ||||
|                                %(uploader_id)s for the uploader nickname if | ||||
|                                different, %(autonumber)s to get an automatically | ||||
|                                incremented number, %(ext)s for the filename | ||||
|                                extension, %(upload_date)s for the upload date | ||||
|                                extension, %(format)s for the format description | ||||
|                                (like "22 - 1280x720" or "HD"),%(format_id)s for | ||||
|                                the unique id of the format (like Youtube's | ||||
|                                itags: "137"),%(upload_date)s for the upload date | ||||
|                                (YYYYMMDD), %(extractor)s for the provider | ||||
|                                (youtube, metacafe, etc), %(id)s for the video id | ||||
|                                , %(playlist)s for the playlist the video is in, | ||||
| @@ -76,12 +92,14 @@ which means you can modify it, redistribute it or use it however you like. | ||||
|                                ownloads/%(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 | ||||
|                                --autonumber option is given | ||||
|                                --auto-number option is given | ||||
|     --restrict-filenames       Restrict filenames to only ASCII characters, and | ||||
|                                avoid "&" and spaces in filenames | ||||
|     -a, --batch-file FILE      file containing URLs to download ('-' for stdin) | ||||
|     -w, --no-overwrites        do not overwrite files | ||||
|     -c, --continue             resume partially downloaded files | ||||
|     -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) | ||||
|     --cookies FILE             file to read cookies from and dump cookie jar in | ||||
| @@ -90,6 +108,7 @@ which means you can modify it, redistribute it or use it however you like. | ||||
|                                file modification time | ||||
|     --write-description        write video description to a .description file | ||||
|     --write-info-json          write video metadata to a .info.json file | ||||
|     --write-annotations        write video annotations to a .annotation file | ||||
|     --write-thumbnail          write thumbnail image to disk | ||||
|  | ||||
| ## Verbosity / Simulation Options: | ||||
| @@ -110,28 +129,31 @@ which means you can modify it, redistribute it or use it however you like. | ||||
|     -v, --verbose              print various debugging information | ||||
|     --dump-intermediate-pages  print downloaded pages to debug problems(very | ||||
|                                verbose) | ||||
|     --write-pages              Write downloaded pages to files in the current | ||||
|                                directory | ||||
|  | ||||
| ## Video Format Options: | ||||
|     -f, --format FORMAT        video format code, specifiy the order of | ||||
|                                preference using slashes: "-f 22/17/18" | ||||
|                                preference using slashes: "-f 22/17/18". "-f mp4" | ||||
|                                and "-f flv" are also supported | ||||
|     --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 (currently youtube | ||||
|                                only) | ||||
|     --write-sub                write subtitle file (currently youtube only) | ||||
|     --write-auto-sub           write automatic subtitle file (currently youtube | ||||
|                                only) | ||||
|     --only-sub                 [deprecated] alias of --skip-download | ||||
|  | ||||
| ## Subtitle Options: | ||||
|     --write-sub                write subtitle file | ||||
|     --write-auto-sub           write automatic subtitle file (youtube only) | ||||
|     --all-subs                 downloads all the available subtitles of the | ||||
|                                video (currently youtube only) | ||||
|                                video | ||||
|     --list-subs                lists all available subtitles for the video | ||||
|                                (currently youtube only) | ||||
|     --sub-format FORMAT        subtitle format [srt/sbv/vtt] (default=srt) | ||||
|                                (currently youtube only) | ||||
|     --sub-lang LANG            language of the subtitles to download (optional) | ||||
|                                use IETF language tags like 'en' | ||||
|     --sub-format FORMAT        subtitle format (default=srt) ([sbv/vtt] youtube | ||||
|                                only) | ||||
|     --sub-lang LANGS           languages of the subtitles to download (optional) | ||||
|                                separated by commas, use IETF language tags like | ||||
|                                'en,pt' | ||||
|  | ||||
| ## Authentication Options: | ||||
|     -u, --username USERNAME    account username | ||||
| @@ -153,6 +175,9 @@ which means you can modify it, redistribute it or use it however you like. | ||||
|                                processing; the video is erased by default | ||||
|     --no-post-overwrites       do not overwrite post-processed files; the post- | ||||
|                                processed files are overwritten by default | ||||
|     --embed-subs               embed subtitles in the video (only for mp4 | ||||
|                                videos) | ||||
|     --add-metadata             add metadata to the files | ||||
|  | ||||
| # CONFIGURATION | ||||
|  | ||||
|   | ||||
| @@ -1,14 +1,18 @@ | ||||
| __youtube-dl() | ||||
| __youtube_dl() | ||||
| { | ||||
|     local cur prev opts | ||||
|     COMPREPLY=() | ||||
|     cur="${COMP_WORDS[COMP_CWORD]}" | ||||
|     opts="{{flags}}" | ||||
|     keywords=":ytfavorites :ytrecommended :ytsubscriptions :ytwatchlater" | ||||
|  | ||||
|     if [[ ${cur} == * ]] ; then | ||||
|     if [[ ${cur} =~ : ]]; then | ||||
|         COMPREPLY=( $(compgen -W "${keywords}" -- ${cur}) ) | ||||
|         return 0 | ||||
|     elif [[ ${cur} == * ]] ; then | ||||
|         COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) | ||||
|         return 0 | ||||
|     fi | ||||
| } | ||||
|  | ||||
| complete -F __youtube-dl youtube-dl | ||||
| complete -F __youtube_dl youtube-dl | ||||
|   | ||||
							
								
								
									
										405
									
								
								devscripts/buildserver.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										405
									
								
								devscripts/buildserver.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,405 @@ | ||||
| #!/usr/bin/python3 | ||||
|  | ||||
| from http.server import HTTPServer, BaseHTTPRequestHandler | ||||
| from socketserver import ThreadingMixIn | ||||
| import argparse | ||||
| import ctypes | ||||
| import functools | ||||
| import sys | ||||
| import threading | ||||
| import traceback | ||||
| import os.path | ||||
|  | ||||
|  | ||||
| class BuildHTTPServer(ThreadingMixIn, HTTPServer): | ||||
|     allow_reuse_address = True | ||||
|  | ||||
|  | ||||
| advapi32 = ctypes.windll.advapi32 | ||||
|  | ||||
| SC_MANAGER_ALL_ACCESS = 0xf003f | ||||
| SC_MANAGER_CREATE_SERVICE = 0x02 | ||||
| SERVICE_WIN32_OWN_PROCESS = 0x10 | ||||
| SERVICE_AUTO_START = 0x2 | ||||
| SERVICE_ERROR_NORMAL = 0x1 | ||||
| DELETE = 0x00010000 | ||||
| SERVICE_STATUS_START_PENDING = 0x00000002 | ||||
| SERVICE_STATUS_RUNNING = 0x00000004 | ||||
| SERVICE_ACCEPT_STOP = 0x1 | ||||
|  | ||||
| SVCNAME = 'youtubedl_builder' | ||||
|  | ||||
| LPTSTR = ctypes.c_wchar_p | ||||
| START_CALLBACK = ctypes.WINFUNCTYPE(None, ctypes.c_int, ctypes.POINTER(LPTSTR)) | ||||
|  | ||||
|  | ||||
| class SERVICE_TABLE_ENTRY(ctypes.Structure): | ||||
|     _fields_ = [ | ||||
|         ('lpServiceName', LPTSTR), | ||||
|         ('lpServiceProc', START_CALLBACK) | ||||
|     ] | ||||
|  | ||||
|  | ||||
| HandlerEx = ctypes.WINFUNCTYPE( | ||||
|     ctypes.c_int,     # return | ||||
|     ctypes.c_int,     # dwControl | ||||
|     ctypes.c_int,     # dwEventType | ||||
|     ctypes.c_void_p,  # lpEventData, | ||||
|     ctypes.c_void_p,  # lpContext, | ||||
| ) | ||||
|  | ||||
|  | ||||
| def _ctypes_array(c_type, py_array): | ||||
|     ar = (c_type * len(py_array))() | ||||
|     ar[:] = py_array | ||||
|     return ar | ||||
|  | ||||
|  | ||||
| def win_OpenSCManager(): | ||||
|     res = advapi32.OpenSCManagerW(None, None, SC_MANAGER_ALL_ACCESS) | ||||
|     if not res: | ||||
|         raise Exception('Opening service manager failed - ' | ||||
|                         'are you running this as administrator?') | ||||
|     return res | ||||
|  | ||||
|  | ||||
| def win_install_service(service_name, cmdline): | ||||
|     manager = win_OpenSCManager() | ||||
|     try: | ||||
|         h = advapi32.CreateServiceW( | ||||
|             manager, service_name, None, | ||||
|             SC_MANAGER_CREATE_SERVICE, SERVICE_WIN32_OWN_PROCESS, | ||||
|             SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, | ||||
|             cmdline, None, None, None, None, None) | ||||
|         if not h: | ||||
|             raise OSError('Service creation failed: %s' % ctypes.FormatError()) | ||||
|  | ||||
|         advapi32.CloseServiceHandle(h) | ||||
|     finally: | ||||
|         advapi32.CloseServiceHandle(manager) | ||||
|  | ||||
|  | ||||
| def win_uninstall_service(service_name): | ||||
|     manager = win_OpenSCManager() | ||||
|     try: | ||||
|         h = advapi32.OpenServiceW(manager, service_name, DELETE) | ||||
|         if not h: | ||||
|             raise OSError('Could not find service %s: %s' % ( | ||||
|                 service_name, ctypes.FormatError())) | ||||
|  | ||||
|         try: | ||||
|             if not advapi32.DeleteService(h): | ||||
|                 raise OSError('Deletion failed: %s' % ctypes.FormatError()) | ||||
|         finally: | ||||
|             advapi32.CloseServiceHandle(h) | ||||
|     finally: | ||||
|         advapi32.CloseServiceHandle(manager) | ||||
|  | ||||
|  | ||||
| def win_service_report_event(service_name, msg, is_error=True): | ||||
|     with open('C:/sshkeys/log', 'a', encoding='utf-8') as f: | ||||
|         f.write(msg + '\n') | ||||
|  | ||||
|     event_log = advapi32.RegisterEventSourceW(None, service_name) | ||||
|     if not event_log: | ||||
|         raise OSError('Could not report event: %s' % ctypes.FormatError()) | ||||
|  | ||||
|     try: | ||||
|         type_id = 0x0001 if is_error else 0x0004 | ||||
|         event_id = 0xc0000000 if is_error else 0x40000000 | ||||
|         lines = _ctypes_array(LPTSTR, [msg]) | ||||
|  | ||||
|         if not advapi32.ReportEventW( | ||||
|                 event_log, type_id, 0, event_id, None, len(lines), 0, | ||||
|                 lines, None): | ||||
|             raise OSError('Event reporting failed: %s' % ctypes.FormatError()) | ||||
|     finally: | ||||
|         advapi32.DeregisterEventSource(event_log) | ||||
|  | ||||
|  | ||||
| def win_service_handler(stop_event, *args): | ||||
|     try: | ||||
|         raise ValueError('Handler called with args ' + repr(args)) | ||||
|         TODO | ||||
|     except Exception as e: | ||||
|         tb = traceback.format_exc() | ||||
|         msg = str(e) + '\n' + tb | ||||
|         win_service_report_event(service_name, msg, is_error=True) | ||||
|         raise | ||||
|  | ||||
|  | ||||
| def win_service_set_status(handle, status_code): | ||||
|     svcStatus = SERVICE_STATUS() | ||||
|     svcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS | ||||
|     svcStatus.dwCurrentState = status_code | ||||
|     svcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | ||||
|  | ||||
|     svcStatus.dwServiceSpecificExitCode = 0 | ||||
|  | ||||
|     if not advapi32.SetServiceStatus(handle, ctypes.byref(svcStatus)): | ||||
|         raise OSError('SetServiceStatus failed: %r' % ctypes.FormatError()) | ||||
|  | ||||
|  | ||||
| def win_service_main(service_name, real_main, argc, argv_raw): | ||||
|     try: | ||||
|         #args = [argv_raw[i].value for i in range(argc)] | ||||
|         stop_event = threading.Event() | ||||
|         handler = HandlerEx(functools.partial(stop_event, win_service_handler)) | ||||
|         h = advapi32.RegisterServiceCtrlHandlerExW(service_name, handler, None) | ||||
|         if not h: | ||||
|             raise OSError('Handler registration failed: %s' % | ||||
|                           ctypes.FormatError()) | ||||
|  | ||||
|         TODO | ||||
|     except Exception as e: | ||||
|         tb = traceback.format_exc() | ||||
|         msg = str(e) + '\n' + tb | ||||
|         win_service_report_event(service_name, msg, is_error=True) | ||||
|         raise | ||||
|  | ||||
|  | ||||
| def win_service_start(service_name, real_main): | ||||
|     try: | ||||
|         cb = START_CALLBACK( | ||||
|             functools.partial(win_service_main, service_name, real_main)) | ||||
|         dispatch_table = _ctypes_array(SERVICE_TABLE_ENTRY, [ | ||||
|             SERVICE_TABLE_ENTRY( | ||||
|                 service_name, | ||||
|                 cb | ||||
|             ), | ||||
|             SERVICE_TABLE_ENTRY(None, ctypes.cast(None, START_CALLBACK)) | ||||
|         ]) | ||||
|  | ||||
|         if not advapi32.StartServiceCtrlDispatcherW(dispatch_table): | ||||
|             raise OSError('ctypes start failed: %s' % ctypes.FormatError()) | ||||
|     except Exception as e: | ||||
|         tb = traceback.format_exc() | ||||
|         msg = str(e) + '\n' + tb | ||||
|         win_service_report_event(service_name, msg, is_error=True) | ||||
|         raise | ||||
|  | ||||
|  | ||||
| def main(args=None): | ||||
|     parser = argparse.ArgumentParser() | ||||
|     parser.add_argument('-i', '--install', | ||||
|                         action='store_const', dest='action', const='install', | ||||
|                         help='Launch at Windows startup') | ||||
|     parser.add_argument('-u', '--uninstall', | ||||
|                         action='store_const', dest='action', const='uninstall', | ||||
|                         help='Remove Windows service') | ||||
|     parser.add_argument('-s', '--service', | ||||
|                         action='store_const', dest='action', const='service', | ||||
|                         help='Run as a Windows service') | ||||
|     parser.add_argument('-b', '--bind', metavar='<host:port>', | ||||
|                         action='store', default='localhost:8142', | ||||
|                         help='Bind to host:port (default %default)') | ||||
|     options = parser.parse_args(args=args) | ||||
|  | ||||
|     if options.action == 'install': | ||||
|         fn = os.path.abspath(__file__).replace('v:', '\\\\vboxsrv\\vbox') | ||||
|         cmdline = '%s %s -s -b %s' % (sys.executable, fn, options.bind) | ||||
|         win_install_service(SVCNAME, cmdline) | ||||
|         return | ||||
|  | ||||
|     if options.action == 'uninstall': | ||||
|         win_uninstall_service(SVCNAME) | ||||
|         return | ||||
|  | ||||
|     if options.action == 'service': | ||||
|         win_service_start(SVCNAME, main) | ||||
|         return | ||||
|  | ||||
|     host, port_str = options.bind.split(':') | ||||
|     port = int(port_str) | ||||
|  | ||||
|     print('Listening on %s:%d' % (host, port)) | ||||
|     srv = BuildHTTPServer((host, port), BuildHTTPRequestHandler) | ||||
|     thr = threading.Thread(target=srv.serve_forever) | ||||
|     thr.start() | ||||
|     input('Press ENTER to shut down') | ||||
|     srv.shutdown() | ||||
|     thr.join() | ||||
|  | ||||
|  | ||||
| def rmtree(path): | ||||
|     for name in os.listdir(path): | ||||
|         fname = os.path.join(path, name) | ||||
|         if os.path.isdir(fname): | ||||
|             rmtree(fname) | ||||
|         else: | ||||
|             os.chmod(fname, 0o666) | ||||
|             os.remove(fname) | ||||
|     os.rmdir(path) | ||||
|  | ||||
| #============================================================================== | ||||
|  | ||||
| class BuildError(Exception): | ||||
|     def __init__(self, output, code=500): | ||||
|         self.output = output | ||||
|         self.code = code | ||||
|  | ||||
|     def __str__(self): | ||||
|         return self.output | ||||
|  | ||||
|  | ||||
| class HTTPError(BuildError): | ||||
|     pass | ||||
|  | ||||
|  | ||||
| class PythonBuilder(object): | ||||
|     def __init__(self, **kwargs): | ||||
|         pythonVersion = kwargs.pop('python', '2.7') | ||||
|         try: | ||||
|             key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, r'SOFTWARE\Python\PythonCore\%s\InstallPath' % pythonVersion) | ||||
|             try: | ||||
|                 self.pythonPath, _ = _winreg.QueryValueEx(key, '') | ||||
|             finally: | ||||
|                 _winreg.CloseKey(key) | ||||
|         except Exception: | ||||
|             raise BuildError('No such Python version: %s' % pythonVersion) | ||||
|  | ||||
|         super(PythonBuilder, self).__init__(**kwargs) | ||||
|  | ||||
|  | ||||
| class GITInfoBuilder(object): | ||||
|     def __init__(self, **kwargs): | ||||
|         try: | ||||
|             self.user, self.repoName = kwargs['path'][:2] | ||||
|             self.rev = kwargs.pop('rev') | ||||
|         except ValueError: | ||||
|             raise BuildError('Invalid path') | ||||
|         except KeyError as e: | ||||
|             raise BuildError('Missing mandatory parameter "%s"' % e.args[0]) | ||||
|  | ||||
|         path = os.path.join(os.environ['APPDATA'], 'Build archive', self.repoName, self.user) | ||||
|         if not os.path.exists(path): | ||||
|             os.makedirs(path) | ||||
|         self.basePath = tempfile.mkdtemp(dir=path) | ||||
|         self.buildPath = os.path.join(self.basePath, 'build') | ||||
|  | ||||
|         super(GITInfoBuilder, self).__init__(**kwargs) | ||||
|  | ||||
|  | ||||
| class GITBuilder(GITInfoBuilder): | ||||
|     def build(self): | ||||
|         try: | ||||
|             subprocess.check_output(['git', 'clone', 'git://github.com/%s/%s.git' % (self.user, self.repoName), self.buildPath]) | ||||
|             subprocess.check_output(['git', 'checkout', self.rev], cwd=self.buildPath) | ||||
|         except subprocess.CalledProcessError as e: | ||||
|             raise BuildError(e.output) | ||||
|  | ||||
|         super(GITBuilder, self).build() | ||||
|  | ||||
|  | ||||
| class YoutubeDLBuilder(object): | ||||
|     authorizedUsers = ['fraca7', 'phihag', 'rg3', 'FiloSottile'] | ||||
|  | ||||
|     def __init__(self, **kwargs): | ||||
|         if self.repoName != 'youtube-dl': | ||||
|             raise BuildError('Invalid repository "%s"' % self.repoName) | ||||
|         if self.user not in self.authorizedUsers: | ||||
|             raise HTTPError('Unauthorized user "%s"' % self.user, 401) | ||||
|  | ||||
|         super(YoutubeDLBuilder, self).__init__(**kwargs) | ||||
|  | ||||
|     def build(self): | ||||
|         try: | ||||
|             subprocess.check_output([os.path.join(self.pythonPath, 'python.exe'), 'setup.py', 'py2exe'], | ||||
|                                     cwd=self.buildPath) | ||||
|         except subprocess.CalledProcessError as e: | ||||
|             raise BuildError(e.output) | ||||
|  | ||||
|         super(YoutubeDLBuilder, self).build() | ||||
|  | ||||
|  | ||||
| class DownloadBuilder(object): | ||||
|     def __init__(self, **kwargs): | ||||
|         self.handler = kwargs.pop('handler') | ||||
|         self.srcPath = os.path.join(self.buildPath, *tuple(kwargs['path'][2:])) | ||||
|         self.srcPath = os.path.abspath(os.path.normpath(self.srcPath)) | ||||
|         if not self.srcPath.startswith(self.buildPath): | ||||
|             raise HTTPError(self.srcPath, 401) | ||||
|  | ||||
|         super(DownloadBuilder, self).__init__(**kwargs) | ||||
|  | ||||
|     def build(self): | ||||
|         if not os.path.exists(self.srcPath): | ||||
|             raise HTTPError('No such file', 404) | ||||
|         if os.path.isdir(self.srcPath): | ||||
|             raise HTTPError('Is a directory: %s' % self.srcPath, 401) | ||||
|  | ||||
|         self.handler.send_response(200) | ||||
|         self.handler.send_header('Content-Type', 'application/octet-stream') | ||||
|         self.handler.send_header('Content-Disposition', 'attachment; filename=%s' % os.path.split(self.srcPath)[-1]) | ||||
|         self.handler.send_header('Content-Length', str(os.stat(self.srcPath).st_size)) | ||||
|         self.handler.end_headers() | ||||
|  | ||||
|         with open(self.srcPath, 'rb') as src: | ||||
|             shutil.copyfileobj(src, self.handler.wfile) | ||||
|  | ||||
|         super(DownloadBuilder, self).build() | ||||
|  | ||||
|  | ||||
| class CleanupTempDir(object): | ||||
|     def build(self): | ||||
|         try: | ||||
|             rmtree(self.basePath) | ||||
|         except Exception as e: | ||||
|             print('WARNING deleting "%s": %s' % (self.basePath, e)) | ||||
|  | ||||
|         super(CleanupTempDir, self).build() | ||||
|  | ||||
|  | ||||
| class Null(object): | ||||
|     def __init__(self, **kwargs): | ||||
|         pass | ||||
|  | ||||
|     def start(self): | ||||
|         pass | ||||
|  | ||||
|     def close(self): | ||||
|         pass | ||||
|  | ||||
|     def build(self): | ||||
|         pass | ||||
|  | ||||
|  | ||||
| class Builder(PythonBuilder, GITBuilder, YoutubeDLBuilder, DownloadBuilder, CleanupTempDir, Null): | ||||
|     pass | ||||
|  | ||||
|  | ||||
| class BuildHTTPRequestHandler(BaseHTTPRequestHandler): | ||||
|     actionDict = { 'build': Builder, 'download': Builder } # They're the same, no more caching. | ||||
|  | ||||
|     def do_GET(self): | ||||
|         path = urlparse.urlparse(self.path) | ||||
|         paramDict = dict([(key, value[0]) for key, value in urlparse.parse_qs(path.query).items()]) | ||||
|         action, _, path = path.path.strip('/').partition('/') | ||||
|         if path: | ||||
|             path = path.split('/') | ||||
|             if action in self.actionDict: | ||||
|                 try: | ||||
|                     builder = self.actionDict[action](path=path, handler=self, **paramDict) | ||||
|                     builder.start() | ||||
|                     try: | ||||
|                         builder.build() | ||||
|                     finally: | ||||
|                         builder.close() | ||||
|                 except BuildError as e: | ||||
|                     self.send_response(e.code) | ||||
|                     msg = unicode(e).encode('UTF-8') | ||||
|                     self.send_header('Content-Type', 'text/plain; charset=UTF-8') | ||||
|                     self.send_header('Content-Length', len(msg)) | ||||
|                     self.end_headers() | ||||
|                     self.wfile.write(msg) | ||||
|                 except HTTPError as e: | ||||
|                     self.send_response(e.code, str(e)) | ||||
|             else: | ||||
|                 self.send_response(500, 'Unknown build method "%s"' % action) | ||||
|         else: | ||||
|             self.send_response(500, 'Malformed URL') | ||||
|  | ||||
| #============================================================================== | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     main() | ||||
							
								
								
									
										39
									
								
								devscripts/check-porn.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								devscripts/check-porn.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| #!/usr/bin/env python | ||||
|  | ||||
| """ | ||||
| This script employs a VERY basic heuristic ('porn' in webpage.lower()) to check | ||||
| if we are not 'age_limit' tagging some porn site | ||||
| """ | ||||
|  | ||||
| # Allow direct execution | ||||
| import os | ||||
| import sys | ||||
| sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | ||||
|  | ||||
| from test.helper import get_testcases | ||||
| from youtube_dl.utils import compat_urllib_request | ||||
|  | ||||
| for test in get_testcases(): | ||||
|     try: | ||||
|         webpage = compat_urllib_request.urlopen(test['url'], timeout=10).read() | ||||
|     except: | ||||
|         print('\nFail: {0}'.format(test['name'])) | ||||
|         continue | ||||
|  | ||||
|     webpage = webpage.decode('utf8', 'replace') | ||||
|  | ||||
|     if 'porn' in webpage.lower() and ('info_dict' not in test | ||||
|                                       or 'age_limit' not in test['info_dict'] | ||||
|                                       or test['info_dict']['age_limit'] != 18): | ||||
|         print('\nPotential missing age_limit check: {0}'.format(test['name'])) | ||||
|  | ||||
|     elif 'porn' not in webpage.lower() and ('info_dict' in test and | ||||
|                                             'age_limit' in test['info_dict'] and | ||||
|                                             test['info_dict']['age_limit'] == 18): | ||||
|         print('\nPotential false negative: {0}'.format(test['name'])) | ||||
|  | ||||
|     else: | ||||
|         sys.stdout.write('.') | ||||
|     sys.stdout.flush() | ||||
|  | ||||
| print() | ||||
| @@ -3,31 +3,40 @@ | ||||
| import json | ||||
| import sys | ||||
| import hashlib | ||||
| import urllib.request | ||||
| import os.path | ||||
|  | ||||
|  | ||||
| if len(sys.argv) <= 1: | ||||
| 	print('Specify the version number as parameter') | ||||
| 	sys.exit() | ||||
|     print('Specify the version number as parameter') | ||||
|     sys.exit() | ||||
| version = sys.argv[1] | ||||
|  | ||||
| with open('update/LATEST_VERSION', 'w') as f: | ||||
| 	f.write(version) | ||||
|     f.write(version) | ||||
|  | ||||
| versions_info = json.load(open('update/versions.json')) | ||||
| if 'signature' in versions_info: | ||||
| 	del versions_info['signature'] | ||||
|     del versions_info['signature'] | ||||
|  | ||||
| new_version = {} | ||||
|  | ||||
| filenames = {'bin': 'youtube-dl', 'exe': 'youtube-dl.exe', 'tar': 'youtube-dl-%s.tar.gz' % version} | ||||
| filenames = { | ||||
|     'bin': 'youtube-dl', | ||||
|     'exe': 'youtube-dl.exe', | ||||
|     'tar': 'youtube-dl-%s.tar.gz' % version} | ||||
| build_dir = os.path.join('..', '..', 'build', version) | ||||
| for key, filename in filenames.items(): | ||||
| 	print('Downloading and checksumming %s...' %filename) | ||||
| 	url = 'http://youtube-dl.org/downloads/%s/%s' % (version, filename) | ||||
| 	data = urllib.request.urlopen(url).read() | ||||
| 	sha256sum = hashlib.sha256(data).hexdigest() | ||||
| 	new_version[key] = (url, sha256sum) | ||||
|     url = 'https://yt-dl.org/downloads/%s/%s' % (version, filename) | ||||
|     fn = os.path.join(build_dir, filename) | ||||
|     with open(fn, 'rb') as f: | ||||
|         data = f.read() | ||||
|     if not data: | ||||
|         raise ValueError('File %s is empty!' % fn) | ||||
|     sha256sum = hashlib.sha256(data).hexdigest() | ||||
|     new_version[key] = (url, sha256sum) | ||||
|  | ||||
| versions_info['versions'][version] = new_version | ||||
| versions_info['latest'] = version | ||||
|  | ||||
| json.dump(versions_info, open('update/versions.json', 'w'), indent=4, sort_keys=True) | ||||
| with open('update/versions.json', 'w') as jsonf: | ||||
|     json.dump(versions_info, jsonf, indent=4, sort_keys=True) | ||||
|   | ||||
| @@ -22,7 +22,7 @@ entry_template=textwrap.dedent(""" | ||||
| 									<atom:link href="http://rg3.github.io/youtube-dl" /> | ||||
| 									<atom:content type="xhtml"> | ||||
| 										<div xmlns="http://www.w3.org/1999/xhtml"> | ||||
| 											Downloads available at <a href="http://youtube-dl.org/downloads/@VERSION@/">http://youtube-dl.org/downloads/@VERSION@/</a> | ||||
| 											Downloads available at <a href="https://yt-dl.org/downloads/@VERSION@/">https://yt-dl.org/downloads/@VERSION@/</a> | ||||
| 										</div> | ||||
| 									</atom:content> | ||||
| 									<atom:author> | ||||
| @@ -54,4 +54,3 @@ atom_template = atom_template.replace('@ENTRIES@', entries_str) | ||||
| with open('update/releases.atom','w',encoding='utf-8') as atom_file: | ||||
| 	atom_file.write(atom_template) | ||||
|  | ||||
|  | ||||
|   | ||||
							
								
								
									
										34
									
								
								devscripts/gh-pages/update-sites.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										34
									
								
								devscripts/gh-pages/update-sites.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| #!/usr/bin/env python3 | ||||
|  | ||||
| import sys | ||||
| import os | ||||
| import textwrap | ||||
|  | ||||
| # We must be able to import youtube_dl | ||||
| sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) | ||||
|  | ||||
| import youtube_dl | ||||
|  | ||||
| def main(): | ||||
|     with open('supportedsites.html.in', 'r', encoding='utf-8') as tmplf: | ||||
|         template = tmplf.read() | ||||
|  | ||||
|     ie_htmls = [] | ||||
|     for ie in sorted(youtube_dl.gen_extractors(), key=lambda i: i.IE_NAME.lower()): | ||||
|         ie_html = '<b>{}</b>'.format(ie.IE_NAME) | ||||
|         ie_desc = getattr(ie, 'IE_DESC', None) | ||||
|         if ie_desc is False: | ||||
|             continue | ||||
|         elif ie_desc is not None: | ||||
|             ie_html += ': {}'.format(ie.IE_DESC) | ||||
|         if ie.working() == False: | ||||
|             ie_html += ' (Currently broken)' | ||||
|         ie_htmls.append('<li>{}</li>'.format(ie_html)) | ||||
|  | ||||
|     template = template.replace('@SITES@', textwrap.indent('\n'.join(ie_htmls), '\t')) | ||||
|  | ||||
|     with open('supportedsites.html', 'w', encoding='utf-8') as sitesf: | ||||
|         sitesf.write(template) | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     main() | ||||
| @@ -55,8 +55,8 @@ git push origin "$version" | ||||
| /bin/echo -e "\n### OK, now it is time to build the binaries..." | ||||
| REV=$(git rev-parse HEAD) | ||||
| make youtube-dl youtube-dl.tar.gz | ||||
| wget "http://jeromelaheurte.net:8142/download/rg3/youtube-dl/youtube-dl.exe?rev=$REV" -O youtube-dl.exe || \ | ||||
| 	wget "http://jeromelaheurte.net:8142/build/rg3/youtube-dl/youtube-dl.exe?rev=$REV" -O youtube-dl.exe | ||||
| read -p "VM running? (y/n) " -n 1 | ||||
| wget "http://localhost:8142/build/rg3/youtube-dl/youtube-dl.exe?rev=$REV" -O youtube-dl.exe | ||||
| mkdir -p "build/$version" | ||||
| mv youtube-dl youtube-dl.exe "build/$version" | ||||
| mv youtube-dl.tar.gz "build/$version/youtube-dl-$version.tar.gz" | ||||
| @@ -67,7 +67,7 @@ RELEASE_FILES="youtube-dl youtube-dl.exe youtube-dl-$version.tar.gz" | ||||
| (cd build/$version/ && sha512sum $RELEASE_FILES > SHA2-512SUMS) | ||||
| git checkout HEAD -- youtube-dl youtube-dl.exe | ||||
|  | ||||
| /bin/echo -e "\n### Signing and uploading the new binaries to youtube-dl.org..." | ||||
| /bin/echo -e "\n### Signing and uploading the new binaries to yt-dl.org ..." | ||||
| for f in $RELEASE_FILES; do gpg --detach-sig "build/$version/$f"; done | ||||
| scp -r "build/$version" ytdl@yt-dl.org:html/tmp/ | ||||
| ssh ytdl@yt-dl.org "mv html/tmp/$version html/downloads/" | ||||
| @@ -85,12 +85,9 @@ ROOT=$(pwd) | ||||
|     "$ROOT/devscripts/gh-pages/sign-versions.py" < "$ROOT/updates_key.pem" | ||||
|     "$ROOT/devscripts/gh-pages/generate-download.py" | ||||
|     "$ROOT/devscripts/gh-pages/update-copyright.py" | ||||
|     "$ROOT/devscripts/gh-pages/update-sites.py" | ||||
|     git add *.html *.html.in update | ||||
|     git commit -m "release $version" | ||||
|     git show HEAD | ||||
|     read -p "Is it good, can I push? (y/n) " -n 1 | ||||
|     if [[ ! $REPLY =~ ^[Yy]$ ]]; then exit 1; fi | ||||
|     echo | ||||
|     git push "$ROOT" gh-pages | ||||
|     git push "$ORIGIN_URL" gh-pages | ||||
| ) | ||||
|   | ||||
| @@ -1,92 +0,0 @@ | ||||
| #!/usr/bin/env python | ||||
|  | ||||
| # Generate youtube signature algorithm from test cases | ||||
|  | ||||
| import sys | ||||
|  | ||||
| tests = [ | ||||
|     # 92 - vflQw-fB4 2013/07/17 | ||||
|     ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$%^&*()_-+={[]}|:;?/>.<'`~\"", | ||||
|      "mrtyuioplkjhgfdsazxcvbnq1234567890QWERTY}IOPLKJHGFDSAZXCVBNM!@#$%^&*()_-+={[]\"|:;"), | ||||
|     # 90 | ||||
|     ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$%^&*()_-+={[]}|:;?/>.<'`", | ||||
|      "mrtyuioplkjhgfdsazxcvbne1234567890QWER[YUIOPLKJHGFDSAZXCVBNM!@#$%^&*()_-+={`]}|"), | ||||
|     # 88 | ||||
|     ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$%^&*()_-+={[]}|:;?/>.<", | ||||
|      "J:|}][{=+-_)(*&;%$#@>MNBVCXZASDFGH^KLPOIUYTREWQ0987654321mnbvcxzasdfghrklpoiuytej"), | ||||
|     # 87 | ||||
|     ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$^&*()_-+={[]}|:;?/>.<", | ||||
|      "tyuioplkjhgfdsazxcv<nm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$^&*()_-+={[]}|:;?/>"), | ||||
|     # 86 - vfl_ymO4Z 2013/06/27 | ||||
|     ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$%^&*()_-+={[|};?/>.<", | ||||
|      "ertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!/#$%^&*()_-+={[|};?@"), | ||||
|     # 85 - vflSAFCP9 2013/07/19 | ||||
|     ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$%^&*()_-+={[};?/>.<", | ||||
|      "ertyuiqplkjhgfdsazx$vbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#<%^&*()_-+={[};?/c"), | ||||
|     # 84 | ||||
|     ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$%^&*()_-+={[};?>.<", | ||||
|      "<.>?;}[{=+-_)(*&^%$#@!MNBVCXZASDFGHJKLPOIUYTREWe098765432rmnbvcxzasdfghjklpoiuyt1"), | ||||
|     # 83 - vflcaqGO8 2013/07/11 | ||||
|     ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!#$%^&*()_+={[};?/>.<", | ||||
|      "urty8ioplkjhgfdsazxcvbqm1234567S90QWERTYUIOPLKJHGFDnAZXCVBNM!#$%^&*()_+={[};?/>.<"), | ||||
|     # 82 | ||||
|     ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKHGFDSAZXCVBNM!@#$%^&*(-+={[};?/>.<", | ||||
|      "Q>/?;}[{=+-(*<^%$#@!MNBVCXZASDFGHKLPOIUY8REWT0q&7654321mnbvcxzasdfghjklpoiuytrew9"), | ||||
|     # 81 | ||||
|     ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKHGFDSAZXCVBNM!@#$%^&*(-+={[};?/>.", | ||||
|      "urty8ioplkjhgfdsazxcvbqm1234567e90QWERTYUIOPLKHGFDSnZXCVBNM!@#$%^&*(-+={[};?/>."), | ||||
| ] | ||||
|  | ||||
| def find_matching(wrong, right): | ||||
|     idxs = [wrong.index(c) for c in right] | ||||
|     return compress(idxs) | ||||
|     return ('s[%d]' % i for i in idxs) | ||||
|  | ||||
| def compress(idxs): | ||||
|     def _genslice(start, end, step): | ||||
|         starts = '' if start == 0 else str(start) | ||||
|         ends = ':%d' % (end+step) | ||||
|         steps = '' if step == 1 else (':%d' % step) | ||||
|         return 's[%s%s%s]' % (starts, ends, steps) | ||||
|  | ||||
|     step = None | ||||
|     for i, prev in zip(idxs[1:], idxs[:-1]): | ||||
|         if step is not None: | ||||
|             if i - prev == step: | ||||
|                 continue | ||||
|             yield _genslice(start, prev, step) | ||||
|             step = None | ||||
|             continue | ||||
|         if i - prev in [-1, 1]: | ||||
|             step = i - prev | ||||
|             start = prev | ||||
|             continue | ||||
|         else: | ||||
|             yield 's[%d]' % prev | ||||
|     if step is None: | ||||
|         yield 's[%d]' % i | ||||
|     else: | ||||
|         yield _genslice(start, i, step) | ||||
|  | ||||
| def _assert_compress(inp, exp): | ||||
|     res = list(compress(inp)) | ||||
|     if res != exp: | ||||
|         print('Got %r, expected %r' % (res, exp)) | ||||
|         assert res == exp | ||||
| _assert_compress([0,2,4,6], ['s[0]', 's[2]', 's[4]', 's[6]']) | ||||
| _assert_compress([0,1,2,4,6,7], ['s[:3]', 's[4]', 's[6:8]']) | ||||
| _assert_compress([8,0,1,2,4,7,6,9], ['s[8]', 's[:3]', 's[4]', 's[7:5:-1]', 's[9]']) | ||||
|  | ||||
| def gen(wrong, right, indent): | ||||
|     code = ' + '.join(find_matching(wrong, right)) | ||||
|     return 'if len(s) == %d:\n%s    return %s\n' % (len(wrong), indent, code) | ||||
|  | ||||
| def genall(tests): | ||||
|     indent = ' ' * 8 | ||||
|     return indent + (indent + 'el').join(gen(wrong, right, indent) for wrong,right in tests) | ||||
|  | ||||
| def main(): | ||||
|     print(genall(tests)) | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     main() | ||||
							
								
								
									
										8
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								setup.py
									
									
									
									
									
								
							| @@ -8,8 +8,10 @@ import sys | ||||
|  | ||||
| try: | ||||
|     from setuptools import setup | ||||
|     setuptools_available = True | ||||
| except ImportError: | ||||
|     from distutils.core import setup | ||||
|     setuptools_available = False | ||||
|  | ||||
| try: | ||||
|     # This will create an exe that needs Microsoft Visual C++ 2008 | ||||
| @@ -43,13 +45,16 @@ if len(sys.argv) >= 2 and sys.argv[1] == 'py2exe': | ||||
|     params = py2exe_params | ||||
| else: | ||||
|     params = { | ||||
|         'scripts': ['bin/youtube-dl'], | ||||
|         'data_files': [  # Installing system-wide would require sudo... | ||||
|             ('etc/bash_completion.d', ['youtube-dl.bash-completion']), | ||||
|             ('share/doc/youtube_dl', ['README.txt']), | ||||
|             ('share/man/man1/', ['youtube-dl.1']) | ||||
|         ] | ||||
|     } | ||||
|     if setuptools_available: | ||||
|         params['entry_points'] = {'console_scripts': ['youtube-dl = youtube_dl:main']} | ||||
|     else: | ||||
|         params['scripts'] = ['bin/youtube-dl'] | ||||
|  | ||||
| # Get the version from youtube_dl/version.py without importing the package | ||||
| exec(compile(open('youtube_dl/version.py').read(), | ||||
| @@ -63,6 +68,7 @@ setup( | ||||
|     ' YouTube.com and other video sites.', | ||||
|     url='https://github.com/rg3/youtube-dl', | ||||
|     author='Ricardo Garcia', | ||||
|     author_email='ytdl@yt-dl.org', | ||||
|     maintainer='Philipp Hagemeister', | ||||
|     maintainer_email='phihag@phihag.de', | ||||
|     packages=['youtube_dl', 'youtube_dl.extractor'], | ||||
|   | ||||
							
								
								
									
										0
									
								
								test/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								test/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -1,38 +1,80 @@ | ||||
| import errno | ||||
| import io | ||||
| import hashlib | ||||
| import json | ||||
| import os.path | ||||
| import re | ||||
| import types | ||||
| import sys | ||||
|  | ||||
| import youtube_dl.extractor | ||||
| from youtube_dl import YoutubeDL, YoutubeDLHandler | ||||
| from youtube_dl.utils import ( | ||||
|     compat_cookiejar, | ||||
|     compat_urllib_request, | ||||
| ) | ||||
| from youtube_dl import YoutubeDL | ||||
| from youtube_dl.utils import preferredencoding | ||||
|  | ||||
| # General configuration (from __init__, not very elegant...) | ||||
| jar = compat_cookiejar.CookieJar() | ||||
| cookie_processor = compat_urllib_request.HTTPCookieProcessor(jar) | ||||
| proxy_handler = compat_urllib_request.ProxyHandler() | ||||
| opener = compat_urllib_request.build_opener(proxy_handler, cookie_processor, YoutubeDLHandler()) | ||||
| compat_urllib_request.install_opener(opener) | ||||
|  | ||||
| PARAMETERS_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "parameters.json") | ||||
| with io.open(PARAMETERS_FILE, encoding='utf-8') as pf: | ||||
|     parameters = json.load(pf) | ||||
| def global_setup(): | ||||
|     youtube_dl._setup_opener(timeout=10) | ||||
|  | ||||
|  | ||||
| def get_params(override=None): | ||||
|     PARAMETERS_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), | ||||
|                                    "parameters.json") | ||||
|     with io.open(PARAMETERS_FILE, encoding='utf-8') as pf: | ||||
|         parameters = json.load(pf) | ||||
|     if override: | ||||
|         parameters.update(override) | ||||
|     return parameters | ||||
|  | ||||
|  | ||||
| def try_rm(filename): | ||||
|     """ Remove a file if it exists """ | ||||
|     try: | ||||
|         os.remove(filename) | ||||
|     except OSError as ose: | ||||
|         if ose.errno != errno.ENOENT: | ||||
|             raise | ||||
|  | ||||
|  | ||||
| def report_warning(message): | ||||
|     ''' | ||||
|     Print the message to stderr, it will be prefixed with 'WARNING:' | ||||
|     If stderr is a tty file the 'WARNING:' will be colored | ||||
|     ''' | ||||
|     if sys.stderr.isatty() and os.name != 'nt': | ||||
|         _msg_header = u'\033[0;33mWARNING:\033[0m' | ||||
|     else: | ||||
|         _msg_header = u'WARNING:' | ||||
|     output = u'%s %s\n' % (_msg_header, message) | ||||
|     if 'b' in getattr(sys.stderr, 'mode', '') or sys.version_info[0] < 3: | ||||
|         output = output.encode(preferredencoding()) | ||||
|     sys.stderr.write(output) | ||||
|  | ||||
|  | ||||
| class FakeYDL(YoutubeDL): | ||||
|     def __init__(self): | ||||
|         self.result = [] | ||||
|     def __init__(self, override=None): | ||||
|         # Different instances of the downloader can't share the same dictionary | ||||
|         # some test set the "sublang" parameter, which would break the md5 checks. | ||||
|         self.params = dict(parameters) | ||||
|     def to_screen(self, s): | ||||
|         params = get_params(override=override) | ||||
|         super(FakeYDL, self).__init__(params) | ||||
|         self.result = [] | ||||
|          | ||||
|     def to_screen(self, s, skip_eol=None): | ||||
|         print(s) | ||||
|  | ||||
|     def trouble(self, s, tb=None): | ||||
|         raise Exception(s) | ||||
|  | ||||
|     def download(self, x): | ||||
|         self.result.append(x) | ||||
|  | ||||
|     def expect_warning(self, regex): | ||||
|         # Silence an expected warning matching a regex | ||||
|         old_report_warning = self.report_warning | ||||
|         def report_warning(self, message): | ||||
|             if re.match(regex, message): return | ||||
|             old_report_warning(message) | ||||
|         self.report_warning = types.MethodType(report_warning, self) | ||||
|  | ||||
| def get_testcases(): | ||||
|     for ie in youtube_dl.extractor.gen_extractors(): | ||||
|         t = getattr(ie, '_TEST', None) | ||||
| @@ -42,3 +84,6 @@ def get_testcases(): | ||||
|         for t in getattr(ie, '_TESTS', []): | ||||
|             t['name'] = type(ie).__name__[:-len('IE')] | ||||
|             yield t | ||||
|  | ||||
|  | ||||
| md5 = lambda s: hashlib.md5(s.encode('utf-8')).hexdigest() | ||||
|   | ||||
| @@ -38,7 +38,6 @@ | ||||
|     "writedescription": false,  | ||||
|     "writeinfojson": true,  | ||||
|     "writesubtitles": false, | ||||
|     "onlysubtitles": false, | ||||
|     "allsubtitles": false, | ||||
|     "listssubtitles": false | ||||
| } | ||||
|   | ||||
							
								
								
									
										145
									
								
								test/test_YoutubeDL.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								test/test_YoutubeDL.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,145 @@ | ||||
| #!/usr/bin/env python | ||||
|  | ||||
| # Allow direct execution | ||||
| import os | ||||
| import sys | ||||
| import unittest | ||||
| sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | ||||
|  | ||||
| from test.helper import FakeYDL | ||||
|  | ||||
|  | ||||
| class YDL(FakeYDL): | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         super(YDL, self).__init__(*args, **kwargs) | ||||
|         self.downloaded_info_dicts = [] | ||||
|         self.msgs = [] | ||||
|  | ||||
|     def process_info(self, info_dict): | ||||
|         self.downloaded_info_dicts.append(info_dict) | ||||
|  | ||||
|     def to_screen(self, msg): | ||||
|         self.msgs.append(msg) | ||||
|  | ||||
|  | ||||
| class TestFormatSelection(unittest.TestCase): | ||||
|     def test_prefer_free_formats(self): | ||||
|         # Same resolution => download webm | ||||
|         ydl = YDL() | ||||
|         ydl.params['prefer_free_formats'] = True | ||||
|         formats = [ | ||||
|             {u'ext': u'webm', u'height': 460}, | ||||
|             {u'ext': u'mp4',  u'height': 460}, | ||||
|         ] | ||||
|         info_dict = {u'formats': formats, u'extractor': u'test'} | ||||
|         ydl.process_ie_result(info_dict) | ||||
|         downloaded = ydl.downloaded_info_dicts[0] | ||||
|         self.assertEqual(downloaded[u'ext'], u'webm') | ||||
|  | ||||
|         # Different resolution => download best quality (mp4) | ||||
|         ydl = YDL() | ||||
|         ydl.params['prefer_free_formats'] = True | ||||
|         formats = [ | ||||
|             {u'ext': u'webm', u'height': 720}, | ||||
|             {u'ext': u'mp4', u'height': 1080}, | ||||
|         ] | ||||
|         info_dict[u'formats'] = formats | ||||
|         ydl.process_ie_result(info_dict) | ||||
|         downloaded = ydl.downloaded_info_dicts[0] | ||||
|         self.assertEqual(downloaded[u'ext'], u'mp4') | ||||
|  | ||||
|         # No prefer_free_formats => keep original formats order | ||||
|         ydl = YDL() | ||||
|         ydl.params['prefer_free_formats'] = False | ||||
|         formats = [ | ||||
|             {u'ext': u'webm', u'height': 720}, | ||||
|             {u'ext': u'flv', u'height': 720}, | ||||
|         ] | ||||
|         info_dict[u'formats'] = formats | ||||
|         ydl.process_ie_result(info_dict) | ||||
|         downloaded = ydl.downloaded_info_dicts[0] | ||||
|         self.assertEqual(downloaded[u'ext'], u'flv') | ||||
|  | ||||
|     def test_format_limit(self): | ||||
|         formats = [ | ||||
|             {u'format_id': u'meh', u'url': u'http://example.com/meh'}, | ||||
|             {u'format_id': u'good', u'url': u'http://example.com/good'}, | ||||
|             {u'format_id': u'great', u'url': u'http://example.com/great'}, | ||||
|             {u'format_id': u'excellent', u'url': u'http://example.com/exc'}, | ||||
|         ] | ||||
|         info_dict = { | ||||
|             u'formats': formats, u'extractor': u'test', 'id': 'testvid'} | ||||
|  | ||||
|         ydl = YDL() | ||||
|         ydl.process_ie_result(info_dict) | ||||
|         downloaded = ydl.downloaded_info_dicts[0] | ||||
|         self.assertEqual(downloaded[u'format_id'], u'excellent') | ||||
|  | ||||
|         ydl = YDL({'format_limit': 'good'}) | ||||
|         assert ydl.params['format_limit'] == 'good' | ||||
|         ydl.process_ie_result(info_dict) | ||||
|         downloaded = ydl.downloaded_info_dicts[0] | ||||
|         self.assertEqual(downloaded[u'format_id'], u'good') | ||||
|  | ||||
|         ydl = YDL({'format_limit': 'great', 'format': 'all'}) | ||||
|         ydl.process_ie_result(info_dict) | ||||
|         self.assertEqual(ydl.downloaded_info_dicts[0][u'format_id'], u'meh') | ||||
|         self.assertEqual(ydl.downloaded_info_dicts[1][u'format_id'], u'good') | ||||
|         self.assertEqual(ydl.downloaded_info_dicts[2][u'format_id'], u'great') | ||||
|         self.assertTrue('3' in ydl.msgs[0]) | ||||
|  | ||||
|         ydl = YDL() | ||||
|         ydl.params['format_limit'] = 'excellent' | ||||
|         ydl.process_ie_result(info_dict) | ||||
|         downloaded = ydl.downloaded_info_dicts[0] | ||||
|         self.assertEqual(downloaded[u'format_id'], u'excellent') | ||||
|  | ||||
|     def test_format_selection(self): | ||||
|         formats = [ | ||||
|             {u'format_id': u'35', u'ext': u'mp4'}, | ||||
|             {u'format_id': u'45', u'ext': u'webm'}, | ||||
|             {u'format_id': u'47', u'ext': u'webm'}, | ||||
|             {u'format_id': u'2', u'ext': u'flv'}, | ||||
|         ] | ||||
|         info_dict = {u'formats': formats, u'extractor': u'test'} | ||||
|  | ||||
|         ydl = YDL({'format': u'20/47'}) | ||||
|         ydl.process_ie_result(info_dict) | ||||
|         downloaded = ydl.downloaded_info_dicts[0] | ||||
|         self.assertEqual(downloaded['format_id'], u'47') | ||||
|  | ||||
|         ydl = YDL({'format': u'20/71/worst'}) | ||||
|         ydl.process_ie_result(info_dict) | ||||
|         downloaded = ydl.downloaded_info_dicts[0] | ||||
|         self.assertEqual(downloaded['format_id'], u'35') | ||||
|  | ||||
|         ydl = YDL() | ||||
|         ydl.process_ie_result(info_dict) | ||||
|         downloaded = ydl.downloaded_info_dicts[0] | ||||
|         self.assertEqual(downloaded['format_id'], u'2') | ||||
|  | ||||
|         ydl = YDL({'format': u'webm/mp4'}) | ||||
|         ydl.process_ie_result(info_dict) | ||||
|         downloaded = ydl.downloaded_info_dicts[0] | ||||
|         self.assertEqual(downloaded['format_id'], u'47') | ||||
|  | ||||
|         ydl = YDL({'format': u'3gp/40/mp4'}) | ||||
|         ydl.process_ie_result(info_dict) | ||||
|         downloaded = ydl.downloaded_info_dicts[0] | ||||
|         self.assertEqual(downloaded['format_id'], u'35') | ||||
|  | ||||
|     def test_add_extra_info(self): | ||||
|         test_dict = { | ||||
|             'extractor': 'Foo', | ||||
|         } | ||||
|         extra_info = { | ||||
|             'extractor': 'Bar', | ||||
|             'playlist': 'funny videos', | ||||
|         } | ||||
|         YDL.add_extra_info(test_dict, extra_info) | ||||
|         self.assertEqual(test_dict['extractor'], 'Foo') | ||||
|         self.assertEqual(test_dict['playlist'], 'funny videos') | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     unittest.main() | ||||
							
								
								
									
										55
									
								
								test/test_age_restriction.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								test/test_age_restriction.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| #!/usr/bin/env python | ||||
|  | ||||
| # Allow direct execution | ||||
| import os | ||||
| import sys | ||||
| import unittest | ||||
| sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | ||||
|  | ||||
| from test.helper import global_setup, try_rm | ||||
| global_setup() | ||||
|  | ||||
|  | ||||
| from youtube_dl import YoutubeDL | ||||
|  | ||||
|  | ||||
| def _download_restricted(url, filename, age): | ||||
|     """ Returns true iff the file has been downloaded """ | ||||
|  | ||||
|     params = { | ||||
|         'age_limit': age, | ||||
|         'skip_download': True, | ||||
|         'writeinfojson': True, | ||||
|         "outtmpl": "%(id)s.%(ext)s", | ||||
|     } | ||||
|     ydl = YoutubeDL(params) | ||||
|     ydl.add_default_info_extractors() | ||||
|     json_filename = filename + '.info.json' | ||||
|     try_rm(json_filename) | ||||
|     ydl.download([url]) | ||||
|     res = os.path.exists(json_filename) | ||||
|     try_rm(json_filename) | ||||
|     return res | ||||
|  | ||||
|  | ||||
| class TestAgeRestriction(unittest.TestCase): | ||||
|     def _assert_restricted(self, url, filename, age, old_age=None): | ||||
|         self.assertTrue(_download_restricted(url, filename, old_age)) | ||||
|         self.assertFalse(_download_restricted(url, filename, age)) | ||||
|  | ||||
|     def test_youtube(self): | ||||
|         self._assert_restricted('07FYdnEawAQ', '07FYdnEawAQ.mp4', 10) | ||||
|  | ||||
|     def test_youporn(self): | ||||
|         self._assert_restricted( | ||||
|             'http://www.youporn.com/watch/505835/sex-ed-is-it-safe-to-masturbate-daily/', | ||||
|             '505835.mp4', 2, old_age=25) | ||||
|  | ||||
|     def test_pornotube(self): | ||||
|         self._assert_restricted( | ||||
|             'http://pornotube.com/c/173/m/1689755/Marilyn-Monroe-Bathing', | ||||
|             '1689755.flv', 13) | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     unittest.main() | ||||
| @@ -1,34 +1,66 @@ | ||||
| #!/usr/bin/env python | ||||
|  | ||||
| import sys | ||||
| import unittest | ||||
|  | ||||
| # Allow direct execution | ||||
| import os | ||||
| sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | ||||
| import sys | ||||
| import unittest | ||||
| sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | ||||
|  | ||||
|  | ||||
| from test.helper import get_testcases | ||||
|  | ||||
| from youtube_dl.extractor import ( | ||||
|     gen_extractors, | ||||
|     JustinTVIE, | ||||
|     YoutubeIE, | ||||
| ) | ||||
|  | ||||
| from youtube_dl.extractor import YoutubeIE, YoutubePlaylistIE, YoutubeChannelIE, JustinTVIE, gen_extractors | ||||
| from helper import get_testcases | ||||
|  | ||||
| class TestAllURLsMatching(unittest.TestCase): | ||||
|     def setUp(self): | ||||
|         self.ies = gen_extractors() | ||||
|  | ||||
|     def matching_ies(self, url): | ||||
|         return [ie.IE_NAME for ie in self.ies if ie.suitable(url) and ie.IE_NAME != 'generic'] | ||||
|  | ||||
|     def assertMatch(self, url, ie_list): | ||||
|         self.assertEqual(self.matching_ies(url), ie_list) | ||||
|  | ||||
|     def test_youtube_playlist_matching(self): | ||||
|         self.assertTrue(YoutubePlaylistIE.suitable(u'ECUl4u3cNGP61MdtwGTqZA0MreSaDybji8')) | ||||
|         self.assertTrue(YoutubePlaylistIE.suitable(u'UUBABnxM4Ar9ten8Mdjj1j0Q')) #585 | ||||
|         self.assertTrue(YoutubePlaylistIE.suitable(u'PL63F0C78739B09958')) | ||||
|         self.assertTrue(YoutubePlaylistIE.suitable(u'https://www.youtube.com/playlist?list=UUBABnxM4Ar9ten8Mdjj1j0Q')) | ||||
|         self.assertTrue(YoutubePlaylistIE.suitable(u'https://www.youtube.com/course?list=ECUl4u3cNGP61MdtwGTqZA0MreSaDybji8')) | ||||
|         self.assertTrue(YoutubePlaylistIE.suitable(u'https://www.youtube.com/playlist?list=PLwP_SiAcdui0KVebT0mU9Apz359a4ubsC')) | ||||
|         self.assertTrue(YoutubePlaylistIE.suitable(u'https://www.youtube.com/watch?v=AV6J6_AeFEQ&playnext=1&list=PL4023E734DA416012')) #668 | ||||
|         self.assertFalse(YoutubePlaylistIE.suitable(u'PLtS2H6bU1M')) | ||||
|         assertPlaylist = lambda url: self.assertMatch(url, ['youtube:playlist']) | ||||
|         assertPlaylist(u'ECUl4u3cNGP61MdtwGTqZA0MreSaDybji8') | ||||
|         assertPlaylist(u'UUBABnxM4Ar9ten8Mdjj1j0Q') #585 | ||||
|         assertPlaylist(u'PL63F0C78739B09958') | ||||
|         assertPlaylist(u'https://www.youtube.com/playlist?list=UUBABnxM4Ar9ten8Mdjj1j0Q') | ||||
|         assertPlaylist(u'https://www.youtube.com/course?list=ECUl4u3cNGP61MdtwGTqZA0MreSaDybji8') | ||||
|         assertPlaylist(u'https://www.youtube.com/playlist?list=PLwP_SiAcdui0KVebT0mU9Apz359a4ubsC') | ||||
|         assertPlaylist(u'https://www.youtube.com/watch?v=AV6J6_AeFEQ&playnext=1&list=PL4023E734DA416012') #668 | ||||
|         self.assertFalse('youtube:playlist' in self.matching_ies(u'PLtS2H6bU1M')) | ||||
|  | ||||
|     def test_youtube_matching(self): | ||||
|         self.assertTrue(YoutubeIE.suitable(u'PLtS2H6bU1M')) | ||||
|         self.assertFalse(YoutubeIE.suitable(u'https://www.youtube.com/watch?v=AV6J6_AeFEQ&playnext=1&list=PL4023E734DA416012')) #668 | ||||
|         self.assertMatch('http://youtu.be/BaW_jenozKc', ['youtube']) | ||||
|         self.assertMatch('http://www.youtube.com/v/BaW_jenozKc', ['youtube']) | ||||
|         self.assertMatch('https://youtube.googleapis.com/v/BaW_jenozKc', ['youtube']) | ||||
|  | ||||
|     def test_youtube_channel_matching(self): | ||||
|         self.assertTrue(YoutubeChannelIE.suitable('https://www.youtube.com/channel/HCtnHdj3df7iM')) | ||||
|         self.assertTrue(YoutubeChannelIE.suitable('https://www.youtube.com/channel/HCtnHdj3df7iM?feature=gb_ch_rec')) | ||||
|         self.assertTrue(YoutubeChannelIE.suitable('https://www.youtube.com/channel/HCtnHdj3df7iM/videos')) | ||||
|         assertChannel = lambda url: self.assertMatch(url, ['youtube:channel']) | ||||
|         assertChannel('https://www.youtube.com/channel/HCtnHdj3df7iM') | ||||
|         assertChannel('https://www.youtube.com/channel/HCtnHdj3df7iM?feature=gb_ch_rec') | ||||
|         assertChannel('https://www.youtube.com/channel/HCtnHdj3df7iM/videos') | ||||
|  | ||||
|     def test_youtube_user_matching(self): | ||||
|         self.assertMatch('www.youtube.com/NASAgovVideo/videos', ['youtube:user']) | ||||
|  | ||||
|     def test_youtube_feeds(self): | ||||
|         self.assertMatch('https://www.youtube.com/feed/watch_later', ['youtube:watch_later']) | ||||
|         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/my_favorites', ['youtube:favorites']) | ||||
|  | ||||
|     def test_youtube_show_matching(self): | ||||
|         self.assertMatch('http://www.youtube.com/show/airdisasters', ['youtube:show']) | ||||
|  | ||||
|     def test_justin_tv_channelid_matching(self): | ||||
|         self.assertTrue(JustinTVIE.suitable(u"justin.tv/vanillatv")) | ||||
| @@ -47,9 +79,13 @@ class TestAllURLsMatching(unittest.TestCase): | ||||
|         self.assertTrue(JustinTVIE.suitable(u"http://www.twitch.tv/tsm_theoddone/c/2349361")) | ||||
|  | ||||
|     def test_youtube_extract(self): | ||||
|         self.assertEqual(YoutubeIE()._extract_id('http://www.youtube.com/watch?&v=BaW_jenozKc'), 'BaW_jenozKc') | ||||
|         self.assertEqual(YoutubeIE()._extract_id('https://www.youtube.com/watch?&v=BaW_jenozKc'), 'BaW_jenozKc') | ||||
|         self.assertEqual(YoutubeIE()._extract_id('https://www.youtube.com/watch?feature=player_embedded&v=BaW_jenozKc'), 'BaW_jenozKc') | ||||
|         assertExtractId = lambda url, id: self.assertEqual(YoutubeIE()._extract_id(url), id) | ||||
|         assertExtractId('http://www.youtube.com/watch?&v=BaW_jenozKc', 'BaW_jenozKc') | ||||
|         assertExtractId('https://www.youtube.com/watch?&v=BaW_jenozKc', 'BaW_jenozKc') | ||||
|         assertExtractId('https://www.youtube.com/watch?feature=player_embedded&v=BaW_jenozKc', 'BaW_jenozKc') | ||||
|         assertExtractId('https://www.youtube.com/watch_popup?v=BaW_jenozKc', 'BaW_jenozKc') | ||||
|         assertExtractId('http://www.youtube.com/watch?v=BaW_jenozKcsharePLED17F32AD9753930', 'BaW_jenozKc') | ||||
|         assertExtractId('BaW_jenozKc', 'BaW_jenozKc') | ||||
|  | ||||
|     def test_no_duplicates(self): | ||||
|         ies = gen_extractors() | ||||
| @@ -62,15 +98,12 @@ class TestAllURLsMatching(unittest.TestCase): | ||||
|                     self.assertFalse(ie.suitable(url), '%s should not match URL %r' % (type(ie).__name__, url)) | ||||
|  | ||||
|     def test_keywords(self): | ||||
|         ies = gen_extractors() | ||||
|         matching_ies = lambda url: [ie.IE_NAME for ie in ies | ||||
|                                     if ie.suitable(url) and ie.IE_NAME != 'generic'] | ||||
|         self.assertEqual(matching_ies(':ytsubs'), ['youtube:subscriptions']) | ||||
|         self.assertEqual(matching_ies(':ytsubscriptions'), ['youtube:subscriptions']) | ||||
|         self.assertEqual(matching_ies(':thedailyshow'), ['ComedyCentral']) | ||||
|         self.assertEqual(matching_ies(':tds'), ['ComedyCentral']) | ||||
|         self.assertEqual(matching_ies(':colbertreport'), ['ComedyCentral']) | ||||
|         self.assertEqual(matching_ies(':cr'), ['ComedyCentral']) | ||||
|         self.assertMatch(':ytsubs', ['youtube:subscriptions']) | ||||
|         self.assertMatch(':ytsubscriptions', ['youtube:subscriptions']) | ||||
|         self.assertMatch(':thedailyshow', ['ComedyCentral']) | ||||
|         self.assertMatch(':tds', ['ComedyCentral']) | ||||
|         self.assertMatch(':colbertreport', ['ComedyCentral']) | ||||
|         self.assertMatch(':cr', ['ComedyCentral']) | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|   | ||||
| @@ -1,43 +1,40 @@ | ||||
| #!/usr/bin/env python | ||||
|  | ||||
| import errno | ||||
| # Allow direct execution | ||||
| import os | ||||
| import sys | ||||
| import unittest | ||||
| sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | ||||
|  | ||||
| from test.helper import ( | ||||
|     get_params, | ||||
|     get_testcases, | ||||
|     global_setup, | ||||
|     try_rm, | ||||
|     md5, | ||||
|     report_warning | ||||
| ) | ||||
| global_setup() | ||||
|  | ||||
|  | ||||
| import hashlib | ||||
| import io | ||||
| import os | ||||
| import json | ||||
| import unittest | ||||
| import sys | ||||
| import socket | ||||
| import binascii | ||||
|  | ||||
| # Allow direct execution | ||||
| sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | ||||
|  | ||||
| import youtube_dl.YoutubeDL | ||||
| from youtube_dl.utils import * | ||||
|  | ||||
| PARAMETERS_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "parameters.json") | ||||
| from youtube_dl.utils import ( | ||||
|     compat_str, | ||||
|     compat_urllib_error, | ||||
|     compat_HTTPError, | ||||
|     DownloadError, | ||||
|     ExtractorError, | ||||
|     UnavailableVideoError, | ||||
| ) | ||||
| from youtube_dl.extractor import get_info_extractor | ||||
|  | ||||
| RETRIES = 3 | ||||
|  | ||||
| # General configuration (from __init__, not very elegant...) | ||||
| jar = compat_cookiejar.CookieJar() | ||||
| cookie_processor = compat_urllib_request.HTTPCookieProcessor(jar) | ||||
| proxy_handler = compat_urllib_request.ProxyHandler() | ||||
| opener = compat_urllib_request.build_opener(proxy_handler, cookie_processor, YoutubeDLHandler()) | ||||
| compat_urllib_request.install_opener(opener) | ||||
| socket.setdefaulttimeout(10) | ||||
|  | ||||
| def _try_rm(filename): | ||||
|     """ Remove a file if it exists """ | ||||
|     try: | ||||
|         os.remove(filename) | ||||
|     except OSError as ose: | ||||
|         if ose.errno != errno.ENOENT: | ||||
|             raise | ||||
|  | ||||
| md5 = lambda s: hashlib.md5(s.encode('utf-8')).hexdigest() | ||||
|  | ||||
| class YoutubeDL(youtube_dl.YoutubeDL): | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         self.to_stderr = self.to_screen | ||||
| @@ -54,17 +51,12 @@ def _file_md5(fn): | ||||
|     with open(fn, 'rb') as f: | ||||
|         return hashlib.md5(f.read()).hexdigest() | ||||
|  | ||||
| from helper import get_testcases | ||||
| defs = get_testcases() | ||||
|  | ||||
| with io.open(PARAMETERS_FILE, encoding='utf-8') as pf: | ||||
|     parameters = json.load(pf) | ||||
|  | ||||
|  | ||||
| class TestDownload(unittest.TestCase): | ||||
|     maxDiff = None | ||||
|     def setUp(self): | ||||
|         self.parameters = parameters | ||||
|         self.defs = defs | ||||
|  | ||||
| ### Dynamically generate tests | ||||
| @@ -72,20 +64,27 @@ def generator(test_case): | ||||
|  | ||||
|     def test_template(self): | ||||
|         ie = youtube_dl.extractor.get_info_extractor(test_case['name']) | ||||
|         other_ies = [get_info_extractor(ie_key) for ie_key in test_case.get('add_ie', [])] | ||||
|         def print_skipping(reason): | ||||
|             print('Skipping %s: %s' % (test_case['name'], reason)) | ||||
|         if not ie._WORKING: | ||||
|         if not ie.working(): | ||||
|             print_skipping('IE marked as not _WORKING') | ||||
|             return | ||||
|         if 'playlist' not in test_case and not test_case['file']: | ||||
|             print_skipping('No output file specified') | ||||
|             return | ||||
|         if 'playlist' not in test_case: | ||||
|             info_dict = test_case.get('info_dict', {}) | ||||
|             if not test_case.get('file') and not (info_dict.get('id') and info_dict.get('ext')): | ||||
|                 print_skipping('The output file cannot be know, the "file" ' | ||||
|                     'key is missing or the info_dict is incomplete') | ||||
|                 return | ||||
|         if 'skip' in test_case: | ||||
|             print_skipping(test_case['skip']) | ||||
|             return | ||||
|         for other_ie in other_ies: | ||||
|             if not other_ie.working(): | ||||
|                 print_skipping(u'test depends on %sIE, marked as not WORKING' % other_ie.ie_key()) | ||||
|                 return | ||||
|  | ||||
|         params = self.parameters.copy() | ||||
|         params.update(test_case.get('params', {})) | ||||
|         params = get_params(test_case.get('params', {})) | ||||
|  | ||||
|         ydl = YoutubeDL(params) | ||||
|         ydl.add_default_info_extractors() | ||||
| @@ -95,44 +94,55 @@ def generator(test_case): | ||||
|                 finished_hook_called.add(status['filename']) | ||||
|         ydl.fd.add_progress_hook(_hook) | ||||
|  | ||||
|         def get_tc_filename(tc): | ||||
|             return tc.get('file') or ydl.prepare_filename(tc.get('info_dict', {})) | ||||
|  | ||||
|         test_cases = test_case.get('playlist', [test_case]) | ||||
|         for tc in test_cases: | ||||
|             _try_rm(tc['file']) | ||||
|             _try_rm(tc['file'] + '.part') | ||||
|             _try_rm(tc['file'] + '.info.json') | ||||
|         def try_rm_tcs_files(): | ||||
|             for tc in test_cases: | ||||
|                 tc_filename = get_tc_filename(tc) | ||||
|                 try_rm(tc_filename) | ||||
|                 try_rm(tc_filename + '.part') | ||||
|                 try_rm(tc_filename + '.info.json') | ||||
|         try_rm_tcs_files() | ||||
|         try: | ||||
|             for retry in range(1, RETRIES + 1): | ||||
|             try_num = 1 | ||||
|             while True: | ||||
|                 try: | ||||
|                     ydl.download([test_case['url']]) | ||||
|                 except (DownloadError, ExtractorError) as err: | ||||
|                     if retry == RETRIES: raise | ||||
|  | ||||
|                     # Check if the exception is not a network related one | ||||
|                     if not err.exc_info[0] in (compat_urllib_error.URLError, socket.timeout, UnavailableVideoError): | ||||
|                     if not err.exc_info[0] in (compat_urllib_error.URLError, socket.timeout, UnavailableVideoError) or (err.exc_info[0] == compat_HTTPError and err.exc_info[1].code == 503): | ||||
|                         raise | ||||
|  | ||||
|                     print('Retrying: {0} failed tries\n\n##########\n\n'.format(retry)) | ||||
|                     if try_num == RETRIES: | ||||
|                         report_warning(u'Failed due to network errors, skipping...') | ||||
|                         return | ||||
|  | ||||
|                     print('Retrying: {0} failed tries\n\n##########\n\n'.format(try_num)) | ||||
|  | ||||
|                     try_num += 1 | ||||
|                 else: | ||||
|                     break | ||||
|  | ||||
|             for tc in test_cases: | ||||
|                 tc_filename = get_tc_filename(tc) | ||||
|                 if not test_case.get('params', {}).get('skip_download', False): | ||||
|                     self.assertTrue(os.path.exists(tc['file']), msg='Missing file ' + tc['file']) | ||||
|                     self.assertTrue(tc['file'] in finished_hook_called) | ||||
|                 self.assertTrue(os.path.exists(tc['file'] + '.info.json')) | ||||
|                     self.assertTrue(os.path.exists(tc_filename), msg='Missing file ' + tc_filename) | ||||
|                     self.assertTrue(tc_filename in finished_hook_called) | ||||
|                 self.assertTrue(os.path.exists(tc_filename + '.info.json')) | ||||
|                 if 'md5' in tc: | ||||
|                     md5_for_file = _file_md5(tc['file']) | ||||
|                     md5_for_file = _file_md5(tc_filename) | ||||
|                     self.assertEqual(md5_for_file, tc['md5']) | ||||
|                 with io.open(tc['file'] + '.info.json', encoding='utf-8') as infof: | ||||
|                 with io.open(tc_filename + '.info.json', encoding='utf-8') as infof: | ||||
|                     info_dict = json.load(infof) | ||||
|                 for (info_field, expected) in tc.get('info_dict', {}).items(): | ||||
|                     if isinstance(expected, compat_str) and expected.startswith('md5:'): | ||||
|                         self.assertEqual(expected, 'md5:' + md5(info_dict.get(info_field))) | ||||
|                         got = 'md5:' + md5(info_dict.get(info_field)) | ||||
|                     else: | ||||
|                         got = info_dict.get(info_field) | ||||
|                         self.assertEqual( | ||||
|                             expected, got, | ||||
|                             u'invalid value for field %s, expected %r, got %r' % (info_field, expected, got)) | ||||
|                     self.assertEqual(expected, got, | ||||
|                         u'invalid value for field %s, expected %r, got %r' % (info_field, expected, got)) | ||||
|  | ||||
|                 # If checkable fields are missing from the test case, print the info_dict | ||||
|                 test_info_dict = dict((key, value if not isinstance(value, compat_str) or len(value) < 250 else 'md5:' + md5(value)) | ||||
| @@ -144,11 +154,11 @@ def generator(test_case): | ||||
|                 # Check for the presence of mandatory fields | ||||
|                 for key in ('id', 'url', 'title', 'ext'): | ||||
|                     self.assertTrue(key in info_dict.keys() and info_dict[key]) | ||||
|                 # Check for mandatory fields that are automatically set by YoutubeDL | ||||
|                 for key in ['webpage_url', 'extractor', 'extractor_key']: | ||||
|                     self.assertTrue(info_dict.get(key), u'Missing field: %s' % key) | ||||
|         finally: | ||||
|             for tc in test_cases: | ||||
|                 _try_rm(tc['file']) | ||||
|                 _try_rm(tc['file'] + '.part') | ||||
|                 _try_rm(tc['file'] + '.info.json') | ||||
|             try_rm_tcs_files() | ||||
|  | ||||
|     return test_template | ||||
|  | ||||
|   | ||||
							
								
								
									
										107
									
								
								test/test_playlists.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								test/test_playlists.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,107 @@ | ||||
| #!/usr/bin/env python | ||||
| # encoding: utf-8 | ||||
|  | ||||
|  | ||||
| # Allow direct execution | ||||
| import os | ||||
| import sys | ||||
| import unittest | ||||
| sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | ||||
|  | ||||
| from test.helper import FakeYDL, global_setup | ||||
| global_setup() | ||||
|  | ||||
|  | ||||
| from youtube_dl.extractor import ( | ||||
|     DailymotionPlaylistIE, | ||||
|     DailymotionUserIE, | ||||
|     VimeoChannelIE, | ||||
|     UstreamChannelIE, | ||||
|     SoundcloudSetIE, | ||||
|     SoundcloudUserIE, | ||||
|     LivestreamIE, | ||||
|     NHLVideocenterIE, | ||||
|     BambuserChannelIE, | ||||
| ) | ||||
|  | ||||
|  | ||||
| class TestPlaylists(unittest.TestCase): | ||||
|     def assertIsPlaylist(self, info): | ||||
|         """Make sure the info has '_type' set to 'playlist'""" | ||||
|         self.assertEqual(info['_type'], 'playlist') | ||||
|  | ||||
|     def test_dailymotion_playlist(self): | ||||
|         dl = FakeYDL() | ||||
|         ie = DailymotionPlaylistIE(dl) | ||||
|         result = ie.extract('http://www.dailymotion.com/playlist/xv4bw_nqtv_sport/1#video=xl8v3q') | ||||
|         self.assertIsPlaylist(result) | ||||
|         self.assertEqual(result['title'], u'SPORT') | ||||
|         self.assertTrue(len(result['entries']) > 20) | ||||
|  | ||||
|     def test_dailymotion_user(self): | ||||
|         dl = FakeYDL() | ||||
|         ie = DailymotionUserIE(dl) | ||||
|         result = ie.extract('http://www.dailymotion.com/user/generation-quoi/') | ||||
|         self.assertIsPlaylist(result) | ||||
|         self.assertEqual(result['title'], u'Génération Quoi') | ||||
|         self.assertTrue(len(result['entries']) >= 26) | ||||
|  | ||||
|     def test_vimeo_channel(self): | ||||
|         dl = FakeYDL() | ||||
|         ie = VimeoChannelIE(dl) | ||||
|         result = ie.extract('http://vimeo.com/channels/tributes') | ||||
|         self.assertIsPlaylist(result) | ||||
|         self.assertEqual(result['title'], u'Vimeo Tributes') | ||||
|         self.assertTrue(len(result['entries']) > 24) | ||||
|  | ||||
|     def test_ustream_channel(self): | ||||
|         dl = FakeYDL() | ||||
|         ie = UstreamChannelIE(dl) | ||||
|         result = ie.extract('http://www.ustream.tv/channel/young-americans-for-liberty') | ||||
|         self.assertIsPlaylist(result) | ||||
|         self.assertEqual(result['id'], u'5124905') | ||||
|         self.assertTrue(len(result['entries']) >= 11) | ||||
|  | ||||
|     def test_soundcloud_set(self): | ||||
|         dl = FakeYDL() | ||||
|         ie = SoundcloudSetIE(dl) | ||||
|         result = ie.extract('https://soundcloud.com/the-concept-band/sets/the-royal-concept-ep') | ||||
|         self.assertIsPlaylist(result) | ||||
|         self.assertEqual(result['title'], u'The Royal Concept EP') | ||||
|         self.assertTrue(len(result['entries']) >= 6) | ||||
|  | ||||
|     def test_soundcloud_user(self): | ||||
|         dl = FakeYDL() | ||||
|         ie = SoundcloudUserIE(dl) | ||||
|         result = ie.extract('https://soundcloud.com/the-concept-band') | ||||
|         self.assertIsPlaylist(result) | ||||
|         self.assertEqual(result['id'], u'9615865') | ||||
|         self.assertTrue(len(result['entries']) >= 12) | ||||
|  | ||||
|     def test_livestream_event(self): | ||||
|         dl = FakeYDL() | ||||
|         ie = LivestreamIE(dl) | ||||
|         result = ie.extract('http://new.livestream.com/tedx/cityenglish') | ||||
|         self.assertIsPlaylist(result) | ||||
|         self.assertEqual(result['title'], u'TEDCity2.0 (English)') | ||||
|         self.assertTrue(len(result['entries']) >= 4) | ||||
|  | ||||
|     def test_nhl_videocenter(self): | ||||
|         dl = FakeYDL() | ||||
|         ie = NHLVideocenterIE(dl) | ||||
|         result = ie.extract('http://video.canucks.nhl.com/videocenter/console?catid=999') | ||||
|         self.assertIsPlaylist(result) | ||||
|         self.assertEqual(result['id'], u'999') | ||||
|         self.assertEqual(result['title'], u'Highlights') | ||||
|         self.assertEqual(len(result['entries']), 12) | ||||
|  | ||||
|     def test_bambuser_channel(self): | ||||
|         dl = FakeYDL() | ||||
|         ie = BambuserChannelIE(dl) | ||||
|         result = ie.extract('http://bambuser.com/channel/pixelversity') | ||||
|         self.assertIsPlaylist(result) | ||||
|         self.assertEqual(result['title'], u'pixelversity') | ||||
|         self.assertTrue(len(result['entries']) >= 66) | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     unittest.main() | ||||
							
								
								
									
										211
									
								
								test/test_subtitles.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										211
									
								
								test/test_subtitles.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,211 @@ | ||||
| #!/usr/bin/env python | ||||
|  | ||||
| # Allow direct execution | ||||
| import os | ||||
| import sys | ||||
| import unittest | ||||
| sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | ||||
|  | ||||
| from test.helper import FakeYDL, global_setup, md5 | ||||
| global_setup() | ||||
|  | ||||
|  | ||||
| from youtube_dl.extractor import ( | ||||
|     YoutubeIE, | ||||
|     DailymotionIE, | ||||
|     TEDIE, | ||||
| ) | ||||
|  | ||||
|  | ||||
| class BaseTestSubtitles(unittest.TestCase): | ||||
|     url = None | ||||
|     IE = None | ||||
|     def setUp(self): | ||||
|         self.DL = FakeYDL() | ||||
|         self.ie = self.IE(self.DL) | ||||
|  | ||||
|     def getInfoDict(self): | ||||
|         info_dict = self.ie.extract(self.url) | ||||
|         return info_dict | ||||
|  | ||||
|     def getSubtitles(self): | ||||
|         info_dict = self.getInfoDict() | ||||
|         return info_dict['subtitles'] | ||||
|  | ||||
|  | ||||
| class TestYoutubeSubtitles(BaseTestSubtitles): | ||||
|     url = 'QRS8MkLhQmM' | ||||
|     IE = YoutubeIE | ||||
|  | ||||
|     def getSubtitles(self): | ||||
|         info_dict = self.getInfoDict() | ||||
|         return info_dict[0]['subtitles'] | ||||
|  | ||||
|     def test_youtube_no_writesubtitles(self): | ||||
|         self.DL.params['writesubtitles'] = False | ||||
|         subtitles = self.getSubtitles() | ||||
|         self.assertEqual(subtitles, None) | ||||
|  | ||||
|     def test_youtube_subtitles(self): | ||||
|         self.DL.params['writesubtitles'] = True | ||||
|         subtitles = self.getSubtitles() | ||||
|         self.assertEqual(md5(subtitles['en']), '4cd9278a35ba2305f47354ee13472260') | ||||
|  | ||||
|     def test_youtube_subtitles_lang(self): | ||||
|         self.DL.params['writesubtitles'] = True | ||||
|         self.DL.params['subtitleslangs'] = ['it'] | ||||
|         subtitles = self.getSubtitles() | ||||
|         self.assertEqual(md5(subtitles['it']), '164a51f16f260476a05b50fe4c2f161d') | ||||
|  | ||||
|     def test_youtube_allsubtitles(self): | ||||
|         self.DL.params['writesubtitles'] = True | ||||
|         self.DL.params['allsubtitles'] = True | ||||
|         subtitles = self.getSubtitles() | ||||
|         self.assertEqual(len(subtitles.keys()), 13) | ||||
|  | ||||
|     def test_youtube_subtitles_sbv_format(self): | ||||
|         self.DL.params['writesubtitles'] = True | ||||
|         self.DL.params['subtitlesformat'] = 'sbv' | ||||
|         subtitles = self.getSubtitles() | ||||
|         self.assertEqual(md5(subtitles['en']), '13aeaa0c245a8bed9a451cb643e3ad8b') | ||||
|  | ||||
|     def test_youtube_subtitles_vtt_format(self): | ||||
|         self.DL.params['writesubtitles'] = True | ||||
|         self.DL.params['subtitlesformat'] = 'vtt' | ||||
|         subtitles = self.getSubtitles() | ||||
|         self.assertEqual(md5(subtitles['en']), '356cdc577fde0c6783b9b822e7206ff7') | ||||
|  | ||||
|     def test_youtube_list_subtitles(self): | ||||
|         self.DL.expect_warning(u'Video doesn\'t have automatic captions') | ||||
|         self.DL.params['listsubtitles'] = True | ||||
|         info_dict = self.getInfoDict() | ||||
|         self.assertEqual(info_dict, None) | ||||
|  | ||||
|     def test_youtube_automatic_captions(self): | ||||
|         self.url = '8YoUxe5ncPo' | ||||
|         self.DL.params['writeautomaticsub'] = True | ||||
|         self.DL.params['subtitleslangs'] = ['it'] | ||||
|         subtitles = self.getSubtitles() | ||||
|         self.assertTrue(subtitles['it'] is not None) | ||||
|  | ||||
|     def test_youtube_nosubtitles(self): | ||||
|         self.DL.expect_warning(u'video doesn\'t have subtitles') | ||||
|         self.url = 'sAjKT8FhjI8' | ||||
|         self.DL.params['writesubtitles'] = True | ||||
|         self.DL.params['allsubtitles'] = True | ||||
|         subtitles = self.getSubtitles() | ||||
|         self.assertEqual(len(subtitles), 0) | ||||
|  | ||||
|     def test_youtube_multiple_langs(self): | ||||
|         self.url = 'QRS8MkLhQmM' | ||||
|         self.DL.params['writesubtitles'] = True | ||||
|         langs = ['it', 'fr', 'de'] | ||||
|         self.DL.params['subtitleslangs'] = langs | ||||
|         subtitles = self.getSubtitles() | ||||
|         for lang in langs: | ||||
|             self.assertTrue(subtitles.get(lang) is not None, u'Subtitles for \'%s\' not extracted' % lang) | ||||
|  | ||||
|  | ||||
| class TestDailymotionSubtitles(BaseTestSubtitles): | ||||
|     url = 'http://www.dailymotion.com/video/xczg00' | ||||
|     IE = DailymotionIE | ||||
|  | ||||
|     def test_no_writesubtitles(self): | ||||
|         subtitles = self.getSubtitles() | ||||
|         self.assertEqual(subtitles, None) | ||||
|  | ||||
|     def test_subtitles(self): | ||||
|         self.DL.params['writesubtitles'] = True | ||||
|         subtitles = self.getSubtitles() | ||||
|         self.assertEqual(md5(subtitles['en']), '976553874490cba125086bbfea3ff76f') | ||||
|  | ||||
|     def test_subtitles_lang(self): | ||||
|         self.DL.params['writesubtitles'] = True | ||||
|         self.DL.params['subtitleslangs'] = ['fr'] | ||||
|         subtitles = self.getSubtitles() | ||||
|         self.assertEqual(md5(subtitles['fr']), '594564ec7d588942e384e920e5341792') | ||||
|  | ||||
|     def test_allsubtitles(self): | ||||
|         self.DL.params['writesubtitles'] = True | ||||
|         self.DL.params['allsubtitles'] = True | ||||
|         subtitles = self.getSubtitles() | ||||
|         self.assertEqual(len(subtitles.keys()), 5) | ||||
|  | ||||
|     def test_list_subtitles(self): | ||||
|         self.DL.expect_warning(u'Automatic Captions not supported by this server') | ||||
|         self.DL.params['listsubtitles'] = True | ||||
|         info_dict = self.getInfoDict() | ||||
|         self.assertEqual(info_dict, None) | ||||
|  | ||||
|     def test_automatic_captions(self): | ||||
|         self.DL.expect_warning(u'Automatic Captions not supported by this server') | ||||
|         self.DL.params['writeautomaticsub'] = True | ||||
|         self.DL.params['subtitleslang'] = ['en'] | ||||
|         subtitles = self.getSubtitles() | ||||
|         self.assertTrue(len(subtitles.keys()) == 0) | ||||
|  | ||||
|     def test_nosubtitles(self): | ||||
|         self.DL.expect_warning(u'video doesn\'t have subtitles') | ||||
|         self.url = 'http://www.dailymotion.com/video/x12u166_le-zapping-tele-star-du-08-aout-2013_tv' | ||||
|         self.DL.params['writesubtitles'] = True | ||||
|         self.DL.params['allsubtitles'] = True | ||||
|         subtitles = self.getSubtitles() | ||||
|         self.assertEqual(len(subtitles), 0) | ||||
|  | ||||
|     def test_multiple_langs(self): | ||||
|         self.DL.params['writesubtitles'] = True | ||||
|         langs = ['es', 'fr', 'de'] | ||||
|         self.DL.params['subtitleslangs'] = langs | ||||
|         subtitles = self.getSubtitles() | ||||
|         for lang in langs: | ||||
|             self.assertTrue(subtitles.get(lang) is not None, u'Subtitles for \'%s\' not extracted' % lang) | ||||
|  | ||||
|  | ||||
| class TestTedSubtitles(BaseTestSubtitles): | ||||
|     url = 'http://www.ted.com/talks/dan_dennett_on_our_consciousness.html' | ||||
|     IE = TEDIE | ||||
|  | ||||
|     def test_no_writesubtitles(self): | ||||
|         subtitles = self.getSubtitles() | ||||
|         self.assertEqual(subtitles, None) | ||||
|  | ||||
|     def test_subtitles(self): | ||||
|         self.DL.params['writesubtitles'] = True | ||||
|         subtitles = self.getSubtitles() | ||||
|         self.assertEqual(md5(subtitles['en']), '2154f31ff9b9f89a0aa671537559c21d') | ||||
|  | ||||
|     def test_subtitles_lang(self): | ||||
|         self.DL.params['writesubtitles'] = True | ||||
|         self.DL.params['subtitleslangs'] = ['fr'] | ||||
|         subtitles = self.getSubtitles() | ||||
|         self.assertEqual(md5(subtitles['fr']), '7616cbc6df20ec2c1204083c83871cf6') | ||||
|  | ||||
|     def test_allsubtitles(self): | ||||
|         self.DL.params['writesubtitles'] = True | ||||
|         self.DL.params['allsubtitles'] = True | ||||
|         subtitles = self.getSubtitles() | ||||
|         self.assertEqual(len(subtitles.keys()), 28) | ||||
|  | ||||
|     def test_list_subtitles(self): | ||||
|         self.DL.expect_warning(u'Automatic Captions not supported by this server') | ||||
|         self.DL.params['listsubtitles'] = True | ||||
|         info_dict = self.getInfoDict() | ||||
|         self.assertEqual(info_dict, None) | ||||
|  | ||||
|     def test_automatic_captions(self): | ||||
|         self.DL.expect_warning(u'Automatic Captions not supported by this server') | ||||
|         self.DL.params['writeautomaticsub'] = True | ||||
|         self.DL.params['subtitleslang'] = ['en'] | ||||
|         subtitles = self.getSubtitles() | ||||
|         self.assertTrue(len(subtitles.keys()) == 0) | ||||
|  | ||||
|     def test_multiple_langs(self): | ||||
|         self.DL.params['writesubtitles'] = True | ||||
|         langs = ['es', 'fr', 'de'] | ||||
|         self.DL.params['subtitleslangs'] = langs | ||||
|         subtitles = self.getSubtitles() | ||||
|         for lang in langs: | ||||
|             self.assertTrue(subtitles.get(lang) is not None, u'Subtitles for \'%s\' not extracted' % lang) | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     unittest.main() | ||||
| @@ -1,23 +1,30 @@ | ||||
| #!/usr/bin/env python | ||||
|  | ||||
| # Various small unit tests | ||||
|  | ||||
| import sys | ||||
| import unittest | ||||
| import xml.etree.ElementTree | ||||
| # coding: utf-8 | ||||
|  | ||||
| # Allow direct execution | ||||
| import os | ||||
| sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | ||||
| import sys | ||||
| import unittest | ||||
| sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | ||||
|  | ||||
|  | ||||
| # Various small unit tests | ||||
| import xml.etree.ElementTree | ||||
|  | ||||
| #from youtube_dl.utils import htmlentity_transform | ||||
| from youtube_dl.utils import timeconvert | ||||
| from youtube_dl.utils import sanitize_filename | ||||
| from youtube_dl.utils import unescapeHTML | ||||
| from youtube_dl.utils import orderedSet | ||||
| from youtube_dl.utils import DateRange | ||||
| from youtube_dl.utils import unified_strdate | ||||
| from youtube_dl.utils import find_xpath_attr | ||||
| from youtube_dl.utils import ( | ||||
|     timeconvert, | ||||
|     sanitize_filename, | ||||
|     unescapeHTML, | ||||
|     orderedSet, | ||||
|     DateRange, | ||||
|     unified_strdate, | ||||
|     find_xpath_attr, | ||||
|     get_meta_content, | ||||
|     xpath_with_ns, | ||||
|     smuggle_url, | ||||
|     unsmuggle_url, | ||||
| ) | ||||
|  | ||||
| if sys.version_info < (3, 0): | ||||
|     _compat_str = lambda b: b.decode('unicode-escape') | ||||
| @@ -127,5 +134,42 @@ class TestUtil(unittest.TestCase): | ||||
|         self.assertEqual(find_xpath_attr(doc, './/node', 'x', 'a'), doc[1]) | ||||
|         self.assertEqual(find_xpath_attr(doc, './/node', 'y', 'c'), doc[2]) | ||||
|  | ||||
|     def test_meta_parser(self): | ||||
|         testhtml = u''' | ||||
|         <head> | ||||
|             <meta name="description" content="foo & bar"> | ||||
|             <meta content='Plato' name='author'/> | ||||
|         </head> | ||||
|         ''' | ||||
|         get_meta = lambda name: get_meta_content(name, testhtml) | ||||
|         self.assertEqual(get_meta('description'), u'foo & bar') | ||||
|         self.assertEqual(get_meta('author'), 'Plato') | ||||
|  | ||||
|     def test_xpath_with_ns(self): | ||||
|         testxml = u'''<root xmlns:media="http://example.com/"> | ||||
|             <media:song> | ||||
|                 <media:author>The Author</media:author> | ||||
|                 <url>http://server.com/download.mp3</url> | ||||
|             </media:song> | ||||
|         </root>''' | ||||
|         doc = xml.etree.ElementTree.fromstring(testxml) | ||||
|         find = lambda p: doc.find(xpath_with_ns(p, {'media': 'http://example.com/'})) | ||||
|         self.assertTrue(find('media:song') is not None) | ||||
|         self.assertEqual(find('media:song/media:author').text, u'The Author') | ||||
|         self.assertEqual(find('media:song/url').text, u'http://server.com/download.mp3') | ||||
|  | ||||
|     def test_smuggle_url(self): | ||||
|         data = {u"ö": u"ö", u"abc": [3]} | ||||
|         url = 'https://foo.bar/baz?x=y#a' | ||||
|         smug_url = smuggle_url(url, data) | ||||
|         unsmug_url, unsmug_data = unsmuggle_url(smug_url) | ||||
|         self.assertEqual(url, unsmug_url) | ||||
|         self.assertEqual(data, unsmug_data) | ||||
|  | ||||
|         res_url, res_data = unsmuggle_url(url) | ||||
|         self.assertEqual(res_url, url) | ||||
|         self.assertEqual(res_data, None) | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     unittest.main() | ||||
|   | ||||
							
								
								
									
										80
									
								
								test/test_write_annotations.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								test/test_write_annotations.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,80 @@ | ||||
| #!/usr/bin/env python | ||||
| # coding: utf-8 | ||||
|  | ||||
| # Allow direct execution | ||||
| import os | ||||
| import sys | ||||
| import unittest | ||||
| sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | ||||
|  | ||||
| from test.helper import get_params, global_setup, try_rm | ||||
| global_setup() | ||||
|  | ||||
|  | ||||
| import io | ||||
|  | ||||
| import xml.etree.ElementTree | ||||
|  | ||||
| import youtube_dl.YoutubeDL | ||||
| import youtube_dl.extractor | ||||
|  | ||||
|  | ||||
| class YoutubeDL(youtube_dl.YoutubeDL): | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         super(YoutubeDL, self).__init__(*args, **kwargs) | ||||
|         self.to_stderr = self.to_screen | ||||
|  | ||||
| params = get_params({ | ||||
|     'writeannotations': True, | ||||
|     'skip_download': True, | ||||
|     'writeinfojson': False, | ||||
|     'format': 'flv', | ||||
| }) | ||||
|  | ||||
|  | ||||
|  | ||||
| TEST_ID = 'gr51aVj-mLg' | ||||
| ANNOTATIONS_FILE = TEST_ID + '.flv.annotations.xml' | ||||
| EXPECTED_ANNOTATIONS = ['Speech bubble', 'Note', 'Title', 'Spotlight', 'Label'] | ||||
|  | ||||
| class TestAnnotations(unittest.TestCase): | ||||
|     def setUp(self): | ||||
|         # Clear old files | ||||
|         self.tearDown() | ||||
|  | ||||
|  | ||||
|     def test_info_json(self): | ||||
|         expected = list(EXPECTED_ANNOTATIONS) #Two annotations could have the same text. | ||||
|         ie = youtube_dl.extractor.YoutubeIE() | ||||
|         ydl = YoutubeDL(params) | ||||
|         ydl.add_info_extractor(ie) | ||||
|         ydl.download([TEST_ID]) | ||||
|         self.assertTrue(os.path.exists(ANNOTATIONS_FILE)) | ||||
|         annoxml = None | ||||
|         with io.open(ANNOTATIONS_FILE, 'r', encoding='utf-8') as annof: | ||||
|                 annoxml = xml.etree.ElementTree.parse(annof) | ||||
|         self.assertTrue(annoxml is not None, 'Failed to parse annotations XML') | ||||
|         root = annoxml.getroot() | ||||
|         self.assertEqual(root.tag, 'document') | ||||
|         annotationsTag = root.find('annotations') | ||||
|         self.assertEqual(annotationsTag.tag, 'annotations') | ||||
|         annotations = annotationsTag.findall('annotation') | ||||
|  | ||||
|         #Not all the annotations have TEXT children and the annotations are returned unsorted. | ||||
|         for a in annotations: | ||||
|                 self.assertEqual(a.tag, 'annotation') | ||||
|                 if a.get('type') == 'text': | ||||
|                         textTag = a.find('TEXT') | ||||
|                         text = textTag.text | ||||
|                         self.assertTrue(text in expected) #assertIn only added in python 2.7 | ||||
|                         #remove the first occurance, there could be more than one annotation with the same text | ||||
|                         expected.remove(text) | ||||
|         #We should have seen (and removed) all the expected annotation texts. | ||||
|         self.assertEqual(len(expected), 0, 'Not all expected annotations were found.') | ||||
|          | ||||
|  | ||||
|     def tearDown(self): | ||||
|         try_rm(ANNOTATIONS_FILE) | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     unittest.main() | ||||
| @@ -1,37 +1,34 @@ | ||||
| #!/usr/bin/env python | ||||
| # coding: utf-8 | ||||
|  | ||||
| import json | ||||
| # Allow direct execution | ||||
| import os | ||||
| import sys | ||||
| import unittest | ||||
| sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | ||||
|  | ||||
| # Allow direct execution | ||||
| sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | ||||
| from test.helper import get_params, global_setup | ||||
| global_setup() | ||||
|  | ||||
|  | ||||
| import io | ||||
| import json | ||||
|  | ||||
| import youtube_dl.YoutubeDL | ||||
| import youtube_dl.extractor | ||||
| from youtube_dl.utils import * | ||||
|  | ||||
| PARAMETERS_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "parameters.json") | ||||
|  | ||||
| # General configuration (from __init__, not very elegant...) | ||||
| jar = compat_cookiejar.CookieJar() | ||||
| cookie_processor = compat_urllib_request.HTTPCookieProcessor(jar) | ||||
| proxy_handler = compat_urllib_request.ProxyHandler() | ||||
| opener = compat_urllib_request.build_opener(proxy_handler, cookie_processor, YoutubeDLHandler()) | ||||
| compat_urllib_request.install_opener(opener) | ||||
|  | ||||
| class YoutubeDL(youtube_dl.YoutubeDL): | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         super(YoutubeDL, self).__init__(*args, **kwargs) | ||||
|         self.to_stderr = self.to_screen | ||||
|  | ||||
| with io.open(PARAMETERS_FILE, encoding='utf-8') as pf: | ||||
|     params = json.load(pf) | ||||
| params['writeinfojson'] = True | ||||
| params['skip_download'] = True | ||||
| params['writedescription'] = True | ||||
| params = get_params({ | ||||
|     'writeinfojson': True, | ||||
|     'skip_download': True, | ||||
|     'writedescription': True, | ||||
| }) | ||||
|  | ||||
|  | ||||
| TEST_ID = 'BaW_jenozKc' | ||||
| INFO_JSON_FILE = TEST_ID + '.mp4.info.json' | ||||
| @@ -42,6 +39,7 @@ This is a test video for youtube-dl. | ||||
|  | ||||
| For more information, contact phihag@phihag.de .''' | ||||
|  | ||||
|  | ||||
| class TestInfoJSON(unittest.TestCase): | ||||
|     def setUp(self): | ||||
|         # Clear old files | ||||
|   | ||||
| @@ -1,20 +1,26 @@ | ||||
| #!/usr/bin/env python | ||||
|  | ||||
| import sys | ||||
| import unittest | ||||
| import json | ||||
|  | ||||
| # Allow direct execution | ||||
| import os | ||||
| sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | ||||
| import sys | ||||
| import unittest | ||||
| sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | ||||
|  | ||||
| from youtube_dl.extractor import YoutubeUserIE, YoutubePlaylistIE, YoutubeIE, YoutubeChannelIE, YoutubeShowIE | ||||
| from youtube_dl.utils import * | ||||
| from test.helper import FakeYDL, global_setup | ||||
| global_setup() | ||||
|  | ||||
|  | ||||
| from youtube_dl.extractor import ( | ||||
|     YoutubeUserIE, | ||||
|     YoutubePlaylistIE, | ||||
|     YoutubeIE, | ||||
|     YoutubeChannelIE, | ||||
|     YoutubeShowIE, | ||||
| ) | ||||
|  | ||||
| from helper import FakeYDL | ||||
|  | ||||
| class TestYoutubeLists(unittest.TestCase): | ||||
|     def assertIsPlaylist(self,info): | ||||
|     def assertIsPlaylist(self, info): | ||||
|         """Make sure the info has '_type' set to 'playlist'""" | ||||
|         self.assertEqual(info['_type'], 'playlist') | ||||
|  | ||||
| @@ -27,6 +33,14 @@ class TestYoutubeLists(unittest.TestCase): | ||||
|         ytie_results = [YoutubeIE()._extract_id(url['url']) for url in result['entries']] | ||||
|         self.assertEqual(ytie_results, [ 'bV9L5Ht9LgY', 'FXxLjLQi3Fg', 'tU3Bgo5qJZE']) | ||||
|  | ||||
|     def test_youtube_playlist_noplaylist(self): | ||||
|         dl = FakeYDL() | ||||
|         dl.params['noplaylist'] = True | ||||
|         ie = YoutubePlaylistIE(dl) | ||||
|         result = ie.extract('https://www.youtube.com/watch?v=FXxLjLQi3Fg&list=PLwiyx1dc3P2JR9N8gQaQN_BCvlSlap7re') | ||||
|         self.assertEqual(result['_type'], 'url') | ||||
|         self.assertEqual(YoutubeIE()._extract_id(result['url']), 'FXxLjLQi3Fg') | ||||
|  | ||||
|     def test_issue_673(self): | ||||
|         dl = FakeYDL() | ||||
|         ie = YoutubePlaylistIE(dl) | ||||
| @@ -92,7 +106,7 @@ class TestYoutubeLists(unittest.TestCase): | ||||
|         dl = FakeYDL() | ||||
|         ie = YoutubeShowIE(dl) | ||||
|         result = ie.extract('http://www.youtube.com/show/airdisasters') | ||||
|         self.assertTrue(len(result) >= 4) | ||||
|         self.assertTrue(len(result) >= 3) | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     unittest.main() | ||||
|   | ||||
| @@ -1,67 +0,0 @@ | ||||
| #!/usr/bin/env python | ||||
|  | ||||
| import unittest | ||||
| import sys | ||||
|  | ||||
| # Allow direct execution | ||||
| import os | ||||
| sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | ||||
|  | ||||
| from youtube_dl.extractor.youtube import YoutubeIE | ||||
| from helper import FakeYDL | ||||
|  | ||||
| sig = YoutubeIE(FakeYDL())._decrypt_signature | ||||
|  | ||||
| class TestYoutubeSig(unittest.TestCase): | ||||
|     def test_92(self): | ||||
|         wrong = "F9F9B6E6FD47029957AB911A964CC20D95A181A5D37A2DBEFD67D403DB0E8BE4F4910053E4E8A79.0B70B.0B80B8" | ||||
|         right = "69B6E6FD47029957AB911A9F4CC20D95A181A5D3.A2DBEFD67D403DB0E8BE4F4910053E4E8A7980B7" | ||||
|         self.assertEqual(sig(wrong), right) | ||||
|  | ||||
|     def test_90(self): | ||||
|         wrong = "qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$%^&*()_-+={[]}|:;?/>.<'`" | ||||
|         right = "mrtyuioplkjhgfdsazxcvbne1234567890QWER[YUIOPLKJHGFDSAZXCVBNM!@#$%^&*()_-+={`]}|" | ||||
|         self.assertEqual(sig(wrong), right) | ||||
|  | ||||
|     def test_88(self): | ||||
|         wrong = "qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$%^&*()_-+={[]}|:;?/>.<" | ||||
|         right = "J:|}][{=+-_)(*&;%$#@>MNBVCXZASDFGH^KLPOIUYTREWQ0987654321mnbvcxzasdfghrklpoiuytej" | ||||
|         self.assertEqual(sig(wrong), right) | ||||
|  | ||||
|     def test_87(self): | ||||
|         wrong = "qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$^&*()_-+={[]}|:;?/>.<" | ||||
|         right = "tyuioplkjhgfdsazxcv<nm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$^&*()_-+={[]}|:;?/>" | ||||
|         self.assertEqual(sig(wrong), right) | ||||
|  | ||||
|     def test_86(self): | ||||
|         wrong = "qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$%^&*()_-+={[|};?/>.<" | ||||
|         right = "ertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!/#$%^&*()_-+={[|};?@" | ||||
|         self.assertEqual(sig(wrong), right) | ||||
|  | ||||
|     def test_85(self): | ||||
|         wrong = "qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$%^&*()_-+={[};?/>.<" | ||||
|         right = "ertyuiqplkjhgfdsazx$vbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#<%^&*()_-+={[};?/c" | ||||
|         self.assertEqual(sig(wrong), right) | ||||
|  | ||||
|     def test_84(self): | ||||
|         wrong = "qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$%^&*()_-+={[};?>.<" | ||||
|         right = "<.>?;}[{=+-_)(*&^%$#@!MNBVCXZASDFGHJKLPOIUYTREWe098765432rmnbvcxzasdfghjklpoiuyt1" | ||||
|         self.assertEqual(sig(wrong), right) | ||||
|  | ||||
|     def test_83(self): | ||||
|         wrong = "qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!#$%^&*()_+={[};?/>.<" | ||||
|         right = "urty8ioplkjhgfdsazxcvbqm1234567S90QWERTYUIOPLKJHGFDnAZXCVBNM!#$%^&*()_+={[};?/>.<" | ||||
|         self.assertEqual(sig(wrong), right) | ||||
|  | ||||
|     def test_82(self): | ||||
|         wrong = "qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKHGFDSAZXCVBNM!@#$%^&*(-+={[};?/>.<" | ||||
|         right = "Q>/?;}[{=+-(*<^%$#@!MNBVCXZASDFGHKLPOIUY8REWT0q&7654321mnbvcxzasdfghjklpoiuytrew9" | ||||
|         self.assertEqual(sig(wrong), right) | ||||
|  | ||||
|     def test_81(self): | ||||
|         wrong = "qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKHGFDSAZXCVBNM!@#$%^&*(-+={[};?/>." | ||||
|         right = "urty8ioplkjhgfdsazxcvbqm1234567e90QWERTYUIOPLKHGFDSnZXCVBNM!@#$%^&*(-+={[};?/>." | ||||
|         self.assertEqual(sig(wrong), right) | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     unittest.main() | ||||
							
								
								
									
										84
									
								
								test/test_youtube_signature.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								test/test_youtube_signature.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | ||||
| #!/usr/bin/env python | ||||
|  | ||||
| # Allow direct execution | ||||
| import os | ||||
| import sys | ||||
| import unittest | ||||
| sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | ||||
|  | ||||
| from test.helper import global_setup | ||||
| global_setup() | ||||
|  | ||||
|  | ||||
| import io | ||||
| import re | ||||
| import string | ||||
|  | ||||
| from youtube_dl.extractor import YoutubeIE | ||||
| from youtube_dl.utils import compat_str, compat_urlretrieve | ||||
|  | ||||
| _TESTS = [ | ||||
|     ( | ||||
|         u'https://s.ytimg.com/yts/jsbin/html5player-vflHOr_nV.js', | ||||
|         u'js', | ||||
|         86, | ||||
|         u'>=<;:/.-[+*)(\'&%$#"!ZYX0VUTSRQPONMLKJIHGFEDCBA\\yxwvutsrqponmlkjihgfedcba987654321', | ||||
|     ), | ||||
|     ( | ||||
|         u'https://s.ytimg.com/yts/jsbin/html5player-vfldJ8xgI.js', | ||||
|         u'js', | ||||
|         85, | ||||
|         u'3456789a0cdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRS[UVWXYZ!"#$%&\'()*+,-./:;<=>?@', | ||||
|     ), | ||||
|     ( | ||||
|         u'https://s.ytimg.com/yts/swfbin/watch_as3-vflg5GhxU.swf', | ||||
|         u'swf', | ||||
|         82, | ||||
|         u':/.-,+*)=\'&%$#"!ZYX0VUTSRQPONMLKJIHGFEDCBAzyxw>utsrqponmlkjihgfedcba987654321' | ||||
|     ), | ||||
| ] | ||||
|  | ||||
|  | ||||
| class TestSignature(unittest.TestCase): | ||||
|     def setUp(self): | ||||
|         TEST_DIR = os.path.dirname(os.path.abspath(__file__)) | ||||
|         self.TESTDATA_DIR = os.path.join(TEST_DIR, 'testdata') | ||||
|         if not os.path.exists(self.TESTDATA_DIR): | ||||
|             os.mkdir(self.TESTDATA_DIR) | ||||
|  | ||||
|  | ||||
| def make_tfunc(url, stype, sig_length, expected_sig): | ||||
|     basename = url.rpartition('/')[2] | ||||
|     m = re.match(r'.*-([a-zA-Z0-9_-]+)\.[a-z]+$', basename) | ||||
|     assert m, '%r should follow URL format' % basename | ||||
|     test_id = m.group(1) | ||||
|  | ||||
|     def test_func(self): | ||||
|         fn = os.path.join(self.TESTDATA_DIR, basename) | ||||
|  | ||||
|         if not os.path.exists(fn): | ||||
|             compat_urlretrieve(url, fn) | ||||
|  | ||||
|         ie = YoutubeIE() | ||||
|         if stype == 'js': | ||||
|             with io.open(fn, encoding='utf-8') as testf: | ||||
|                 jscode = testf.read() | ||||
|             func = ie._parse_sig_js(jscode) | ||||
|         else: | ||||
|             assert stype == 'swf' | ||||
|             with open(fn, 'rb') as testf: | ||||
|                 swfcode = testf.read() | ||||
|             func = ie._parse_sig_swf(swfcode) | ||||
|         src_sig = compat_str(string.printable[:sig_length]) | ||||
|         got_sig = func(src_sig) | ||||
|         self.assertEqual(got_sig, expected_sig) | ||||
|  | ||||
|     test_func.__name__ = str('test_signature_' + stype + '_' + test_id) | ||||
|     setattr(TestSignature, test_func.__name__, test_func) | ||||
|  | ||||
| for test_spec in _TESTS: | ||||
|     make_tfunc(*test_spec) | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     unittest.main() | ||||
| @@ -1,95 +0,0 @@ | ||||
| #!/usr/bin/env python | ||||
|  | ||||
| import sys | ||||
| import unittest | ||||
| import json | ||||
| import io | ||||
| import hashlib | ||||
|  | ||||
| # Allow direct execution | ||||
| import os | ||||
| sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | ||||
|  | ||||
| from youtube_dl.extractor import YoutubeIE | ||||
| from youtube_dl.utils import * | ||||
| from helper import FakeYDL | ||||
|  | ||||
| md5 = lambda s: hashlib.md5(s.encode('utf-8')).hexdigest() | ||||
|  | ||||
| class TestYoutubeSubtitles(unittest.TestCase): | ||||
|     def setUp(self): | ||||
|         DL = FakeYDL() | ||||
|         DL.params['allsubtitles'] = False | ||||
|         DL.params['writesubtitles'] = False | ||||
|         DL.params['subtitlesformat'] = 'srt' | ||||
|         DL.params['listsubtitles'] = False | ||||
|     def test_youtube_no_subtitles(self): | ||||
|         DL = FakeYDL() | ||||
|         DL.params['writesubtitles'] = False | ||||
|         IE = YoutubeIE(DL) | ||||
|         info_dict = IE.extract('QRS8MkLhQmM') | ||||
|         subtitles = info_dict[0]['subtitles'] | ||||
|         self.assertEqual(subtitles, None) | ||||
|     def test_youtube_subtitles(self): | ||||
|         DL = FakeYDL() | ||||
|         DL.params['writesubtitles'] = True | ||||
|         IE = YoutubeIE(DL) | ||||
|         info_dict = IE.extract('QRS8MkLhQmM') | ||||
|         sub = info_dict[0]['subtitles'][0] | ||||
|         self.assertEqual(md5(sub[2]), '4cd9278a35ba2305f47354ee13472260') | ||||
|     def test_youtube_subtitles_it(self): | ||||
|         DL = FakeYDL() | ||||
|         DL.params['writesubtitles'] = True | ||||
|         DL.params['subtitleslang'] = 'it' | ||||
|         IE = YoutubeIE(DL) | ||||
|         info_dict = IE.extract('QRS8MkLhQmM') | ||||
|         sub = info_dict[0]['subtitles'][0] | ||||
|         self.assertEqual(md5(sub[2]), '164a51f16f260476a05b50fe4c2f161d') | ||||
|     def test_youtube_onlysubtitles(self): | ||||
|         DL = FakeYDL() | ||||
|         DL.params['writesubtitles'] = True | ||||
|         DL.params['onlysubtitles'] = True | ||||
|         IE = YoutubeIE(DL) | ||||
|         info_dict = IE.extract('QRS8MkLhQmM') | ||||
|         sub = info_dict[0]['subtitles'][0] | ||||
|         self.assertEqual(md5(sub[2]), '4cd9278a35ba2305f47354ee13472260') | ||||
|     def test_youtube_allsubtitles(self): | ||||
|         DL = FakeYDL() | ||||
|         DL.params['allsubtitles'] = True | ||||
|         IE = YoutubeIE(DL) | ||||
|         info_dict = IE.extract('QRS8MkLhQmM') | ||||
|         subtitles = info_dict[0]['subtitles'] | ||||
|         self.assertEqual(len(subtitles), 13) | ||||
|     def test_youtube_subtitles_sbv_format(self): | ||||
|         DL = FakeYDL() | ||||
|         DL.params['writesubtitles'] = True | ||||
|         DL.params['subtitlesformat'] = 'sbv' | ||||
|         IE = YoutubeIE(DL) | ||||
|         info_dict = IE.extract('QRS8MkLhQmM') | ||||
|         sub = info_dict[0]['subtitles'][0] | ||||
|         self.assertEqual(md5(sub[2]), '13aeaa0c245a8bed9a451cb643e3ad8b') | ||||
|     def test_youtube_subtitles_vtt_format(self): | ||||
|         DL = FakeYDL() | ||||
|         DL.params['writesubtitles'] = True | ||||
|         DL.params['subtitlesformat'] = 'vtt' | ||||
|         IE = YoutubeIE(DL) | ||||
|         info_dict = IE.extract('QRS8MkLhQmM') | ||||
|         sub = info_dict[0]['subtitles'][0] | ||||
|         self.assertEqual(md5(sub[2]), '356cdc577fde0c6783b9b822e7206ff7') | ||||
|     def test_youtube_list_subtitles(self): | ||||
|         DL = FakeYDL() | ||||
|         DL.params['listsubtitles'] = True | ||||
|         IE = YoutubeIE(DL) | ||||
|         info_dict = IE.extract('QRS8MkLhQmM') | ||||
|         self.assertEqual(info_dict, None) | ||||
|     def test_youtube_automatic_captions(self): | ||||
|         DL = FakeYDL() | ||||
|         DL.params['writeautomaticsub'] = True | ||||
|         DL.params['subtitleslang'] = 'it' | ||||
|         IE = YoutubeIE(DL) | ||||
|         info_dict = IE.extract('8YoUxe5ncPo') | ||||
|         sub = info_dict[0]['subtitles'][0] | ||||
|         self.assertTrue(sub[2] is not None) | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     unittest.main() | ||||
							
								
								
									
										8
									
								
								tox.ini
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								tox.ini
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| [tox] | ||||
| envlist = py26,py27,py33 | ||||
| [testenv] | ||||
| deps = | ||||
|    nose | ||||
|    coverage | ||||
| commands = nosetests --verbose {posargs:test}  # --with-coverage --cover-package=youtube_dl --cover-html | ||||
|                                                # test.test_download:TestDownload.test_NowVideo | ||||
| @@ -4,12 +4,16 @@ import re | ||||
| import subprocess | ||||
| import sys | ||||
| import time | ||||
| import traceback | ||||
|  | ||||
| if os.name == 'nt': | ||||
|     import ctypes | ||||
|  | ||||
| from .utils import * | ||||
| from .utils import ( | ||||
|     compat_urllib_error, | ||||
|     compat_urllib_request, | ||||
|     ContentTooShortError, | ||||
|     determine_ext, | ||||
|     encodeFilename, | ||||
|     sanitize_open, | ||||
|     timeconvert, | ||||
| ) | ||||
|  | ||||
|  | ||||
| class FileDownloader(object): | ||||
| @@ -63,32 +67,57 @@ class FileDownloader(object): | ||||
|         converted = float(bytes) / float(1024 ** exponent) | ||||
|         return '%.2f%s' % (converted, suffix) | ||||
|  | ||||
|     @staticmethod | ||||
|     def format_seconds(seconds): | ||||
|         (mins, secs) = divmod(seconds, 60) | ||||
|         (hours, mins) = divmod(mins, 60) | ||||
|         if hours > 99: | ||||
|             return '--:--:--' | ||||
|         if hours == 0: | ||||
|             return '%02d:%02d' % (mins, secs) | ||||
|         else: | ||||
|             return '%02d:%02d:%02d' % (hours, mins, secs) | ||||
|  | ||||
|     @staticmethod | ||||
|     def calc_percent(byte_counter, data_len): | ||||
|         if data_len is None: | ||||
|             return None | ||||
|         return float(byte_counter) / float(data_len) * 100.0 | ||||
|  | ||||
|     @staticmethod | ||||
|     def format_percent(percent): | ||||
|         if percent is None: | ||||
|             return '---.-%' | ||||
|         return '%6s' % ('%3.1f%%' % (float(byte_counter) / float(data_len) * 100.0)) | ||||
|         return '%6s' % ('%3.1f%%' % percent) | ||||
|  | ||||
|     @staticmethod | ||||
|     def calc_eta(start, now, total, current): | ||||
|         if total is None: | ||||
|             return '--:--' | ||||
|             return None | ||||
|         dif = now - start | ||||
|         if current == 0 or dif < 0.001: # One millisecond | ||||
|             return '--:--' | ||||
|             return None | ||||
|         rate = float(current) / dif | ||||
|         eta = int((float(total) - float(current)) / rate) | ||||
|         (eta_mins, eta_secs) = divmod(eta, 60) | ||||
|         if eta_mins > 99: | ||||
|         return int((float(total) - float(current)) / rate) | ||||
|  | ||||
|     @staticmethod | ||||
|     def format_eta(eta): | ||||
|         if eta is None: | ||||
|             return '--:--' | ||||
|         return '%02d:%02d' % (eta_mins, eta_secs) | ||||
|         return FileDownloader.format_seconds(eta) | ||||
|  | ||||
|     @staticmethod | ||||
|     def calc_speed(start, now, bytes): | ||||
|         dif = now - start | ||||
|         if bytes == 0 or dif < 0.001: # One millisecond | ||||
|             return None | ||||
|         return float(bytes) / dif | ||||
|  | ||||
|     @staticmethod | ||||
|     def format_speed(speed): | ||||
|         if speed is None: | ||||
|             return '%10s' % '---b/s' | ||||
|         return '%10s' % ('%s/s' % FileDownloader.format_bytes(float(bytes) / dif)) | ||||
|         return '%10s' % ('%s/s' % FileDownloader.format_bytes(speed)) | ||||
|  | ||||
|     @staticmethod | ||||
|     def best_block_size(elapsed_time, bytes): | ||||
| @@ -119,16 +148,8 @@ class FileDownloader(object): | ||||
|     def to_stderr(self, message): | ||||
|         self.ydl.to_screen(message) | ||||
|  | ||||
|     def to_cons_title(self, message): | ||||
|         """Set console/terminal window title to message.""" | ||||
|         if not self.params.get('consoletitle', False): | ||||
|             return | ||||
|         if os.name == 'nt' and ctypes.windll.kernel32.GetConsoleWindow(): | ||||
|             # c_wchar_p() might not be necessary if `message` is | ||||
|             # already of type unicode() | ||||
|             ctypes.windll.kernel32.SetConsoleTitleW(ctypes.c_wchar_p(message)) | ||||
|         elif 'TERM' in os.environ: | ||||
|             self.to_screen('\033]0;%s\007' % message, skip_eol=True) | ||||
|     def to_console_title(self, message): | ||||
|         self.ydl.to_console_title(message) | ||||
|  | ||||
|     def trouble(self, *args, **kargs): | ||||
|         self.ydl.trouble(*args, **kargs) | ||||
| @@ -169,7 +190,7 @@ class FileDownloader(object): | ||||
|             if old_filename == new_filename: | ||||
|                 return | ||||
|             os.rename(encodeFilename(old_filename), encodeFilename(new_filename)) | ||||
|         except (IOError, OSError) as err: | ||||
|         except (IOError, OSError): | ||||
|             self.report_error(u'unable to rename file') | ||||
|  | ||||
|     def try_utime(self, filename, last_modified_hdr): | ||||
| @@ -197,18 +218,27 @@ class FileDownloader(object): | ||||
|         """Report destination filename.""" | ||||
|         self.to_screen(u'[download] Destination: ' + filename) | ||||
|  | ||||
|     def report_progress(self, percent_str, data_len_str, speed_str, eta_str): | ||||
|     def report_progress(self, percent, data_len_str, speed, eta): | ||||
|         """Report download progress.""" | ||||
|         if self.params.get('noprogress', False): | ||||
|             return | ||||
|         clear_line = (u'\x1b[K' if sys.stderr.isatty() and os.name != 'nt' else u'') | ||||
|         if eta is not None: | ||||
|             eta_str = self.format_eta(eta) | ||||
|         else: | ||||
|             eta_str = 'Unknown ETA' | ||||
|         if percent is not None: | ||||
|             percent_str = self.format_percent(percent) | ||||
|         else: | ||||
|             percent_str = 'Unknown %' | ||||
|         speed_str = self.format_speed(speed) | ||||
|         if self.params.get('progress_with_newline', False): | ||||
|             self.to_screen(u'[download] %s of %s at %s ETA %s' % | ||||
|                 (percent_str, data_len_str, speed_str, eta_str)) | ||||
|         else: | ||||
|             self.to_screen(u'\r%s[download] %s of %s at %s ETA %s' % | ||||
|                 (clear_line, percent_str, data_len_str, speed_str, eta_str), skip_eol=True) | ||||
|         self.to_cons_title(u'youtube-dl - %s of %s at %s ETA %s' % | ||||
|         self.to_console_title(u'youtube-dl - %s of %s at %s ETA %s' % | ||||
|                 (percent_str.strip(), data_len_str.strip(), speed_str.strip(), eta_str.strip())) | ||||
|  | ||||
|     def report_resuming_byte(self, resume_len): | ||||
| @@ -223,23 +253,26 @@ class FileDownloader(object): | ||||
|         """Report file has already been fully downloaded.""" | ||||
|         try: | ||||
|             self.to_screen(u'[download] %s has already been downloaded' % file_name) | ||||
|         except (UnicodeEncodeError) as err: | ||||
|         except UnicodeEncodeError: | ||||
|             self.to_screen(u'[download] The file has already been downloaded') | ||||
|  | ||||
|     def report_unable_to_resume(self): | ||||
|         """Report it was impossible to resume download.""" | ||||
|         self.to_screen(u'[download] Unable to resume') | ||||
|  | ||||
|     def report_finish(self): | ||||
|     def report_finish(self, data_len_str, tot_time): | ||||
|         """Report download finished.""" | ||||
|         if self.params.get('noprogress', False): | ||||
|             self.to_screen(u'[download] Download completed') | ||||
|         else: | ||||
|             self.to_screen(u'') | ||||
|             clear_line = (u'\x1b[K' if sys.stderr.isatty() and os.name != 'nt' else u'') | ||||
|             self.to_screen(u'\r%s[download] 100%% of %s in %s' % | ||||
|                 (clear_line, data_len_str, self.format_seconds(tot_time))) | ||||
|  | ||||
|     def _download_with_rtmpdump(self, filename, url, player_url, page_url, play_path, tc_url): | ||||
|     def _download_with_rtmpdump(self, filename, url, player_url, page_url, play_path, tc_url, live): | ||||
|         self.report_destination(filename) | ||||
|         tmpfilename = self.temp_name(filename) | ||||
|         test = self.params.get('test', False) | ||||
|  | ||||
|         # Check for rtmpdump first | ||||
|         try: | ||||
| @@ -261,6 +294,10 @@ class FileDownloader(object): | ||||
|             basic_args += ['--playpath', play_path] | ||||
|         if tc_url is not None: | ||||
|             basic_args += ['--tcUrl', url] | ||||
|         if test: | ||||
|             basic_args += ['--stop', '1'] | ||||
|         if live: | ||||
|             basic_args += ['--live'] | ||||
|         args = basic_args + [[], ['--resume', '--skip', '1']][self.params.get('continuedl', False)] | ||||
|         if self.params.get('verbose', False): | ||||
|             try: | ||||
| @@ -270,7 +307,7 @@ class FileDownloader(object): | ||||
|                 shell_quote = repr | ||||
|             self.to_screen(u'[debug] rtmpdump command line: ' + shell_quote(args)) | ||||
|         retval = subprocess.call(args) | ||||
|         while retval == 2 or retval == 1: | ||||
|         while (retval == 2 or retval == 1) and not test: | ||||
|             prevsize = os.path.getsize(encodeFilename(tmpfilename)) | ||||
|             self.to_screen(u'\r[rtmpdump] %s bytes' % prevsize, skip_eol=True) | ||||
|             time.sleep(5.0) # This seems to be needed | ||||
| @@ -283,7 +320,7 @@ class FileDownloader(object): | ||||
|                 self.to_screen(u'\r[rtmpdump] Could not download the whole video. This can happen for some advertisements.') | ||||
|                 retval = 0 | ||||
|                 break | ||||
|         if retval == 0: | ||||
|         if retval == 0 or (test and retval == 2): | ||||
|             fsize = os.path.getsize(encodeFilename(tmpfilename)) | ||||
|             self.to_screen(u'\r[rtmpdump] %s bytes' % fsize) | ||||
|             self.try_rename(tmpfilename, filename) | ||||
| @@ -329,6 +366,40 @@ class FileDownloader(object): | ||||
|             self.report_error(u'mplayer exited with code %d' % retval) | ||||
|             return False | ||||
|  | ||||
|     def _download_m3u8_with_ffmpeg(self, filename, url): | ||||
|         self.report_destination(filename) | ||||
|         tmpfilename = self.temp_name(filename) | ||||
|  | ||||
|         args = ['-y', '-i', url, '-f', 'mp4', '-c', 'copy', | ||||
|             '-bsf:a', 'aac_adtstoasc', tmpfilename] | ||||
|  | ||||
|         for program in ['avconv', 'ffmpeg']: | ||||
|             try: | ||||
|                 subprocess.call([program, '-version'], stdout=(open(os.path.devnull, 'w')), stderr=subprocess.STDOUT) | ||||
|                 break | ||||
|             except (OSError, IOError): | ||||
|                 pass | ||||
|         else: | ||||
|             self.report_error(u'm3u8 download detected but ffmpeg or avconv could not be found') | ||||
|         cmd = [program] + args | ||||
|  | ||||
|         retval = subprocess.call(cmd) | ||||
|         if retval == 0: | ||||
|             fsize = os.path.getsize(encodeFilename(tmpfilename)) | ||||
|             self.to_screen(u'\r[%s] %s bytes' % (args[0], fsize)) | ||||
|             self.try_rename(tmpfilename, filename) | ||||
|             self._hook_progress({ | ||||
|                 'downloaded_bytes': fsize, | ||||
|                 'total_bytes': fsize, | ||||
|                 'filename': filename, | ||||
|                 'status': 'finished', | ||||
|             }) | ||||
|             return True | ||||
|         else: | ||||
|             self.to_stderr(u"\n") | ||||
|             self.report_error(u'ffmpeg exited with code %d' % retval) | ||||
|             return False | ||||
|  | ||||
|  | ||||
|     def _do_download(self, filename, info_dict): | ||||
|         url = info_dict['url'] | ||||
| @@ -339,6 +410,7 @@ class FileDownloader(object): | ||||
|             self._hook_progress({ | ||||
|                 'filename': filename, | ||||
|                 'status': 'finished', | ||||
|                 'total_bytes': os.path.getsize(encodeFilename(filename)), | ||||
|             }) | ||||
|             return True | ||||
|  | ||||
| @@ -348,12 +420,17 @@ class FileDownloader(object): | ||||
|                                                 info_dict.get('player_url', None), | ||||
|                                                 info_dict.get('page_url', None), | ||||
|                                                 info_dict.get('play_path', None), | ||||
|                                                 info_dict.get('tc_url', None)) | ||||
|                                                 info_dict.get('tc_url', None), | ||||
|                                                 info_dict.get('rtmp_live', False)) | ||||
|  | ||||
|         # Attempt to download using mplayer | ||||
|         if url.startswith('mms') or url.startswith('rtsp'): | ||||
|             return self._download_with_mplayer(filename, url) | ||||
|  | ||||
|         # m3u8 manifest are downloaded with ffmpeg | ||||
|         if determine_ext(url) == u'm3u8': | ||||
|             return self._download_m3u8_with_ffmpeg(filename, url) | ||||
|  | ||||
|         tmpfilename = self.temp_name(filename) | ||||
|         stream = None | ||||
|  | ||||
| @@ -481,13 +558,13 @@ class FileDownloader(object): | ||||
|                 block_size = self.best_block_size(after - before, len(data_block)) | ||||
|  | ||||
|             # Progress message | ||||
|             speed_str = self.calc_speed(start, time.time(), byte_counter - resume_len) | ||||
|             speed = self.calc_speed(start, time.time(), byte_counter - resume_len) | ||||
|             if data_len is None: | ||||
|                 self.report_progress('Unknown %', data_len_str, speed_str, 'Unknown ETA') | ||||
|                 eta = percent = None | ||||
|             else: | ||||
|                 percent_str = self.calc_percent(byte_counter, data_len) | ||||
|                 eta_str = self.calc_eta(start, time.time(), data_len - resume_len, byte_counter - resume_len) | ||||
|                 self.report_progress(percent_str, data_len_str, speed_str, eta_str) | ||||
|                 percent = self.calc_percent(byte_counter, data_len) | ||||
|                 eta = self.calc_eta(start, time.time(), data_len - resume_len, byte_counter - resume_len) | ||||
|             self.report_progress(percent, data_len_str, speed, eta) | ||||
|  | ||||
|             self._hook_progress({ | ||||
|                 'downloaded_bytes': byte_counter, | ||||
| @@ -495,6 +572,8 @@ class FileDownloader(object): | ||||
|                 'tmpfilename': tmpfilename, | ||||
|                 'filename': filename, | ||||
|                 'status': 'downloading', | ||||
|                 'eta': eta, | ||||
|                 'speed': speed, | ||||
|             }) | ||||
|  | ||||
|             # Apply rate limit | ||||
| @@ -505,7 +584,7 @@ class FileDownloader(object): | ||||
|             self.report_error(u'Did not get any data blocks') | ||||
|             return False | ||||
|         stream.close() | ||||
|         self.report_finish() | ||||
|         self.report_finish(data_len_str, (time.time() - start)) | ||||
|         if data_len is not None and byte_counter != data_len: | ||||
|             raise ContentTooShortError(byte_counter, int(data_len)) | ||||
|         self.try_rename(tmpfilename, filename) | ||||
| @@ -537,6 +616,8 @@ class FileDownloader(object): | ||||
|         * downloaded_bytes: Bytes on disks | ||||
|         * total_bytes: Total bytes, None if unknown | ||||
|         * tmpfilename: The filename we're currently writing to | ||||
|         * eta: The estimated time in seconds, None if unknown | ||||
|         * speed: The download speed in bytes/second, None if unknown | ||||
|  | ||||
|         Hooks are guaranteed to be called at least once (with status "finished") | ||||
|         if the download is successful. | ||||
|   | ||||
| @@ -3,7 +3,14 @@ import subprocess | ||||
| import sys | ||||
| import time | ||||
|  | ||||
| from .utils import * | ||||
|  | ||||
| from .utils import ( | ||||
|     compat_subprocess_get_DEVNULL, | ||||
|     encodeFilename, | ||||
|     PostProcessingError, | ||||
|     shell_quote, | ||||
|     subtitles_filename, | ||||
| ) | ||||
|  | ||||
|  | ||||
| class PostProcessor(object): | ||||
| @@ -71,12 +78,19 @@ class FFmpegPostProcessor(PostProcessor): | ||||
|         programs = ['avprobe', 'avconv', 'ffmpeg', 'ffprobe'] | ||||
|         return dict((program, executable(program)) for program in programs) | ||||
|  | ||||
|     def run_ffmpeg(self, path, out_path, opts): | ||||
|     def run_ffmpeg_multiple_files(self, input_paths, out_path, opts): | ||||
|         if not self._exes['ffmpeg'] and not self._exes['avconv']: | ||||
|             raise FFmpegPostProcessorError(u'ffmpeg or avconv not found. Please install one.') | ||||
|         cmd = ([self._exes['avconv'] or self._exes['ffmpeg'], '-y', '-i', encodeFilename(path)] | ||||
|  | ||||
|         files_cmd = [] | ||||
|         for path in input_paths: | ||||
|             files_cmd.extend(['-i', encodeFilename(path)]) | ||||
|         cmd = ([self._exes['avconv'] or self._exes['ffmpeg'], '-y'] + files_cmd | ||||
|                + opts + | ||||
|                [encodeFilename(self._ffmpeg_filename_argument(out_path))]) | ||||
|  | ||||
|         if self._downloader.params.get('verbose', False): | ||||
|             self._downloader.to_screen(u'[debug] ffmpeg command line: %s' % shell_quote(cmd)) | ||||
|         p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) | ||||
|         stdout,stderr = p.communicate() | ||||
|         if p.returncode != 0: | ||||
| @@ -84,6 +98,9 @@ class FFmpegPostProcessor(PostProcessor): | ||||
|             msg = stderr.strip().split('\n')[-1] | ||||
|             raise FFmpegPostProcessorError(msg) | ||||
|  | ||||
|     def run_ffmpeg(self, path, out_path, opts): | ||||
|         self.run_ffmpeg_multiple_files([path], out_path, opts) | ||||
|  | ||||
|     def _ffmpeg_filename_argument(self, fn): | ||||
|         # ffmpeg broke --, see https://ffmpeg.org/trac/ffmpeg/ticket/2127 for details | ||||
|         if fn.startswith(u'-'): | ||||
| @@ -100,7 +117,8 @@ class FFmpegExtractAudioPP(FFmpegPostProcessor): | ||||
|         self._nopostoverwrites = nopostoverwrites | ||||
|  | ||||
|     def get_audio_codec(self, path): | ||||
|         if not self._exes['ffprobe'] and not self._exes['avprobe']: return None | ||||
|         if not self._exes['ffprobe'] and not self._exes['avprobe']: | ||||
|             raise PostProcessingError(u'ffprobe or avprobe not found. Please install one.') | ||||
|         try: | ||||
|             cmd = [self._exes['avprobe'] or self._exes['ffprobe'], '-show_streams', encodeFilename(self._ffmpeg_filename_argument(path))] | ||||
|             handle = subprocess.Popen(cmd, stderr=compat_subprocess_get_DEVNULL(), stdout=subprocess.PIPE) | ||||
| @@ -128,7 +146,7 @@ class FFmpegExtractAudioPP(FFmpegPostProcessor): | ||||
|         try: | ||||
|             FFmpegPostProcessor.run_ffmpeg(self, path, out_path, opts) | ||||
|         except FFmpegPostProcessorError as err: | ||||
|             raise AudioConversionError(err.message) | ||||
|             raise AudioConversionError(err.msg) | ||||
|  | ||||
|     def run(self, information): | ||||
|         path = information['filepath'] | ||||
| @@ -168,7 +186,8 @@ class FFmpegExtractAudioPP(FFmpegPostProcessor): | ||||
|             extension = self._preferredcodec | ||||
|             more_opts = [] | ||||
|             if self._preferredquality is not None: | ||||
|                 if int(self._preferredquality) < 10: | ||||
|                 # The opus codec doesn't support the -aq option | ||||
|                 if int(self._preferredquality) < 10 and extension != 'opus': | ||||
|                     more_opts += [self._exes['avconv'] and '-q:a' or '-aq', self._preferredquality] | ||||
|                 else: | ||||
|                     more_opts += [self._exes['avconv'] and '-b:a' or '-ab', self._preferredquality + 'k'] | ||||
| @@ -198,7 +217,7 @@ class FFmpegExtractAudioPP(FFmpegPostProcessor): | ||||
|         except: | ||||
|             etype,e,tb = sys.exc_info() | ||||
|             if isinstance(e, AudioConversionError): | ||||
|                 msg = u'audio conversion failed: ' + e.message | ||||
|                 msg = u'audio conversion failed: ' + e.msg | ||||
|             else: | ||||
|                 msg = u'error running ' + (self._exes['avconv'] and 'avconv' or 'ffmpeg') | ||||
|             raise PostProcessingError(msg) | ||||
| @@ -208,7 +227,7 @@ class FFmpegExtractAudioPP(FFmpegPostProcessor): | ||||
|             try: | ||||
|                 os.utime(encodeFilename(new_path), (time.time(), information['filetime'])) | ||||
|             except: | ||||
|                 self._downloader.to_stderr(u'WARNING: Cannot update utime of audio file') | ||||
|                 self._downloader.report_warning(u'Cannot update utime of audio file') | ||||
|  | ||||
|         information['filepath'] = new_path | ||||
|         return self._nopostoverwrites,information | ||||
| @@ -231,3 +250,262 @@ class FFmpegVideoConvertor(FFmpegPostProcessor): | ||||
|         information['format'] = self._preferedformat | ||||
|         information['ext'] = self._preferedformat | ||||
|         return False,information | ||||
|  | ||||
|  | ||||
| class FFmpegEmbedSubtitlePP(FFmpegPostProcessor): | ||||
|     # See http://www.loc.gov/standards/iso639-2/ISO-639-2_utf-8.txt | ||||
|     _lang_map = { | ||||
|         'aa': 'aar', | ||||
|         'ab': 'abk', | ||||
|         'ae': 'ave', | ||||
|         'af': 'afr', | ||||
|         'ak': 'aka', | ||||
|         'am': 'amh', | ||||
|         'an': 'arg', | ||||
|         'ar': 'ara', | ||||
|         'as': 'asm', | ||||
|         'av': 'ava', | ||||
|         'ay': 'aym', | ||||
|         'az': 'aze', | ||||
|         'ba': 'bak', | ||||
|         'be': 'bel', | ||||
|         'bg': 'bul', | ||||
|         'bh': 'bih', | ||||
|         'bi': 'bis', | ||||
|         'bm': 'bam', | ||||
|         'bn': 'ben', | ||||
|         'bo': 'bod', | ||||
|         'br': 'bre', | ||||
|         'bs': 'bos', | ||||
|         'ca': 'cat', | ||||
|         'ce': 'che', | ||||
|         'ch': 'cha', | ||||
|         'co': 'cos', | ||||
|         'cr': 'cre', | ||||
|         'cs': 'ces', | ||||
|         'cu': 'chu', | ||||
|         'cv': 'chv', | ||||
|         'cy': 'cym', | ||||
|         'da': 'dan', | ||||
|         'de': 'deu', | ||||
|         'dv': 'div', | ||||
|         'dz': 'dzo', | ||||
|         'ee': 'ewe', | ||||
|         'el': 'ell', | ||||
|         'en': 'eng', | ||||
|         'eo': 'epo', | ||||
|         'es': 'spa', | ||||
|         'et': 'est', | ||||
|         'eu': 'eus', | ||||
|         'fa': 'fas', | ||||
|         'ff': 'ful', | ||||
|         'fi': 'fin', | ||||
|         'fj': 'fij', | ||||
|         'fo': 'fao', | ||||
|         'fr': 'fra', | ||||
|         'fy': 'fry', | ||||
|         'ga': 'gle', | ||||
|         'gd': 'gla', | ||||
|         'gl': 'glg', | ||||
|         'gn': 'grn', | ||||
|         'gu': 'guj', | ||||
|         'gv': 'glv', | ||||
|         'ha': 'hau', | ||||
|         'he': 'heb', | ||||
|         'hi': 'hin', | ||||
|         'ho': 'hmo', | ||||
|         'hr': 'hrv', | ||||
|         'ht': 'hat', | ||||
|         'hu': 'hun', | ||||
|         'hy': 'hye', | ||||
|         'hz': 'her', | ||||
|         'ia': 'ina', | ||||
|         'id': 'ind', | ||||
|         'ie': 'ile', | ||||
|         'ig': 'ibo', | ||||
|         'ii': 'iii', | ||||
|         'ik': 'ipk', | ||||
|         'io': 'ido', | ||||
|         'is': 'isl', | ||||
|         'it': 'ita', | ||||
|         'iu': 'iku', | ||||
|         'ja': 'jpn', | ||||
|         'jv': 'jav', | ||||
|         'ka': 'kat', | ||||
|         'kg': 'kon', | ||||
|         'ki': 'kik', | ||||
|         'kj': 'kua', | ||||
|         'kk': 'kaz', | ||||
|         'kl': 'kal', | ||||
|         'km': 'khm', | ||||
|         'kn': 'kan', | ||||
|         'ko': 'kor', | ||||
|         'kr': 'kau', | ||||
|         'ks': 'kas', | ||||
|         'ku': 'kur', | ||||
|         'kv': 'kom', | ||||
|         'kw': 'cor', | ||||
|         'ky': 'kir', | ||||
|         'la': 'lat', | ||||
|         'lb': 'ltz', | ||||
|         'lg': 'lug', | ||||
|         'li': 'lim', | ||||
|         'ln': 'lin', | ||||
|         'lo': 'lao', | ||||
|         'lt': 'lit', | ||||
|         'lu': 'lub', | ||||
|         'lv': 'lav', | ||||
|         'mg': 'mlg', | ||||
|         'mh': 'mah', | ||||
|         'mi': 'mri', | ||||
|         'mk': 'mkd', | ||||
|         'ml': 'mal', | ||||
|         'mn': 'mon', | ||||
|         'mr': 'mar', | ||||
|         'ms': 'msa', | ||||
|         'mt': 'mlt', | ||||
|         'my': 'mya', | ||||
|         'na': 'nau', | ||||
|         'nb': 'nob', | ||||
|         'nd': 'nde', | ||||
|         'ne': 'nep', | ||||
|         'ng': 'ndo', | ||||
|         'nl': 'nld', | ||||
|         'nn': 'nno', | ||||
|         'no': 'nor', | ||||
|         'nr': 'nbl', | ||||
|         'nv': 'nav', | ||||
|         'ny': 'nya', | ||||
|         'oc': 'oci', | ||||
|         'oj': 'oji', | ||||
|         'om': 'orm', | ||||
|         'or': 'ori', | ||||
|         'os': 'oss', | ||||
|         'pa': 'pan', | ||||
|         'pi': 'pli', | ||||
|         'pl': 'pol', | ||||
|         'ps': 'pus', | ||||
|         'pt': 'por', | ||||
|         'qu': 'que', | ||||
|         'rm': 'roh', | ||||
|         'rn': 'run', | ||||
|         'ro': 'ron', | ||||
|         'ru': 'rus', | ||||
|         'rw': 'kin', | ||||
|         'sa': 'san', | ||||
|         'sc': 'srd', | ||||
|         'sd': 'snd', | ||||
|         'se': 'sme', | ||||
|         'sg': 'sag', | ||||
|         'si': 'sin', | ||||
|         'sk': 'slk', | ||||
|         'sl': 'slv', | ||||
|         'sm': 'smo', | ||||
|         'sn': 'sna', | ||||
|         'so': 'som', | ||||
|         'sq': 'sqi', | ||||
|         'sr': 'srp', | ||||
|         'ss': 'ssw', | ||||
|         'st': 'sot', | ||||
|         'su': 'sun', | ||||
|         'sv': 'swe', | ||||
|         'sw': 'swa', | ||||
|         'ta': 'tam', | ||||
|         'te': 'tel', | ||||
|         'tg': 'tgk', | ||||
|         'th': 'tha', | ||||
|         'ti': 'tir', | ||||
|         'tk': 'tuk', | ||||
|         'tl': 'tgl', | ||||
|         'tn': 'tsn', | ||||
|         'to': 'ton', | ||||
|         'tr': 'tur', | ||||
|         'ts': 'tso', | ||||
|         'tt': 'tat', | ||||
|         'tw': 'twi', | ||||
|         'ty': 'tah', | ||||
|         'ug': 'uig', | ||||
|         'uk': 'ukr', | ||||
|         'ur': 'urd', | ||||
|         'uz': 'uzb', | ||||
|         've': 'ven', | ||||
|         'vi': 'vie', | ||||
|         'vo': 'vol', | ||||
|         'wa': 'wln', | ||||
|         'wo': 'wol', | ||||
|         'xh': 'xho', | ||||
|         'yi': 'yid', | ||||
|         'yo': 'yor', | ||||
|         'za': 'zha', | ||||
|         'zh': 'zho', | ||||
|         'zu': 'zul', | ||||
|     } | ||||
|  | ||||
|     def __init__(self, downloader=None, subtitlesformat='srt'): | ||||
|         super(FFmpegEmbedSubtitlePP, self).__init__(downloader) | ||||
|         self._subformat = subtitlesformat | ||||
|  | ||||
|     @classmethod | ||||
|     def _conver_lang_code(cls, code): | ||||
|         """Convert language code from ISO 639-1 to ISO 639-2/T""" | ||||
|         return cls._lang_map.get(code[:2]) | ||||
|  | ||||
|     def run(self, information): | ||||
|         if information['ext'] != u'mp4': | ||||
|             self._downloader.to_screen(u'[ffmpeg] Subtitles can only be embedded in mp4 files') | ||||
|             return True, information | ||||
|         if not information.get('subtitles'): | ||||
|             self._downloader.to_screen(u'[ffmpeg] There aren\'t any subtitles to embed')  | ||||
|             return True, information | ||||
|  | ||||
|         sub_langs = [key for key in information['subtitles']] | ||||
|         filename = information['filepath'] | ||||
|         input_files = [filename] + [subtitles_filename(filename, lang, self._subformat) for lang in sub_langs] | ||||
|  | ||||
|         opts = ['-map', '0:0', '-map', '0:1', '-c:v', 'copy', '-c:a', 'copy'] | ||||
|         for (i, lang) in enumerate(sub_langs): | ||||
|             opts.extend(['-map', '%d:0' % (i+1), '-c:s:%d' % i, 'mov_text']) | ||||
|             lang_code = self._conver_lang_code(lang) | ||||
|             if lang_code is not None: | ||||
|                 opts.extend(['-metadata:s:s:%d' % i, 'language=%s' % lang_code]) | ||||
|         opts.extend(['-f', 'mp4']) | ||||
|  | ||||
|         temp_filename = filename + u'.temp' | ||||
|         self._downloader.to_screen(u'[ffmpeg] Embedding subtitles in \'%s\'' % filename) | ||||
|         self.run_ffmpeg_multiple_files(input_files, temp_filename, opts) | ||||
|         os.remove(encodeFilename(filename)) | ||||
|         os.rename(encodeFilename(temp_filename), encodeFilename(filename)) | ||||
|  | ||||
|         return True, information | ||||
|  | ||||
|  | ||||
| class FFmpegMetadataPP(FFmpegPostProcessor): | ||||
|     def run(self, info): | ||||
|         metadata = {} | ||||
|         if info.get('title') is not None: | ||||
|             metadata['title'] = info['title'] | ||||
|         if info.get('upload_date') is not None: | ||||
|             metadata['date'] = info['upload_date'] | ||||
|         if info.get('uploader') is not None: | ||||
|             metadata['artist'] = info['uploader'] | ||||
|         elif info.get('uploader_id') is not None: | ||||
|             metadata['artist'] = info['uploader_id'] | ||||
|  | ||||
|         if not metadata: | ||||
|             self._downloader.to_screen(u'[ffmpeg] There isn\'t any metadata to add') | ||||
|             return True, info | ||||
|  | ||||
|         filename = info['filepath'] | ||||
|         ext = os.path.splitext(filename)[1][1:] | ||||
|         temp_filename = filename + u'.temp' | ||||
|  | ||||
|         options = ['-c', 'copy'] | ||||
|         for (name, value) in metadata.items(): | ||||
|             options.extend(['-metadata', '%s=%s' % (name, value)]) | ||||
|         options.extend(['-f', ext]) | ||||
|  | ||||
|         self._downloader.to_screen(u'[ffmpeg] Adding metadata to \'%s\'' % filename) | ||||
|         self.run_ffmpeg(filename, temp_filename, options) | ||||
|         os.remove(encodeFilename(filename)) | ||||
|         os.rename(encodeFilename(temp_filename), encodeFilename(filename)) | ||||
|         return True, info | ||||
|   | ||||
| @@ -3,6 +3,7 @@ | ||||
|  | ||||
| from __future__ import absolute_import | ||||
|  | ||||
| import errno | ||||
| import io | ||||
| import os | ||||
| import re | ||||
| @@ -12,7 +13,34 @@ import sys | ||||
| import time | ||||
| import traceback | ||||
|  | ||||
| from .utils import * | ||||
| if os.name == 'nt': | ||||
|     import ctypes | ||||
|  | ||||
| from .utils import ( | ||||
|     compat_http_client, | ||||
|     compat_print, | ||||
|     compat_str, | ||||
|     compat_urllib_error, | ||||
|     compat_urllib_request, | ||||
|     ContentTooShortError, | ||||
|     date_from_str, | ||||
|     DateRange, | ||||
|     determine_ext, | ||||
|     DownloadError, | ||||
|     encodeFilename, | ||||
|     ExtractorError, | ||||
|     locked_file, | ||||
|     MaxDownloadsReached, | ||||
|     PostProcessingError, | ||||
|     preferredencoding, | ||||
|     SameFileError, | ||||
|     sanitize_filename, | ||||
|     subtitles_filename, | ||||
|     takewhile_inclusive, | ||||
|     UnavailableVideoError, | ||||
|     write_json_file, | ||||
|     write_string, | ||||
| ) | ||||
| from .extractor import get_info_extractor, gen_extractors | ||||
| from .FileDownloader import FileDownloader | ||||
|  | ||||
| @@ -70,17 +98,27 @@ class YoutubeDL(object): | ||||
|     logtostderr:       Log messages to stderr instead of stdout. | ||||
|     writedescription:  Write the video description to a .description file | ||||
|     writeinfojson:     Write the video description to a .info.json file | ||||
|     writeannotations:  Write the video annotations to a .annotations.xml file | ||||
|     writethumbnail:    Write the thumbnail image to a file | ||||
|     writesubtitles:    Write the video subtitles to a file | ||||
|     writeautomaticsub: Write the automatic subtitles to a file | ||||
|     allsubtitles:      Downloads all the subtitles of the video | ||||
|                        (requires writesubtitles or writeautomaticsub) | ||||
|     listsubtitles:     Lists all available subtitles for the video | ||||
|     subtitlesformat:   Subtitle format [srt/sbv/vtt] (default=srt) | ||||
|     subtitleslang:     Language of the subtitles to download | ||||
|     subtitleslangs:    List of languages of the subtitles to download | ||||
|     keepvideo:         Keep the video file after post-processing | ||||
|     daterange:         A DateRange object, download only if the upload_date is in the range. | ||||
|     skip_download:     Skip the actual download of the video file | ||||
|      | ||||
|     cachedir:          Location of the cache files in the filesystem. | ||||
|                        None to disable filesystem cache. | ||||
|     noplaylist:        Download single video instead of a playlist if in doubt. | ||||
|     age_limit:         An integer representing the user's age in years. | ||||
|                        Unsuitable videos for the given age are skipped. | ||||
|     downloadarchive:   File name of a file where all downloads are recorded. | ||||
|                        Videos already present in the file are not downloaded | ||||
|                        again. | ||||
|  | ||||
|     The following parameters are not used by YoutubeDL itself, they are used by | ||||
|     the FileDownloader: | ||||
|     nopart, updatetime, buffersize, ratelimit, min_filesize, max_filesize, test, | ||||
| @@ -97,11 +135,23 @@ class YoutubeDL(object): | ||||
|     def __init__(self, params): | ||||
|         """Create a FileDownloader object with the given options.""" | ||||
|         self._ies = [] | ||||
|         self._ies_instances = {} | ||||
|         self._pps = [] | ||||
|         self._progress_hooks = [] | ||||
|         self._download_retcode = 0 | ||||
|         self._num_downloads = 0 | ||||
|         self._screen_file = [sys.stdout, sys.stderr][params.get('logtostderr', False)] | ||||
|  | ||||
|         if (sys.version_info >= (3,) and sys.platform != 'win32' and | ||||
|                 sys.getfilesystemencoding() in ['ascii', 'ANSI_X3.4-1968'] | ||||
|                 and not params['restrictfilenames']): | ||||
|             # On Python 3, the Unicode filesystem API will throw errors (#1474) | ||||
|             self.report_warning( | ||||
|                 u'Assuming --restrict-filenames since file system encoding ' | ||||
|                 u'cannot encode all charactes. ' | ||||
|                 u'Set the LC_ALL environment variable to fix this.') | ||||
|             params['restrictfilenames'] = True | ||||
|  | ||||
|         self.params = params | ||||
|         self.fd = FileDownloader(self, self.params) | ||||
|  | ||||
| @@ -111,8 +161,21 @@ class YoutubeDL(object): | ||||
|     def add_info_extractor(self, ie): | ||||
|         """Add an InfoExtractor object to the end of the list.""" | ||||
|         self._ies.append(ie) | ||||
|         self._ies_instances[ie.ie_key()] = ie | ||||
|         ie.set_downloader(self) | ||||
|  | ||||
|     def get_info_extractor(self, ie_key): | ||||
|         """ | ||||
|         Get an instance of an IE with name ie_key, it will try to get one from | ||||
|         the _ies list, if there's no instance it will create a new one and add | ||||
|         it to the extractor list. | ||||
|         """ | ||||
|         ie = self._ies_instances.get(ie_key) | ||||
|         if ie is None: | ||||
|             ie = get_info_extractor(ie_key)() | ||||
|             self.add_info_extractor(ie) | ||||
|         return ie | ||||
|  | ||||
|     def add_default_info_extractors(self): | ||||
|         """ | ||||
|         Add the InfoExtractors returned by gen_extractors to the end of the list | ||||
| @@ -127,14 +190,10 @@ class YoutubeDL(object): | ||||
|  | ||||
|     def to_screen(self, message, skip_eol=False): | ||||
|         """Print message to stdout if not in quiet mode.""" | ||||
|         assert type(message) == type(u'') | ||||
|         if not self.params.get('quiet', False): | ||||
|             terminator = [u'\n', u''][skip_eol] | ||||
|             output = message + terminator | ||||
|             if 'b' in getattr(self._screen_file, 'mode', '') or sys.version_info[0] < 3: # Python 2 lies about the mode of sys.stdout/sys.stderr | ||||
|                 output = output.encode(preferredencoding(), 'ignore') | ||||
|             self._screen_file.write(output) | ||||
|             self._screen_file.flush() | ||||
|             write_string(output, self._screen_file) | ||||
|  | ||||
|     def to_stderr(self, message): | ||||
|         """Print message to stderr.""" | ||||
| @@ -144,6 +203,35 @@ class YoutubeDL(object): | ||||
|             output = output.encode(preferredencoding()) | ||||
|         sys.stderr.write(output) | ||||
|  | ||||
|     def to_console_title(self, message): | ||||
|         if not self.params.get('consoletitle', False): | ||||
|             return | ||||
|         if os.name == 'nt' and ctypes.windll.kernel32.GetConsoleWindow(): | ||||
|             # c_wchar_p() might not be necessary if `message` is | ||||
|             # already of type unicode() | ||||
|             ctypes.windll.kernel32.SetConsoleTitleW(ctypes.c_wchar_p(message)) | ||||
|         elif 'TERM' in os.environ: | ||||
|             write_string(u'\033]0;%s\007' % message, self._screen_file) | ||||
|  | ||||
|     def save_console_title(self): | ||||
|         if not self.params.get('consoletitle', False): | ||||
|             return | ||||
|         if 'TERM' in os.environ: | ||||
|             write_string(u'\033[22t', self._screen_file) | ||||
|  | ||||
|     def restore_console_title(self): | ||||
|         if not self.params.get('consoletitle', False): | ||||
|             return | ||||
|         if 'TERM' in os.environ: | ||||
|             write_string(u'\033[23t', self._screen_file) | ||||
|  | ||||
|     def __enter__(self): | ||||
|         self.save_console_title() | ||||
|         return self | ||||
|  | ||||
|     def __exit__(self, *args): | ||||
|         self.restore_console_title() | ||||
|  | ||||
|     def fixed_template(self): | ||||
|         """Checks if the output template is fixed.""" | ||||
|         return (re.search(u'(?u)%\\(.+?\\)s', self.params['outtmpl']) is None) | ||||
| @@ -184,10 +272,10 @@ class YoutubeDL(object): | ||||
|         If stderr is a tty file the 'WARNING:' will be colored | ||||
|         ''' | ||||
|         if sys.stderr.isatty() and os.name != 'nt': | ||||
|             _msg_header=u'\033[0;33mWARNING:\033[0m' | ||||
|             _msg_header = u'\033[0;33mWARNING:\033[0m' | ||||
|         else: | ||||
|             _msg_header=u'WARNING:' | ||||
|         warning_message=u'%s %s' % (_msg_header,message) | ||||
|             _msg_header = u'WARNING:' | ||||
|         warning_message = u'%s %s' % (_msg_header, message) | ||||
|         self.to_stderr(warning_message) | ||||
|  | ||||
|     def report_error(self, message, tb=None): | ||||
| @@ -202,19 +290,6 @@ class YoutubeDL(object): | ||||
|         error_message = u'%s %s' % (_msg_header, message) | ||||
|         self.trouble(error_message, tb) | ||||
|  | ||||
|     def slow_down(self, start_time, byte_counter): | ||||
|         """Sleep if the download speed is over the rate limit.""" | ||||
|         rate_limit = self.params.get('ratelimit', None) | ||||
|         if rate_limit is None or byte_counter == 0: | ||||
|             return | ||||
|         now = time.time() | ||||
|         elapsed = now - start_time | ||||
|         if elapsed <= 0.0: | ||||
|             return | ||||
|         speed = float(byte_counter) / elapsed | ||||
|         if speed > rate_limit: | ||||
|             time.sleep((byte_counter - rate_limit * (now - start_time)) / rate_limit) | ||||
|  | ||||
|     def report_writedescription(self, descfn): | ||||
|         """ Report that the description file is being written """ | ||||
|         self.to_screen(u'[info] Writing video description to: ' + descfn) | ||||
| @@ -227,11 +302,15 @@ class YoutubeDL(object): | ||||
|         """ Report that the metadata file has been written """ | ||||
|         self.to_screen(u'[info] Video description metadata as JSON to: ' + infofn) | ||||
|  | ||||
|     def report_writeannotations(self, annofn): | ||||
|         """ Report that the annotations file has been written. """ | ||||
|         self.to_screen(u'[info] Writing video annotations to: ' + annofn) | ||||
|  | ||||
|     def report_file_already_downloaded(self, file_name): | ||||
|         """Report file has already been fully downloaded.""" | ||||
|         try: | ||||
|             self.to_screen(u'[download] %s has already been downloaded' % file_name) | ||||
|         except (UnicodeEncodeError) as err: | ||||
|         except UnicodeEncodeError: | ||||
|             self.to_screen(u'[download] The file has already been downloaded') | ||||
|  | ||||
|     def increment_downloads(self): | ||||
| @@ -249,22 +328,24 @@ class YoutubeDL(object): | ||||
|                 autonumber_size = 5 | ||||
|             autonumber_templ = u'%0' + str(autonumber_size) + u'd' | ||||
|             template_dict['autonumber'] = autonumber_templ % self._num_downloads | ||||
|             if template_dict['playlist_index'] is not None: | ||||
|             if template_dict.get('playlist_index') is not None: | ||||
|                 template_dict['playlist_index'] = u'%05d' % template_dict['playlist_index'] | ||||
|  | ||||
|             sanitize = lambda k,v: sanitize_filename( | ||||
|             sanitize = lambda k, v: sanitize_filename( | ||||
|                 u'NA' if v is None else compat_str(v), | ||||
|                 restricted=self.params.get('restrictfilenames'), | ||||
|                 is_id=(k==u'id')) | ||||
|             template_dict = dict((k, sanitize(k, v)) for k,v in template_dict.items()) | ||||
|                 is_id=(k == u'id')) | ||||
|             template_dict = dict((k, sanitize(k, v)) | ||||
|                                  for k, v in template_dict.items()) | ||||
|  | ||||
|             filename = self.params['outtmpl'] % template_dict | ||||
|             tmpl = os.path.expanduser(self.params['outtmpl']) | ||||
|             filename = tmpl % template_dict | ||||
|             return filename | ||||
|         except KeyError as err: | ||||
|             self.report_error(u'Erroneous output template') | ||||
|             return None | ||||
|         except ValueError as err: | ||||
|             self.report_error(u'Insufficient system charset ' + repr(preferredencoding())) | ||||
|             self.report_error(u'Error in output template: ' + str(err) + u' (encoding: ' + repr(preferredencoding()) + ')') | ||||
|             return None | ||||
|  | ||||
|     def _match_entry(self, info_dict): | ||||
| @@ -284,19 +365,30 @@ class YoutubeDL(object): | ||||
|             dateRange = self.params.get('daterange', DateRange()) | ||||
|             if date not in dateRange: | ||||
|                 return u'[download] %s upload date is not in range %s' % (date_from_str(date).isoformat(), dateRange) | ||||
|         age_limit = self.params.get('age_limit') | ||||
|         if age_limit is not None: | ||||
|             if age_limit < info_dict.get('age_limit', 0): | ||||
|                 return u'Skipping "' + title + '" because it is age restricted' | ||||
|         if self.in_download_archive(info_dict): | ||||
|             return (u'%(title)s has already been recorded in archive' | ||||
|                     % info_dict) | ||||
|         return None | ||||
|          | ||||
|  | ||||
|     @staticmethod | ||||
|     def add_extra_info(info_dict, extra_info): | ||||
|         '''Set the keys from extra_info in info dict if they are missing''' | ||||
|         for key, value in extra_info.items(): | ||||
|             info_dict.setdefault(key, value) | ||||
|  | ||||
|     def extract_info(self, url, download=True, ie_key=None, extra_info={}): | ||||
|         ''' | ||||
|         Returns a list with a dictionary for each video we find. | ||||
|         If 'download', also downloads the videos. | ||||
|         extra_info is a dict containing the extra values to add to each result | ||||
|          ''' | ||||
|          | ||||
|  | ||||
|         if ie_key: | ||||
|             ie = get_info_extractor(ie_key)() | ||||
|             ie.set_downloader(self) | ||||
|             ies = [ie] | ||||
|             ies = [self.get_info_extractor(ie_key)] | ||||
|         else: | ||||
|             ies = self._ies | ||||
|  | ||||
| @@ -314,17 +406,17 @@ class YoutubeDL(object): | ||||
|                     break | ||||
|                 if isinstance(ie_result, list): | ||||
|                     # Backwards compatibility: old IE result format | ||||
|                     for result in ie_result: | ||||
|                         result.update(extra_info) | ||||
|                     ie_result = { | ||||
|                         '_type': 'compat_list', | ||||
|                         'entries': ie_result, | ||||
|                     } | ||||
|                 else: | ||||
|                     ie_result.update(extra_info) | ||||
|                 if 'extractor' not in ie_result: | ||||
|                     ie_result['extractor'] = ie.IE_NAME | ||||
|                 return self.process_ie_result(ie_result, download=download) | ||||
|                 self.add_extra_info(ie_result, | ||||
|                     { | ||||
|                         'extractor': ie.IE_NAME, | ||||
|                         'webpage_url': url, | ||||
|                         'extractor_key': ie.ie_key(), | ||||
|                     }) | ||||
|                 return self.process_ie_result(ie_result, download, extra_info) | ||||
|             except ExtractorError as de: # An error we somewhat expected | ||||
|                 self.report_error(compat_str(de), de.format_traceback()) | ||||
|                 break | ||||
| @@ -336,7 +428,7 @@ class YoutubeDL(object): | ||||
|                     raise | ||||
|         else: | ||||
|             self.report_error(u'no suitable InfoExtractor: %s' % url) | ||||
|          | ||||
|  | ||||
|     def process_ie_result(self, ie_result, download=True, extra_info={}): | ||||
|         """ | ||||
|         Take the result of the ie(may be modified) and resolve all unresolved | ||||
| @@ -348,14 +440,8 @@ class YoutubeDL(object): | ||||
|  | ||||
|         result_type = ie_result.get('_type', 'video') # If not given we suppose it's a video, support the default old system | ||||
|         if result_type == 'video': | ||||
|             ie_result.update(extra_info) | ||||
|             if 'playlist' not in ie_result: | ||||
|                 # It isn't part of a playlist | ||||
|                 ie_result['playlist'] = None | ||||
|                 ie_result['playlist_index'] = None | ||||
|             if download: | ||||
|                 self.process_info(ie_result) | ||||
|             return ie_result | ||||
|             self.add_extra_info(ie_result, extra_info) | ||||
|             return self.process_video_result(ie_result, download=download) | ||||
|         elif result_type == 'url': | ||||
|             # We have to add extra_info to the results because it may be | ||||
|             # contained in a playlist | ||||
| @@ -364,9 +450,10 @@ class YoutubeDL(object): | ||||
|                                      ie_key=ie_result.get('ie_key'), | ||||
|                                      extra_info=extra_info) | ||||
|         elif result_type == 'playlist': | ||||
|             self.add_extra_info(ie_result, extra_info) | ||||
|             # We process each entry in the playlist | ||||
|             playlist = ie_result.get('title', None) or ie_result.get('id', None) | ||||
|             self.to_screen(u'[download] Downloading playlist: %s'  % playlist) | ||||
|             self.to_screen(u'[download] Downloading playlist: %s' % playlist) | ||||
|  | ||||
|             playlist_results = [] | ||||
|  | ||||
| @@ -384,17 +471,15 @@ class YoutubeDL(object): | ||||
|             self.to_screen(u"[%s] playlist '%s': Collected %d video ids (downloading %d of them)" % | ||||
|                 (ie_result['extractor'], playlist, n_all_entries, n_entries)) | ||||
|  | ||||
|             for i,entry in enumerate(entries,1): | ||||
|                 self.to_screen(u'[download] Downloading video #%s of %s' %(i, n_entries)) | ||||
|             for i, entry in enumerate(entries, 1): | ||||
|                 self.to_screen(u'[download] Downloading video #%s of %s' % (i, n_entries)) | ||||
|                 extra = { | ||||
|                          'playlist': playlist,  | ||||
|                          'playlist_index': i + playliststart, | ||||
|                          } | ||||
|                 if not 'extractor' in entry: | ||||
|                     # We set the extractor, if it's an url it will be set then to | ||||
|                     # the new extractor, but if it's already a video we must make | ||||
|                     # sure it's present: see issue #877 | ||||
|                     entry['extractor'] = ie_result['extractor'] | ||||
|                     'playlist': playlist, | ||||
|                     'playlist_index': i + playliststart, | ||||
|                     'extractor': ie_result['extractor'], | ||||
|                     'webpage_url': ie_result['webpage_url'], | ||||
|                     'extractor_key': ie_result['extractor_key'], | ||||
|                 } | ||||
|                 entry_result = self.process_ie_result(entry, | ||||
|                                                       download=download, | ||||
|                                                       extra_info=extra) | ||||
| @@ -403,16 +488,122 @@ class YoutubeDL(object): | ||||
|             return ie_result | ||||
|         elif result_type == 'compat_list': | ||||
|             def _fixup(r): | ||||
|                 r.setdefault('extractor', ie_result['extractor']) | ||||
|                 self.add_extra_info(r, | ||||
|                     { | ||||
|                         'extractor': ie_result['extractor'], | ||||
|                         'webpage_url': ie_result['webpage_url'], | ||||
|                         'extractor_key': ie_result['extractor_key'], | ||||
|                     }) | ||||
|                 return r | ||||
|             ie_result['entries'] = [ | ||||
|                 self.process_ie_result(_fixup(r), download=download) | ||||
|                 self.process_ie_result(_fixup(r), download, extra_info) | ||||
|                 for r in ie_result['entries'] | ||||
|             ] | ||||
|             return ie_result | ||||
|         else: | ||||
|             raise Exception('Invalid result type: %s' % result_type) | ||||
|  | ||||
|     def select_format(self, format_spec, available_formats): | ||||
|         if format_spec == 'best' or format_spec is None: | ||||
|             return available_formats[-1] | ||||
|         elif format_spec == 'worst': | ||||
|             return available_formats[0] | ||||
|         else: | ||||
|             extensions = [u'mp4', u'flv', u'webm', u'3gp'] | ||||
|             if format_spec in extensions: | ||||
|                 filter_f = lambda f: f['ext'] == format_spec | ||||
|             else: | ||||
|                 filter_f = lambda f: f['format_id'] == format_spec | ||||
|             matches = list(filter(filter_f, available_formats)) | ||||
|             if matches: | ||||
|                 return matches[-1] | ||||
|         return None | ||||
|  | ||||
|     def process_video_result(self, info_dict, download=True): | ||||
|         assert info_dict.get('_type', 'video') == 'video' | ||||
|  | ||||
|         if 'playlist' not in info_dict: | ||||
|             # It isn't part of a playlist | ||||
|             info_dict['playlist'] = None | ||||
|             info_dict['playlist_index'] = None | ||||
|  | ||||
|         # This extractors handle format selection themselves | ||||
|         if info_dict['extractor'] in [u'youtube', u'Youku']: | ||||
|             if download: | ||||
|                 self.process_info(info_dict) | ||||
|             return info_dict | ||||
|  | ||||
|         # We now pick which formats have to be downloaded | ||||
|         if info_dict.get('formats') is None: | ||||
|             # There's only one format available | ||||
|             formats = [info_dict] | ||||
|         else: | ||||
|             formats = info_dict['formats'] | ||||
|  | ||||
|         # We check that all the formats have the format and format_id fields | ||||
|         for (i, format) in enumerate(formats): | ||||
|             if format.get('format_id') is None: | ||||
|                 format['format_id'] = compat_str(i) | ||||
|             if format.get('format') is None: | ||||
|                 format['format'] = u'{id} - {res}{note}'.format( | ||||
|                     id=format['format_id'], | ||||
|                     res=self.format_resolution(format), | ||||
|                     note=u' ({0})'.format(format['format_note']) if format.get('format_note') is not None else '', | ||||
|                 ) | ||||
|             # Automatically determine file extension if missing | ||||
|             if 'ext' not in format: | ||||
|                 format['ext'] = determine_ext(format['url']) | ||||
|  | ||||
|         if self.params.get('listformats', None): | ||||
|             self.list_formats(info_dict) | ||||
|             return | ||||
|  | ||||
|         format_limit = self.params.get('format_limit', None) | ||||
|         if format_limit: | ||||
|             formats = list(takewhile_inclusive( | ||||
|                 lambda f: f['format_id'] != format_limit, formats | ||||
|             )) | ||||
|         if self.params.get('prefer_free_formats'): | ||||
|             def _free_formats_key(f): | ||||
|                 try: | ||||
|                     ext_ord = [u'flv', u'mp4', u'webm'].index(f['ext']) | ||||
|                 except ValueError: | ||||
|                     ext_ord = -1 | ||||
|                 # We only compare the extension if they have the same height and width | ||||
|                 return (f.get('height'), f.get('width'), ext_ord) | ||||
|             formats = sorted(formats, key=_free_formats_key) | ||||
|  | ||||
|         req_format = self.params.get('format', 'best') | ||||
|         if req_format is None: | ||||
|             req_format = 'best' | ||||
|         formats_to_download = [] | ||||
|         # The -1 is for supporting YoutubeIE | ||||
|         if req_format in ('-1', 'all'): | ||||
|             formats_to_download = formats | ||||
|         else: | ||||
|             # We can accept formats requestd in the format: 34/5/best, we pick | ||||
|             # the first that is available, starting from left | ||||
|             req_formats = req_format.split('/') | ||||
|             for rf in req_formats: | ||||
|                 selected_format = self.select_format(rf, formats) | ||||
|                 if selected_format is not None: | ||||
|                     formats_to_download = [selected_format] | ||||
|                     break | ||||
|         if not formats_to_download: | ||||
|             raise ExtractorError(u'requested format not available', | ||||
|                                  expected=True) | ||||
|  | ||||
|         if download: | ||||
|             if len(formats_to_download) > 1: | ||||
|                 self.to_screen(u'[info] %s: downloading video in %s formats' % (info_dict['id'], len(formats_to_download))) | ||||
|             for format in formats_to_download: | ||||
|                 new_info = dict(info_dict) | ||||
|                 new_info.update(format) | ||||
|                 self.process_info(new_info) | ||||
|         # We update the info dict with the best quality format (backwards compatibility) | ||||
|         info_dict.update(formats_to_download[-1]) | ||||
|         return info_dict | ||||
|  | ||||
|     def process_info(self, info_dict): | ||||
|         """Process a single resolved IE result.""" | ||||
|  | ||||
| @@ -448,10 +639,11 @@ class YoutubeDL(object): | ||||
|         if self.params.get('forceid', False): | ||||
|             compat_print(info_dict['id']) | ||||
|         if self.params.get('forceurl', False): | ||||
|             compat_print(info_dict['url']) | ||||
|         if self.params.get('forcethumbnail', False) and 'thumbnail' in info_dict: | ||||
|             # For RTMP URLs, also include the playpath | ||||
|             compat_print(info_dict['url'] + info_dict.get('play_path', u'')) | ||||
|         if self.params.get('forcethumbnail', False) and info_dict.get('thumbnail') is not None: | ||||
|             compat_print(info_dict['thumbnail']) | ||||
|         if self.params.get('forcedescription', False) and 'description' in info_dict: | ||||
|         if self.params.get('forcedescription', False) and info_dict.get('description') is not None: | ||||
|             compat_print(info_dict['description']) | ||||
|         if self.params.get('forcefilename', False) and filename is not None: | ||||
|             compat_print(filename) | ||||
| @@ -479,50 +671,50 @@ class YoutubeDL(object): | ||||
|                 self.report_writedescription(descfn) | ||||
|                 with io.open(encodeFilename(descfn), 'w', encoding='utf-8') as descfile: | ||||
|                     descfile.write(info_dict['description']) | ||||
|             except (KeyError, TypeError): | ||||
|                 self.report_warning(u'There\'s no description to write.') | ||||
|             except (OSError, IOError): | ||||
|                 self.report_error(u'Cannot write description file ' + descfn) | ||||
|                 return | ||||
|  | ||||
|         if (self.params.get('writesubtitles', False) or self.params.get('writeautomaticsub')) and 'subtitles' in info_dict and info_dict['subtitles']: | ||||
|         if self.params.get('writeannotations', False): | ||||
|             try: | ||||
|                 annofn = filename + u'.annotations.xml' | ||||
|                 self.report_writeannotations(annofn) | ||||
|                 with io.open(encodeFilename(annofn), 'w', encoding='utf-8') as annofile: | ||||
|                     annofile.write(info_dict['annotations']) | ||||
|             except (KeyError, TypeError): | ||||
|                 self.report_warning(u'There are no annotations to write.') | ||||
|             except (OSError, IOError): | ||||
|                 self.report_error(u'Cannot write annotations file: ' + annofn) | ||||
|                 return | ||||
|  | ||||
|         subtitles_are_requested = any([self.params.get('writesubtitles', False), | ||||
|                                        self.params.get('writeautomaticsub')]) | ||||
|  | ||||
|         if subtitles_are_requested and 'subtitles' in info_dict and info_dict['subtitles']: | ||||
|             # subtitles download errors are already managed as troubles in relevant IE | ||||
|             # that way it will silently go on when used with unsupporting IE | ||||
|             subtitle = info_dict['subtitles'][0] | ||||
|             (sub_error, sub_lang, sub) = subtitle | ||||
|             sub_format = self.params.get('subtitlesformat') | ||||
|             if sub_error: | ||||
|                 self.report_warning("Some error while getting the subtitles") | ||||
|             else: | ||||
|             subtitles = info_dict['subtitles'] | ||||
|             sub_format = self.params.get('subtitlesformat', 'srt') | ||||
|             for sub_lang in subtitles.keys(): | ||||
|                 sub = subtitles[sub_lang] | ||||
|                 if sub is None: | ||||
|                     continue | ||||
|                 try: | ||||
|                     sub_filename = filename.rsplit('.', 1)[0] + u'.' + sub_lang + u'.' + sub_format | ||||
|                     sub_filename = subtitles_filename(filename, sub_lang, sub_format) | ||||
|                     self.report_writesubtitles(sub_filename) | ||||
|                     with io.open(encodeFilename(sub_filename), 'w', encoding='utf-8') as subfile: | ||||
|                         subfile.write(sub) | ||||
|                             subfile.write(sub) | ||||
|                 except (OSError, IOError): | ||||
|                     self.report_error(u'Cannot write subtitles file ' + descfn) | ||||
|                     return | ||||
|  | ||||
|         if self.params.get('allsubtitles', False) and 'subtitles' in info_dict and info_dict['subtitles']: | ||||
|             subtitles = info_dict['subtitles'] | ||||
|             sub_format = self.params.get('subtitlesformat') | ||||
|             for subtitle in subtitles: | ||||
|                 (sub_error, sub_lang, sub) = subtitle | ||||
|                 if sub_error: | ||||
|                     self.report_warning("Some error while getting the subtitles") | ||||
|                 else: | ||||
|                     try: | ||||
|                         sub_filename = filename.rsplit('.', 1)[0] + u'.' + sub_lang + u'.' + sub_format | ||||
|                         self.report_writesubtitles(sub_filename) | ||||
|                         with io.open(encodeFilename(sub_filename), 'w', encoding='utf-8') as subfile: | ||||
|                                 subfile.write(sub) | ||||
|                     except (OSError, IOError): | ||||
|                         self.report_error(u'Cannot write subtitles file ' + descfn) | ||||
|                         return | ||||
|  | ||||
|         if self.params.get('writeinfojson', False): | ||||
|             infofn = filename + u'.info.json' | ||||
|             self.report_writeinfojson(infofn) | ||||
|             try: | ||||
|                 json_info_dict = dict((k, v) for k,v in info_dict.items() if not k in ['urlhandle']) | ||||
|                 json_info_dict = dict((k, v) for k, v in info_dict.items() if not k in ['urlhandle']) | ||||
|                 write_json_file(json_info_dict, encodeFilename(infofn)) | ||||
|             except (OSError, IOError): | ||||
|                 self.report_error(u'Cannot write metadata to JSON file ' + infofn) | ||||
| @@ -534,11 +726,15 @@ class YoutubeDL(object): | ||||
|                 thumb_filename = filename.rpartition('.')[0] + u'.' + thumb_format | ||||
|                 self.to_screen(u'[%s] %s: Downloading thumbnail ...' % | ||||
|                                (info_dict['extractor'], info_dict['id'])) | ||||
|                 uf = compat_urllib_request.urlopen(info_dict['thumbnail']) | ||||
|                 with open(thumb_filename, 'wb') as thumbf: | ||||
|                     shutil.copyfileobj(uf, thumbf) | ||||
|                 self.to_screen(u'[%s] %s: Writing thumbnail to: %s' % | ||||
|                                (info_dict['extractor'], info_dict['id'], thumb_filename)) | ||||
|                 try: | ||||
|                     uf = compat_urllib_request.urlopen(info_dict['thumbnail']) | ||||
|                     with open(thumb_filename, 'wb') as thumbf: | ||||
|                         shutil.copyfileobj(uf, thumbf) | ||||
|                     self.to_screen(u'[%s] %s: Writing thumbnail to: %s' % | ||||
|                         (info_dict['extractor'], info_dict['id'], thumb_filename)) | ||||
|                 except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err: | ||||
|                     self.report_warning(u'Unable to download thumbnail "%s": %s' % | ||||
|                         (info_dict['thumbnail'], compat_str(err))) | ||||
|  | ||||
|         if not self.params.get('skip_download', False): | ||||
|             if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(filename)): | ||||
| @@ -546,11 +742,11 @@ class YoutubeDL(object): | ||||
|             else: | ||||
|                 try: | ||||
|                     success = self.fd._do_download(filename, info_dict) | ||||
|                 except (OSError, IOError) as err: | ||||
|                     raise UnavailableVideoError() | ||||
|                 except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err: | ||||
|                     self.report_error(u'unable to download video data: %s' % str(err)) | ||||
|                     return | ||||
|                 except (OSError, IOError) as err: | ||||
|                     raise UnavailableVideoError(err) | ||||
|                 except (ContentTooShortError, ) as err: | ||||
|                     self.report_error(u'content too short (expected %s bytes and served %s)' % (err.expected, err.downloaded)) | ||||
|                     return | ||||
| @@ -562,6 +758,8 @@ class YoutubeDL(object): | ||||
|                     self.report_error(u'postprocessing: %s' % str(err)) | ||||
|                     return | ||||
|  | ||||
|         self.record_download_archive(info_dict) | ||||
|  | ||||
|     def download(self, url_list): | ||||
|         """Download a given list of URLs.""" | ||||
|         if len(url_list) > 1 and self.fixed_template(): | ||||
| @@ -586,7 +784,7 @@ class YoutubeDL(object): | ||||
|         keep_video = None | ||||
|         for pp in self._pps: | ||||
|             try: | ||||
|                 keep_video_wish,new_info = pp.run(info) | ||||
|                 keep_video_wish, new_info = pp.run(info) | ||||
|                 if keep_video_wish is not None: | ||||
|                     if keep_video_wish: | ||||
|                         keep_video = keep_video_wish | ||||
| @@ -594,10 +792,90 @@ class YoutubeDL(object): | ||||
|                         # No clear decision yet, let IE decide | ||||
|                         keep_video = keep_video_wish | ||||
|             except PostProcessingError as e: | ||||
|                 self.to_stderr(u'ERROR: ' + e.msg) | ||||
|                 self.report_error(e.msg) | ||||
|         if keep_video is False and not self.params.get('keepvideo', False): | ||||
|             try: | ||||
|                 self.to_screen(u'Deleting original file %s (pass -k to keep)' % filename) | ||||
|                 os.remove(encodeFilename(filename)) | ||||
|             except (IOError, OSError): | ||||
|                 self.report_warning(u'Unable to remove downloaded video file') | ||||
|  | ||||
|     def in_download_archive(self, info_dict): | ||||
|         fn = self.params.get('download_archive') | ||||
|         if fn is None: | ||||
|             return False | ||||
|         vid_id = info_dict['extractor'] + u' ' + info_dict['id'] | ||||
|         try: | ||||
|             with locked_file(fn, 'r', encoding='utf-8') as archive_file: | ||||
|                 for line in archive_file: | ||||
|                     if line.strip() == vid_id: | ||||
|                         return True | ||||
|         except IOError as ioe: | ||||
|             if ioe.errno != errno.ENOENT: | ||||
|                 raise | ||||
|         return False | ||||
|  | ||||
|     def record_download_archive(self, info_dict): | ||||
|         fn = self.params.get('download_archive') | ||||
|         if fn is None: | ||||
|             return | ||||
|         vid_id = info_dict['extractor'] + u' ' + info_dict['id'] | ||||
|         with locked_file(fn, 'a', encoding='utf-8') as archive_file: | ||||
|             archive_file.write(vid_id + u'\n') | ||||
|  | ||||
|     @staticmethod | ||||
|     def format_resolution(format, default='unknown'): | ||||
|         if format.get('_resolution') is not None: | ||||
|             return format['_resolution'] | ||||
|         if format.get('height') is not None: | ||||
|             if format.get('width') is not None: | ||||
|                 res = u'%sx%s' % (format['width'], format['height']) | ||||
|             else: | ||||
|                 res = u'%sp' % format['height'] | ||||
|         else: | ||||
|             res = default | ||||
|         return res | ||||
|  | ||||
|     def list_formats(self, info_dict): | ||||
|         def format_note(fdict): | ||||
|             if fdict.get('format_note') is not None: | ||||
|                 return fdict['format_note'] | ||||
|             res = u'' | ||||
|             if fdict.get('vcodec') is not None: | ||||
|                 res += u'%-5s' % fdict['vcodec'] | ||||
|             elif fdict.get('vbr') is not None: | ||||
|                 res += u'video' | ||||
|             if fdict.get('vbr') is not None: | ||||
|                 res += u'@%4dk' % fdict['vbr'] | ||||
|             if fdict.get('acodec') is not None: | ||||
|                 if res: | ||||
|                     res += u', ' | ||||
|                 res += u'%-5s' % fdict['acodec'] | ||||
|             elif fdict.get('abr') is not None: | ||||
|                 if res: | ||||
|                     res += u', ' | ||||
|                 res += 'audio' | ||||
|             if fdict.get('abr') is not None: | ||||
|                 res += u'@%3dk' % fdict['abr'] | ||||
|             return res | ||||
|  | ||||
|         def line(format): | ||||
|             return (u'%-20s%-10s%-12s%s' % ( | ||||
|                 format['format_id'], | ||||
|                 format['ext'], | ||||
|                 self.format_resolution(format), | ||||
|                 format_note(format), | ||||
|                 ) | ||||
|             ) | ||||
|  | ||||
|         formats = info_dict.get('formats', [info_dict]) | ||||
|         formats_s = list(map(line, formats)) | ||||
|         if len(formats) > 1: | ||||
|             formats_s[0] += (' ' if format_note(formats[0]) else '') + '(worst)' | ||||
|             formats_s[-1] += (' ' if format_note(formats[-1]) else '') + '(best)' | ||||
|  | ||||
|         header_line = line({ | ||||
|             'format_id': u'format code', 'ext': u'extension', | ||||
|             '_resolution': u'resolution', 'format_note': u'note'}) | ||||
|         self.to_screen(u'[info] Available formats for %s:\n%s\n%s' % | ||||
|                        (info_dict['id'], header_line, u"\n".join(formats_s))) | ||||
|   | ||||
| @@ -27,11 +27,19 @@ __authors__  = ( | ||||
|     'Johny Mo Swag', | ||||
|     'Axel Noack', | ||||
|     'Albert Kim', | ||||
|     'Pierre Rudloff', | ||||
|     'Huarong Huo', | ||||
|     'Ismael Mejía', | ||||
|     'Steffan \'Ruirize\' James', | ||||
|     'Andras Elso', | ||||
|     'Jelle van der Waa', | ||||
|     'Marcin Cieślak', | ||||
| ) | ||||
|  | ||||
| __license__ = 'Public Domain' | ||||
|  | ||||
| import codecs | ||||
| import collections | ||||
| import getpass | ||||
| import optparse | ||||
| import os | ||||
| @@ -41,16 +49,43 @@ import shlex | ||||
| import socket | ||||
| import subprocess | ||||
| import sys | ||||
| import warnings | ||||
| import traceback | ||||
| import platform | ||||
|  | ||||
| from .utils import * | ||||
|  | ||||
| from .utils import ( | ||||
|     compat_cookiejar, | ||||
|     compat_print, | ||||
|     compat_str, | ||||
|     compat_urllib_request, | ||||
|     DateRange, | ||||
|     decodeOption, | ||||
|     determine_ext, | ||||
|     DownloadError, | ||||
|     get_cachedir, | ||||
|     make_HTTPS_handler, | ||||
|     MaxDownloadsReached, | ||||
|     platform_name, | ||||
|     preferredencoding, | ||||
|     SameFileError, | ||||
|     std_headers, | ||||
|     write_string, | ||||
|     YoutubeDLHandler, | ||||
| ) | ||||
| from .update import update_self | ||||
| from .version import __version__ | ||||
| from .FileDownloader import * | ||||
| from .FileDownloader import ( | ||||
|     FileDownloader, | ||||
| ) | ||||
| from .extractor import gen_extractors | ||||
| from .YoutubeDL import YoutubeDL | ||||
| from .PostProcessor import * | ||||
| from .PostProcessor import ( | ||||
|     FFmpegMetadataPP, | ||||
|     FFmpegVideoConvertor, | ||||
|     FFmpegExtractAudioPP, | ||||
|     FFmpegEmbedSubtitlePP, | ||||
| ) | ||||
|  | ||||
|  | ||||
| def parseOpts(overrideArguments=None): | ||||
|     def _readOptions(filename_bytes): | ||||
| @@ -82,6 +117,9 @@ def parseOpts(overrideArguments=None): | ||||
|  | ||||
|         return "".join(opts) | ||||
|  | ||||
|     def _comma_separated_values_options_callback(option, opt_str, value, parser): | ||||
|         setattr(parser.values, option.dest, value.split(',')) | ||||
|  | ||||
|     def _find_term_columns(): | ||||
|         columns = os.environ.get('COLUMNS', None) | ||||
|         if columns: | ||||
| @@ -95,6 +133,16 @@ def parseOpts(overrideArguments=None): | ||||
|             pass | ||||
|         return None | ||||
|  | ||||
|     def _hide_login_info(opts): | ||||
|         opts = list(opts) | ||||
|         for private_opt in ['-p', '--password', '-u', '--username', '--video-password']: | ||||
|             try: | ||||
|                 i = opts.index(private_opt) | ||||
|                 opts[i+1] = '<PRIVATE>' | ||||
|             except ValueError: | ||||
|                 pass | ||||
|         return opts | ||||
|  | ||||
|     max_width = 80 | ||||
|     max_help_position = 80 | ||||
|  | ||||
| @@ -119,6 +167,7 @@ def parseOpts(overrideArguments=None): | ||||
|     selection      = optparse.OptionGroup(parser, 'Video Selection') | ||||
|     authentication = optparse.OptionGroup(parser, 'Authentication Options') | ||||
|     video_format   = optparse.OptionGroup(parser, 'Video Format Options') | ||||
|     subtitles      = optparse.OptionGroup(parser, 'Subtitle Options') | ||||
|     downloader     = optparse.OptionGroup(parser, 'Download Options') | ||||
|     postproc       = optparse.OptionGroup(parser, 'Post-processing Options') | ||||
|     filesystem     = optparse.OptionGroup(parser, 'Filesystem Options') | ||||
| @@ -131,7 +180,10 @@ def parseOpts(overrideArguments=None): | ||||
|     general.add_option('-U', '--update', | ||||
|             action='store_true', dest='update_self', help='update this program to latest version. Make sure that you have sufficient permissions (run with sudo if needed)') | ||||
|     general.add_option('-i', '--ignore-errors', | ||||
|             action='store_true', dest='ignoreerrors', help='continue on download errors', default=False) | ||||
|             action='store_true', dest='ignoreerrors', help='continue on download errors, for example to to skip unavailable videos in a playlist', default=False) | ||||
|     general.add_option('--abort-on-error', | ||||
|             action='store_false', dest='ignoreerrors', | ||||
|             help='Abort downloading of further videos (in the playlist or the command line) if an error occurs') | ||||
|     general.add_option('--dump-user-agent', | ||||
|             action='store_true', dest='dump_user_agent', | ||||
|             help='display the current browser identification', default=False) | ||||
| @@ -148,6 +200,12 @@ def parseOpts(overrideArguments=None): | ||||
|             help='Output descriptions of all supported extractors', default=False) | ||||
|     general.add_option('--proxy', dest='proxy', default=None, help='Use the specified HTTP/HTTPS proxy', metavar='URL') | ||||
|     general.add_option('--no-check-certificate', action='store_true', dest='no_check_certificate', default=False, help='Suppress HTTPS certificate validation.') | ||||
|     general.add_option( | ||||
|         '--cache-dir', dest='cachedir', default=get_cachedir(), metavar='DIR', | ||||
|         help='Location in the filesystem where youtube-dl can store downloaded information permanently. By default $XDG_CACHE_HOME/youtube-dl or ~/.cache/youtube-dl .') | ||||
|     general.add_option( | ||||
|         '--no-cache-dir', action='store_const', const=None, dest='cachedir', | ||||
|         help='Disable filesystem caching') | ||||
|  | ||||
|  | ||||
|     selection.add_option('--playlist-start', | ||||
| @@ -162,6 +220,13 @@ def parseOpts(overrideArguments=None): | ||||
|     selection.add_option('--date', metavar='DATE', dest='date', help='download only videos uploaded in this date', default=None) | ||||
|     selection.add_option('--datebefore', metavar='DATE', dest='datebefore', help='download only videos uploaded before this date', default=None) | ||||
|     selection.add_option('--dateafter', metavar='DATE', dest='dateafter', help='download only videos uploaded after this date', default=None) | ||||
|     selection.add_option('--no-playlist', action='store_true', dest='noplaylist', help='download only the currently playing video', default=False) | ||||
|     selection.add_option('--age-limit', metavar='YEARS', dest='age_limit', | ||||
|                          help='download only videos suitable for the given age', | ||||
|                          default=None, type=int) | ||||
|     selection.add_option('--download-archive', metavar='FILE', | ||||
|                          dest='download_archive', | ||||
|                          help='Download only videos not present in the archive file. Record all downloaded videos in it.') | ||||
|  | ||||
|  | ||||
|     authentication.add_option('-u', '--username', | ||||
| @@ -175,8 +240,8 @@ def parseOpts(overrideArguments=None): | ||||
|  | ||||
|  | ||||
|     video_format.add_option('-f', '--format', | ||||
|             action='store', dest='format', metavar='FORMAT', | ||||
|             help='video format code, specifiy the order of preference using slashes: "-f 22/17/18"') | ||||
|             action='store', dest='format', metavar='FORMAT', default='best', | ||||
|             help='video format code, specifiy the order of preference using slashes: "-f 22/17/18". "-f mp4" and "-f flv" are also supported') | ||||
|     video_format.add_option('--all-formats', | ||||
|             action='store_const', dest='format', help='download all available video formats', const='all') | ||||
|     video_format.add_option('--prefer-free-formats', | ||||
| @@ -185,34 +250,33 @@ def parseOpts(overrideArguments=None): | ||||
|             action='store', dest='format_limit', metavar='FORMAT', help='highest quality format to download') | ||||
|     video_format.add_option('-F', '--list-formats', | ||||
|             action='store_true', dest='listformats', help='list all available formats (currently youtube only)') | ||||
|     video_format.add_option('--write-sub', '--write-srt', | ||||
|  | ||||
|     subtitles.add_option('--write-sub', '--write-srt', | ||||
|             action='store_true', dest='writesubtitles', | ||||
|             help='write subtitle file (currently youtube only)', default=False) | ||||
|     video_format.add_option('--write-auto-sub', '--write-automatic-sub', | ||||
|             help='write subtitle file', default=False) | ||||
|     subtitles.add_option('--write-auto-sub', '--write-automatic-sub', | ||||
|             action='store_true', dest='writeautomaticsub', | ||||
|             help='write automatic subtitle file (currently youtube only)', default=False) | ||||
|     video_format.add_option('--only-sub', | ||||
|             action='store_true', dest='skip_download', | ||||
|             help='[deprecated] alias of --skip-download', default=False) | ||||
|     video_format.add_option('--all-subs', | ||||
|             help='write automatic subtitle file (youtube only)', default=False) | ||||
|     subtitles.add_option('--all-subs', | ||||
|             action='store_true', dest='allsubtitles', | ||||
|             help='downloads all the available subtitles of the video (currently youtube only)', default=False) | ||||
|     video_format.add_option('--list-subs', | ||||
|             help='downloads all the available subtitles of the video', default=False) | ||||
|     subtitles.add_option('--list-subs', | ||||
|             action='store_true', dest='listsubtitles', | ||||
|             help='lists all available subtitles for the video (currently youtube only)', default=False) | ||||
|     video_format.add_option('--sub-format', | ||||
|             help='lists all available subtitles for the video', default=False) | ||||
|     subtitles.add_option('--sub-format', | ||||
|             action='store', dest='subtitlesformat', metavar='FORMAT', | ||||
|             help='subtitle format [srt/sbv/vtt] (default=srt) (currently youtube only)', default='srt') | ||||
|     video_format.add_option('--sub-lang', '--srt-lang', | ||||
|             action='store', dest='subtitleslang', metavar='LANG', | ||||
|             help='language of the subtitles to download (optional) use IETF language tags like \'en\'') | ||||
|             help='subtitle format (default=srt) ([sbv/vtt] youtube only)', default='srt') | ||||
|     subtitles.add_option('--sub-lang', '--sub-langs', '--srt-lang', | ||||
|             action='callback', dest='subtitleslangs', metavar='LANGS', type='str', | ||||
|             default=[], callback=_comma_separated_values_options_callback, | ||||
|             help='languages of the subtitles to download (optional) separated by commas, use IETF language tags like \'en,pt\'') | ||||
|  | ||||
|     downloader.add_option('-r', '--rate-limit', | ||||
|             dest='ratelimit', metavar='LIMIT', help='maximum download rate (e.g. 50k or 44.6m)') | ||||
|             dest='ratelimit', metavar='LIMIT', help='maximum download rate in bytes per second (e.g. 50K or 4.2M)') | ||||
|     downloader.add_option('-R', '--retries', | ||||
|             dest='retries', metavar='RETRIES', help='number of retries (default is %default)', default=10) | ||||
|     downloader.add_option('--buffer-size', | ||||
|             dest='buffersize', metavar='SIZE', help='size of download buffer (e.g. 1024 or 16k) (default is %default)', default="1024") | ||||
|             dest='buffersize', metavar='SIZE', help='size of download buffer (e.g. 1024 or 16K) (default is %default)', default="1024") | ||||
|     downloader.add_option('--no-resize-buffer', | ||||
|             action='store_true', dest='noresizebuffer', | ||||
|             help='do not automatically adjust the buffer size. By default, the buffer size is automatically resized from an initial value of SIZE.', default=False) | ||||
| @@ -254,6 +318,13 @@ def parseOpts(overrideArguments=None): | ||||
|     verbosity.add_option('--dump-intermediate-pages', | ||||
|             action='store_true', dest='dump_intermediate_pages', default=False, | ||||
|             help='print downloaded pages to debug problems(very verbose)') | ||||
|     verbosity.add_option('--write-pages', | ||||
|             action='store_true', dest='write_pages', default=False, | ||||
|             help='Write downloaded pages to files in the current directory') | ||||
|     verbosity.add_option('--youtube-print-sig-code', | ||||
|             action='store_true', dest='youtube_print_sig_code', default=False, | ||||
|             help=optparse.SUPPRESS_HELP) | ||||
|  | ||||
|  | ||||
|     filesystem.add_option('-t', '--title', | ||||
|             action='store_true', dest='usetitle', help='use title in file name (default)', default=False) | ||||
| @@ -269,7 +340,10 @@ def parseOpts(overrideArguments=None): | ||||
|             help=('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, %(upload_date)s for the upload date (YYYYMMDD), ' | ||||
|                   '%(ext)s for the filename extension, ' | ||||
|                   '%(format)s for the format description (like "22 - 1280x720" or "HD"),' | ||||
|                   '%(format_id)s for the unique id of the format (like Youtube\'s itags: "137"),' | ||||
|                   '%(upload_date)s for the upload date (YYYYMMDD), ' | ||||
|                   '%(extractor)s for the provider (youtube, metacafe, etc), ' | ||||
|                   '%(id)s for the video id , %(playlist)s for the playlist the video is in, ' | ||||
|                   '%(playlist_index)s for the position in the playlist and %% for a literal percent. ' | ||||
| @@ -277,7 +351,7 @@ def parseOpts(overrideArguments=None): | ||||
|                   'for example with -o \'/my/downloads/%(uploader)s/%(title)s-%(id)s.%(ext)s\' .')) | ||||
|     filesystem.add_option('--autonumber-size', | ||||
|             dest='autonumber_size', metavar='NUMBER', | ||||
|             help='Specifies the number of digits in %(autonumber)s when it is present in output filename template or --autonumber option is given') | ||||
|             help='Specifies the number of digits in %(autonumber)s when it is present in output filename template or --auto-number option is given') | ||||
|     filesystem.add_option('--restrict-filenames', | ||||
|             action='store_true', dest='restrictfilenames', | ||||
|             help='Restrict filenames to only ASCII characters, and avoid "&" and spaces in filenames', default=False) | ||||
| @@ -286,7 +360,7 @@ def parseOpts(overrideArguments=None): | ||||
|     filesystem.add_option('-w', '--no-overwrites', | ||||
|             action='store_true', dest='nooverwrites', help='do not overwrite files', default=False) | ||||
|     filesystem.add_option('-c', '--continue', | ||||
|             action='store_true', dest='continue_dl', help='resume partially downloaded files', default=True) | ||||
|             action='store_true', dest='continue_dl', help='force resume of partially downloaded files. By default, youtube-dl will resume downloads if possible.', default=True) | ||||
|     filesystem.add_option('--no-continue', | ||||
|             action='store_false', dest='continue_dl', | ||||
|             help='do not resume partially downloaded files (restart from beginning)') | ||||
| @@ -303,6 +377,9 @@ def parseOpts(overrideArguments=None): | ||||
|     filesystem.add_option('--write-info-json', | ||||
|             action='store_true', dest='writeinfojson', | ||||
|             help='write video metadata to a .info.json file', default=False) | ||||
|     filesystem.add_option('--write-annotations', | ||||
|             action='store_true', dest='writeannotations', | ||||
|             help='write video annotations to a .annotation file', default=False) | ||||
|     filesystem.add_option('--write-thumbnail', | ||||
|             action='store_true', dest='writethumbnail', | ||||
|             help='write thumbnail image to disk', default=False) | ||||
| @@ -320,6 +397,10 @@ def parseOpts(overrideArguments=None): | ||||
|             help='keeps the video file on disk after the post-processing; the video is erased by default') | ||||
|     postproc.add_option('--no-post-overwrites', action='store_true', dest='nopostoverwrites', default=False, | ||||
|             help='do not overwrite post-processed files; the post-processed files are overwritten by default') | ||||
|     postproc.add_option('--embed-subs', action='store_true', dest='embedsubtitles', default=False, | ||||
|             help='embed subtitles in the video (only for mp4 videos)') | ||||
|     postproc.add_option('--add-metadata', action='store_true', dest='addmetadata', default=False, | ||||
|             help='add metadata to the files') | ||||
|  | ||||
|  | ||||
|     parser.add_option_group(general) | ||||
| @@ -328,28 +409,33 @@ def parseOpts(overrideArguments=None): | ||||
|     parser.add_option_group(filesystem) | ||||
|     parser.add_option_group(verbosity) | ||||
|     parser.add_option_group(video_format) | ||||
|     parser.add_option_group(subtitles) | ||||
|     parser.add_option_group(authentication) | ||||
|     parser.add_option_group(postproc) | ||||
|  | ||||
|     if overrideArguments is not None: | ||||
|         opts, args = parser.parse_args(overrideArguments) | ||||
|         if opts.verbose: | ||||
|             sys.stderr.write(u'[debug] Override config: ' + repr(overrideArguments) + '\n') | ||||
|             write_string(u'[debug] Override config: ' + repr(overrideArguments) + '\n') | ||||
|     else: | ||||
|         xdg_config_home = os.environ.get('XDG_CONFIG_HOME') | ||||
|         if xdg_config_home: | ||||
|             userConfFile = os.path.join(xdg_config_home, 'youtube-dl.conf') | ||||
|             userConfFile = os.path.join(xdg_config_home, 'youtube-dl', 'config') | ||||
|             if not os.path.isfile(userConfFile): | ||||
|                 userConfFile = os.path.join(xdg_config_home, 'youtube-dl.conf') | ||||
|         else: | ||||
|             userConfFile = os.path.join(os.path.expanduser('~'), '.config', 'youtube-dl.conf') | ||||
|             userConfFile = os.path.join(os.path.expanduser('~'), '.config', 'youtube-dl', 'config') | ||||
|             if not os.path.isfile(userConfFile): | ||||
|                 userConfFile = os.path.join(os.path.expanduser('~'), '.config', 'youtube-dl.conf') | ||||
|         systemConf = _readOptions('/etc/youtube-dl.conf') | ||||
|         userConf = _readOptions(userConfFile) | ||||
|         commandLineConf = sys.argv[1:]  | ||||
|         commandLineConf = sys.argv[1:] | ||||
|         argv = systemConf + userConf + commandLineConf | ||||
|         opts, args = parser.parse_args(argv) | ||||
|         if opts.verbose: | ||||
|             sys.stderr.write(u'[debug] System config: ' + repr(systemConf) + '\n') | ||||
|             sys.stderr.write(u'[debug] User config: ' + repr(userConf) + '\n') | ||||
|             sys.stderr.write(u'[debug] Command-line args: ' + repr(commandLineConf) + '\n') | ||||
|             write_string(u'[debug] System config: ' + repr(_hide_login_info(systemConf)) + '\n') | ||||
|             write_string(u'[debug] User config: ' + repr(_hide_login_info(userConf)) + '\n') | ||||
|             write_string(u'[debug] Command-line args: ' + repr(_hide_login_info(commandLineConf)) + '\n') | ||||
|  | ||||
|     return parser, opts, args | ||||
|  | ||||
| @@ -372,12 +458,12 @@ def _real_main(argv=None): | ||||
|         except (IOError, OSError) as err: | ||||
|             if opts.verbose: | ||||
|                 traceback.print_exc() | ||||
|             sys.stderr.write(u'ERROR: unable to open cookie file\n') | ||||
|             write_string(u'ERROR: unable to open cookie file\n') | ||||
|             sys.exit(101) | ||||
|     # Set user agent | ||||
|     if opts.user_agent is not None: | ||||
|         std_headers['User-Agent'] = opts.user_agent | ||||
|      | ||||
|  | ||||
|     # Set referer | ||||
|     if opts.referer is not None: | ||||
|         std_headers['Referer'] = opts.referer | ||||
| @@ -398,28 +484,14 @@ def _real_main(argv=None): | ||||
|             batchurls = batchfd.readlines() | ||||
|             batchurls = [x.strip() for x in batchurls] | ||||
|             batchurls = [x for x in batchurls if len(x) > 0 and not re.search(r'^[#/;]', x)] | ||||
|             if opts.verbose: | ||||
|                 write_string(u'[debug] Batch file urls: ' + repr(batchurls) + u'\n') | ||||
|         except IOError: | ||||
|             sys.exit(u'ERROR: batch file could not be read') | ||||
|     all_urls = batchurls + args | ||||
|     all_urls = [url.strip() for url in all_urls] | ||||
|  | ||||
|     # General configuration | ||||
|     cookie_processor = compat_urllib_request.HTTPCookieProcessor(jar) | ||||
|     if opts.proxy is not None: | ||||
|         if opts.proxy == '': | ||||
|             proxies = {} | ||||
|         else: | ||||
|             proxies = {'http': opts.proxy, 'https': opts.proxy} | ||||
|     else: | ||||
|         proxies = compat_urllib_request.getproxies() | ||||
|         # Set HTTPS proxy to HTTP one if given (https://github.com/rg3/youtube-dl/issues/805) | ||||
|         if 'http' in proxies and 'https' not in proxies: | ||||
|             proxies['https'] = proxies['http'] | ||||
|     proxy_handler = compat_urllib_request.ProxyHandler(proxies) | ||||
|     https_handler = make_HTTPS_handler(opts) | ||||
|     opener = compat_urllib_request.build_opener(https_handler, proxy_handler, cookie_processor, YoutubeDLHandler()) | ||||
|     compat_urllib_request.install_opener(opener) | ||||
|     socket.setdefaulttimeout(300) # 5 minutes should be enough (famous last words) | ||||
|     opener = _setup_opener(jar=jar, opts=opts) | ||||
|  | ||||
|     extractors = gen_extractors() | ||||
|  | ||||
| @@ -436,6 +508,8 @@ def _real_main(argv=None): | ||||
|             if not ie._WORKING: | ||||
|                 continue | ||||
|             desc = getattr(ie, 'IE_DESC', ie.IE_NAME) | ||||
|             if desc is False: | ||||
|                 continue | ||||
|             if hasattr(ie, 'SEARCH_KEY'): | ||||
|                 _SEARCHES = (u'cute kittens', u'slithering pythons', u'falling cat', u'angry poodle', u'purple fish', u'running tortoise') | ||||
|                 _COUNTS = (u'', u'5', u'10', u'all') | ||||
| @@ -507,6 +581,11 @@ def _real_main(argv=None): | ||||
|     else: | ||||
|         date = DateRange(opts.dateafter, opts.datebefore) | ||||
|  | ||||
|     # --all-sub automatically sets --write-sub if --write-auto-sub is not given | ||||
|     # this was the old behaviour if only --all-sub was given. | ||||
|     if opts.allsubtitles and (opts.writeautomaticsub == False): | ||||
|         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: | ||||
| @@ -519,9 +598,12 @@ def _real_main(argv=None): | ||||
|             or (opts.useid and u'%(id)s.%(ext)s') | ||||
|             or (opts.autonumber and u'%(autonumber)s-%(id)s.%(ext)s') | ||||
|             or u'%(title)s-%(id)s.%(ext)s') | ||||
|     if '%(ext)s' not in outtmpl and opts.extractaudio: | ||||
|         parser.error(u'Cannot download a video and extract audio into the same' | ||||
|                      u' file! Use "%%(ext)s" instead of %r' % | ||||
|                      determine_ext(outtmpl, u'')) | ||||
|  | ||||
|     # YoutubeDL | ||||
|     ydl = YoutubeDL({ | ||||
|     ydl_opts = { | ||||
|         'usenetrc': opts.usenetrc, | ||||
|         'username': opts.username, | ||||
|         'password': opts.password, | ||||
| @@ -553,11 +635,13 @@ def _real_main(argv=None): | ||||
|         'progress_with_newline': opts.progress_with_newline, | ||||
|         'playliststart': opts.playliststart, | ||||
|         'playlistend': opts.playlistend, | ||||
|         'noplaylist': opts.noplaylist, | ||||
|         'logtostderr': opts.outtmpl == '-', | ||||
|         'consoletitle': opts.consoletitle, | ||||
|         'nopart': opts.nopart, | ||||
|         'updatetime': opts.updatetime, | ||||
|         'writedescription': opts.writedescription, | ||||
|         'writeannotations': opts.writeannotations, | ||||
|         'writeinfojson': opts.writeinfojson, | ||||
|         'writethumbnail': opts.writethumbnail, | ||||
|         'writesubtitles': opts.writesubtitles, | ||||
| @@ -565,73 +649,121 @@ def _real_main(argv=None): | ||||
|         'allsubtitles': opts.allsubtitles, | ||||
|         'listsubtitles': opts.listsubtitles, | ||||
|         'subtitlesformat': opts.subtitlesformat, | ||||
|         'subtitleslang': opts.subtitleslang, | ||||
|         'subtitleslangs': opts.subtitleslangs, | ||||
|         'matchtitle': decodeOption(opts.matchtitle), | ||||
|         'rejecttitle': decodeOption(opts.rejecttitle), | ||||
|         'max_downloads': opts.max_downloads, | ||||
|         'prefer_free_formats': opts.prefer_free_formats, | ||||
|         'verbose': opts.verbose, | ||||
|         'dump_intermediate_pages': opts.dump_intermediate_pages, | ||||
|         'write_pages': opts.write_pages, | ||||
|         'test': opts.test, | ||||
|         'keepvideo': opts.keepvideo, | ||||
|         'min_filesize': opts.min_filesize, | ||||
|         'max_filesize': opts.max_filesize, | ||||
|         'daterange': date, | ||||
|         }) | ||||
|         'cachedir': opts.cachedir, | ||||
|         'youtube_print_sig_code': opts.youtube_print_sig_code, | ||||
|         'age_limit': opts.age_limit, | ||||
|         'download_archive': opts.download_archive, | ||||
|     } | ||||
|  | ||||
|     if opts.verbose: | ||||
|         sys.stderr.write(u'[debug] youtube-dl version ' + __version__ + u'\n') | ||||
|         try: | ||||
|             sp = subprocess.Popen( | ||||
|                 ['git', 'rev-parse', '--short', 'HEAD'], | ||||
|                 stdout=subprocess.PIPE, stderr=subprocess.PIPE, | ||||
|                 cwd=os.path.dirname(os.path.abspath(__file__))) | ||||
|             out, err = sp.communicate() | ||||
|             out = out.decode().strip() | ||||
|             if re.match('[0-9a-f]+', out): | ||||
|                 sys.stderr.write(u'[debug] Git HEAD: ' + out + u'\n') | ||||
|         except: | ||||
|     with YoutubeDL(ydl_opts) as ydl: | ||||
|         if opts.verbose: | ||||
|             write_string(u'[debug] youtube-dl version ' + __version__ + u'\n') | ||||
|             try: | ||||
|                 sys.exc_clear() | ||||
|                 sp = subprocess.Popen( | ||||
|                     ['git', 'rev-parse', '--short', 'HEAD'], | ||||
|                     stdout=subprocess.PIPE, stderr=subprocess.PIPE, | ||||
|                     cwd=os.path.dirname(os.path.abspath(__file__))) | ||||
|                 out, err = sp.communicate() | ||||
|                 out = out.decode().strip() | ||||
|                 if re.match('[0-9a-f]+', out): | ||||
|                     write_string(u'[debug] Git HEAD: ' + out + u'\n') | ||||
|             except: | ||||
|                 pass | ||||
|         sys.stderr.write(u'[debug] Python version %s - %s' %(platform.python_version(), platform.platform()) + u'\n') | ||||
|         sys.stderr.write(u'[debug] Proxy map: ' + str(proxy_handler.proxies) + u'\n') | ||||
|                 try: | ||||
|                     sys.exc_clear() | ||||
|                 except: | ||||
|                     pass | ||||
|             write_string(u'[debug] Python version %s - %s' % | ||||
|                          (platform.python_version(), platform_name()) + u'\n') | ||||
|  | ||||
|     ydl.add_default_info_extractors() | ||||
|             proxy_map = {} | ||||
|             for handler in opener.handlers: | ||||
|                 if hasattr(handler, 'proxies'): | ||||
|                     proxy_map.update(handler.proxies) | ||||
|             write_string(u'[debug] Proxy map: ' + compat_str(proxy_map) + u'\n') | ||||
|  | ||||
|     # PostProcessors | ||||
|     if opts.extractaudio: | ||||
|         ydl.add_post_processor(FFmpegExtractAudioPP(preferredcodec=opts.audioformat, preferredquality=opts.audioquality, nopostoverwrites=opts.nopostoverwrites)) | ||||
|     if opts.recodevideo: | ||||
|         ydl.add_post_processor(FFmpegVideoConvertor(preferedformat=opts.recodevideo)) | ||||
|         ydl.add_default_info_extractors() | ||||
|  | ||||
|     # Update version | ||||
|     if opts.update_self: | ||||
|         update_self(ydl.to_screen, opts.verbose, sys.argv[0]) | ||||
|         # PostProcessors | ||||
|         # Add the metadata pp first, the other pps will copy it | ||||
|         if opts.addmetadata: | ||||
|             ydl.add_post_processor(FFmpegMetadataPP()) | ||||
|         if opts.extractaudio: | ||||
|             ydl.add_post_processor(FFmpegExtractAudioPP(preferredcodec=opts.audioformat, preferredquality=opts.audioquality, nopostoverwrites=opts.nopostoverwrites)) | ||||
|         if opts.recodevideo: | ||||
|             ydl.add_post_processor(FFmpegVideoConvertor(preferedformat=opts.recodevideo)) | ||||
|         if opts.embedsubtitles: | ||||
|             ydl.add_post_processor(FFmpegEmbedSubtitlePP(subtitlesformat=opts.subtitlesformat)) | ||||
|  | ||||
|     # Maybe do nothing | ||||
|     if len(all_urls) < 1: | ||||
|         if not opts.update_self: | ||||
|             parser.error(u'you must provide at least one URL') | ||||
|         else: | ||||
|             sys.exit() | ||||
|         # Update version | ||||
|         if opts.update_self: | ||||
|             update_self(ydl.to_screen, opts.verbose) | ||||
|  | ||||
|     try: | ||||
|         retcode = ydl.download(all_urls) | ||||
|     except MaxDownloadsReached: | ||||
|         ydl.to_screen(u'--max-download limit reached, aborting.') | ||||
|         retcode = 101 | ||||
|         # Maybe do nothing | ||||
|         if len(all_urls) < 1: | ||||
|             if not opts.update_self: | ||||
|                 parser.error(u'you must provide at least one URL') | ||||
|             else: | ||||
|                 sys.exit() | ||||
|  | ||||
|         try: | ||||
|             retcode = ydl.download(all_urls) | ||||
|         except MaxDownloadsReached: | ||||
|             ydl.to_screen(u'--max-download limit reached, aborting.') | ||||
|             retcode = 101 | ||||
|  | ||||
|     # Dump cookie jar if requested | ||||
|     if opts.cookiefile is not None: | ||||
|         try: | ||||
|             jar.save() | ||||
|         except (IOError, OSError) as err: | ||||
|         except (IOError, OSError): | ||||
|             sys.exit(u'ERROR: unable to save cookie jar') | ||||
|  | ||||
|     sys.exit(retcode) | ||||
|  | ||||
|  | ||||
| def _setup_opener(jar=None, opts=None, timeout=300): | ||||
|     if opts is None: | ||||
|         FakeOptions = collections.namedtuple( | ||||
|             'FakeOptions', ['proxy', 'no_check_certificate']) | ||||
|         opts = FakeOptions(proxy=None, no_check_certificate=False) | ||||
|  | ||||
|     cookie_processor = compat_urllib_request.HTTPCookieProcessor(jar) | ||||
|     if opts.proxy is not None: | ||||
|         if opts.proxy == '': | ||||
|             proxies = {} | ||||
|         else: | ||||
|             proxies = {'http': opts.proxy, 'https': opts.proxy} | ||||
|     else: | ||||
|         proxies = compat_urllib_request.getproxies() | ||||
|         # Set HTTPS proxy to HTTP one if given (https://github.com/rg3/youtube-dl/issues/805) | ||||
|         if 'http' in proxies and 'https' not in proxies: | ||||
|             proxies['https'] = proxies['http'] | ||||
|     proxy_handler = compat_urllib_request.ProxyHandler(proxies) | ||||
|     https_handler = make_HTTPS_handler(opts) | ||||
|     opener = compat_urllib_request.build_opener( | ||||
|         https_handler, proxy_handler, cookie_processor, YoutubeDLHandler()) | ||||
|     # Delete the default user-agent header, which would otherwise apply in | ||||
|     # cases where our custom HTTP handler doesn't come into play | ||||
|     # (See https://github.com/rg3/youtube-dl/issues/1309 for details) | ||||
|     opener.addheaders = [] | ||||
|     compat_urllib_request.install_opener(opener) | ||||
|     socket.setdefaulttimeout(timeout) | ||||
|     return opener | ||||
|  | ||||
|  | ||||
| def main(argv=None): | ||||
|     try: | ||||
|         _real_main(argv) | ||||
|   | ||||
							
								
								
									
										202
									
								
								youtube_dl/aes.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								youtube_dl/aes.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,202 @@ | ||||
| __all__ = ['aes_encrypt', 'key_expansion', 'aes_ctr_decrypt', 'aes_decrypt_text'] | ||||
|  | ||||
| import base64 | ||||
| from math import ceil | ||||
|  | ||||
| from .utils import bytes_to_intlist, intlist_to_bytes | ||||
|  | ||||
| BLOCK_SIZE_BYTES = 16 | ||||
|  | ||||
| def aes_ctr_decrypt(data, key, counter): | ||||
|     """ | ||||
|     Decrypt with aes in counter mode | ||||
|      | ||||
|     @param {int[]} data        cipher | ||||
|     @param {int[]} key         16/24/32-Byte cipher key | ||||
|     @param {instance} counter  Instance whose next_value function (@returns {int[]}  16-Byte block) | ||||
|                                returns the next counter block | ||||
|     @returns {int[]}           decrypted data | ||||
|     """ | ||||
|     expanded_key = key_expansion(key) | ||||
|     block_count = int(ceil(float(len(data)) / BLOCK_SIZE_BYTES)) | ||||
|      | ||||
|     decrypted_data=[] | ||||
|     for i in range(block_count): | ||||
|         counter_block = counter.next_value() | ||||
|         block = data[i*BLOCK_SIZE_BYTES : (i+1)*BLOCK_SIZE_BYTES] | ||||
|         block += [0]*(BLOCK_SIZE_BYTES - len(block)) | ||||
|          | ||||
|         cipher_counter_block = aes_encrypt(counter_block, expanded_key) | ||||
|         decrypted_data += xor(block, cipher_counter_block) | ||||
|     decrypted_data = decrypted_data[:len(data)] | ||||
|      | ||||
|     return decrypted_data | ||||
|  | ||||
| def key_expansion(data): | ||||
|     """ | ||||
|     Generate key schedule | ||||
|      | ||||
|     @param {int[]} data  16/24/32-Byte cipher key | ||||
|     @returns {int[]}     176/208/240-Byte expanded key  | ||||
|     """ | ||||
|     data = data[:] # copy | ||||
|     rcon_iteration = 1 | ||||
|     key_size_bytes = len(data) | ||||
|     expanded_key_size_bytes = (key_size_bytes // 4 + 7) * BLOCK_SIZE_BYTES | ||||
|      | ||||
|     while len(data) < expanded_key_size_bytes: | ||||
|         temp = data[-4:] | ||||
|         temp = key_schedule_core(temp, rcon_iteration) | ||||
|         rcon_iteration += 1 | ||||
|         data += xor(temp, data[-key_size_bytes : 4-key_size_bytes]) | ||||
|          | ||||
|         for _ in range(3): | ||||
|             temp = data[-4:] | ||||
|             data += xor(temp, data[-key_size_bytes : 4-key_size_bytes]) | ||||
|          | ||||
|         if key_size_bytes == 32: | ||||
|             temp = data[-4:] | ||||
|             temp = sub_bytes(temp) | ||||
|             data += xor(temp, data[-key_size_bytes : 4-key_size_bytes]) | ||||
|          | ||||
|         for _ in range(3 if key_size_bytes == 32  else 2 if key_size_bytes == 24 else 0): | ||||
|             temp = data[-4:] | ||||
|             data += xor(temp, data[-key_size_bytes : 4-key_size_bytes]) | ||||
|     data = data[:expanded_key_size_bytes] | ||||
|      | ||||
|     return data | ||||
|  | ||||
| def aes_encrypt(data, expanded_key): | ||||
|     """ | ||||
|     Encrypt one block with aes | ||||
|      | ||||
|     @param {int[]} data          16-Byte state | ||||
|     @param {int[]} expanded_key  176/208/240-Byte expanded key  | ||||
|     @returns {int[]}             16-Byte cipher | ||||
|     """ | ||||
|     rounds = len(expanded_key) // BLOCK_SIZE_BYTES - 1 | ||||
|      | ||||
|     data = xor(data, expanded_key[:BLOCK_SIZE_BYTES]) | ||||
|     for i in range(1, rounds+1): | ||||
|         data = sub_bytes(data) | ||||
|         data = shift_rows(data) | ||||
|         if i != rounds: | ||||
|             data = mix_columns(data) | ||||
|         data = xor(data, expanded_key[i*BLOCK_SIZE_BYTES : (i+1)*BLOCK_SIZE_BYTES]) | ||||
|      | ||||
|     return data | ||||
|  | ||||
| def aes_decrypt_text(data, password, key_size_bytes): | ||||
|     """ | ||||
|     Decrypt text | ||||
|     - The first 8 Bytes of decoded 'data' are the 8 high Bytes of the counter | ||||
|     - The cipher key is retrieved by encrypting the first 16 Byte of 'password' | ||||
|       with the first 'key_size_bytes' Bytes from 'password' (if necessary filled with 0's) | ||||
|     - Mode of operation is 'counter' | ||||
|      | ||||
|     @param {str} data                    Base64 encoded string | ||||
|     @param {str,unicode} password        Password (will be encoded with utf-8) | ||||
|     @param {int} key_size_bytes          Possible values: 16 for 128-Bit, 24 for 192-Bit or 32 for 256-Bit | ||||
|     @returns {str}                       Decrypted data | ||||
|     """ | ||||
|     NONCE_LENGTH_BYTES = 8 | ||||
|      | ||||
|     data = bytes_to_intlist(base64.b64decode(data)) | ||||
|     password = bytes_to_intlist(password.encode('utf-8')) | ||||
|      | ||||
|     key = password[:key_size_bytes] + [0]*(key_size_bytes - len(password)) | ||||
|     key = aes_encrypt(key[:BLOCK_SIZE_BYTES], key_expansion(key)) * (key_size_bytes // BLOCK_SIZE_BYTES) | ||||
|      | ||||
|     nonce = data[:NONCE_LENGTH_BYTES] | ||||
|     cipher = data[NONCE_LENGTH_BYTES:] | ||||
|      | ||||
|     class Counter: | ||||
|         __value = nonce + [0]*(BLOCK_SIZE_BYTES - NONCE_LENGTH_BYTES) | ||||
|         def next_value(self): | ||||
|             temp = self.__value | ||||
|             self.__value = inc(self.__value) | ||||
|             return temp | ||||
|      | ||||
|     decrypted_data = aes_ctr_decrypt(cipher, key, Counter()) | ||||
|     plaintext = intlist_to_bytes(decrypted_data) | ||||
|      | ||||
|     return plaintext | ||||
|  | ||||
| RCON = (0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36) | ||||
| SBOX = (0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, | ||||
|         0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, | ||||
|         0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, | ||||
|         0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, | ||||
|         0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, | ||||
|         0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, | ||||
|         0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, | ||||
|         0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, | ||||
|         0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, | ||||
|         0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, | ||||
|         0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, | ||||
|         0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, | ||||
|         0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, | ||||
|         0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, | ||||
|         0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, | ||||
|         0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16) | ||||
| MIX_COLUMN_MATRIX = ((2,3,1,1), | ||||
|                      (1,2,3,1), | ||||
|                      (1,1,2,3), | ||||
|                      (3,1,1,2)) | ||||
|  | ||||
| def sub_bytes(data): | ||||
|     return [SBOX[x] for x in data] | ||||
|  | ||||
| def rotate(data): | ||||
|     return data[1:] + [data[0]] | ||||
|  | ||||
| def key_schedule_core(data, rcon_iteration): | ||||
|     data = rotate(data) | ||||
|     data = sub_bytes(data) | ||||
|     data[0] = data[0] ^ RCON[rcon_iteration] | ||||
|      | ||||
|     return data | ||||
|  | ||||
| def xor(data1, data2): | ||||
|     return [x^y for x, y in zip(data1, data2)] | ||||
|  | ||||
| def mix_column(data): | ||||
|     data_mixed = [] | ||||
|     for row in range(4): | ||||
|         mixed = 0 | ||||
|         for column in range(4): | ||||
|             addend = data[column] | ||||
|             if MIX_COLUMN_MATRIX[row][column] in (2,3): | ||||
|                 addend <<= 1 | ||||
|                 if addend > 0xff: | ||||
|                     addend &= 0xff | ||||
|                     addend ^= 0x1b | ||||
|                 if MIX_COLUMN_MATRIX[row][column] == 3: | ||||
|                     addend ^= data[column] | ||||
|             mixed ^= addend & 0xff | ||||
|         data_mixed.append(mixed) | ||||
|     return data_mixed | ||||
|  | ||||
| def mix_columns(data): | ||||
|     data_mixed = [] | ||||
|     for i in range(4): | ||||
|         column = data[i*4 : (i+1)*4] | ||||
|         data_mixed += mix_column(column) | ||||
|     return data_mixed | ||||
|  | ||||
| def shift_rows(data): | ||||
|     data_shifted = [] | ||||
|     for column in range(4): | ||||
|         for row in range(4): | ||||
|             data_shifted.append( data[((column + row) & 0b11) * 4 + row] ) | ||||
|     return data_shifted | ||||
|  | ||||
| def inc(data): | ||||
|     data = data[:] # copy | ||||
|     for i in range(len(data)-1,-1,-1): | ||||
|         if data[i] == 255: | ||||
|             data[i] = 0 | ||||
|         else: | ||||
|             data[i] = data[i] + 1 | ||||
|             break | ||||
|     return data | ||||
| @@ -1,34 +1,69 @@ | ||||
| from .appletrailers import AppleTrailersIE | ||||
| from .addanime import AddAnimeIE | ||||
| from .archiveorg import ArchiveOrgIE | ||||
| from .ard import ARDIE | ||||
| from .arte import ArteTvIE | ||||
| from .arte import ( | ||||
|     ArteTvIE, | ||||
|     ArteTVPlus7IE, | ||||
|     ArteTVCreativeIE, | ||||
|     ArteTVFutureIE, | ||||
| ) | ||||
| from .auengine import AUEngineIE | ||||
| from .bambuser import BambuserIE, BambuserChannelIE | ||||
| from .bandcamp import BandcampIE | ||||
| from .bliptv import BlipTVIE, BlipTVUserIE | ||||
| from .bloomberg import BloombergIE | ||||
| from .breakcom import BreakIE | ||||
| from .brightcove import BrightcoveIE | ||||
| from .c56 import C56IE | ||||
| from .canalplus import CanalplusIE | ||||
| from .canalc2 import Canalc2IE | ||||
| from .cinemassacre import CinemassacreIE | ||||
| from .cnn import CNNIE | ||||
| from .collegehumor import CollegeHumorIE | ||||
| from .comedycentral import ComedyCentralIE | ||||
| from .condenast import CondeNastIE | ||||
| from .criterion import CriterionIE | ||||
| from .cspan import CSpanIE | ||||
| from .dailymotion import DailymotionIE | ||||
| from .dailymotion import ( | ||||
|     DailymotionIE, | ||||
|     DailymotionPlaylistIE, | ||||
|     DailymotionUserIE, | ||||
| ) | ||||
| from .daum import DaumIE | ||||
| from .depositfiles import DepositFilesIE | ||||
| from .dotsub import DotsubIE | ||||
| from .dreisat import DreiSatIE | ||||
| from .defense import DefenseGouvFrIE | ||||
| from .ebaumsworld import EbaumsWorldIE | ||||
| from .ehow import EHowIE | ||||
| from .eighttracks import EightTracksIE | ||||
| from .eitb import EitbIE | ||||
| from .escapist import EscapistIE | ||||
| from .exfm import ExfmIE | ||||
| from .extremetube import ExtremeTubeIE | ||||
| from .facebook import FacebookIE | ||||
| from .faz import FazIE | ||||
| from .fktv import ( | ||||
|     FKTVIE, | ||||
|     FKTVPosteckeIE, | ||||
| ) | ||||
| from .flickr import FlickrIE | ||||
| from .francetv import ( | ||||
|     PluzzIE, | ||||
|     FranceTvInfoIE, | ||||
|     France2IE, | ||||
|     GenerationQuoiIE | ||||
| ) | ||||
| from .freesound import FreesoundIE | ||||
| from .funnyordie import FunnyOrDieIE | ||||
| from .gamekings import GamekingsIE | ||||
| from .gamespot import GameSpotIE | ||||
| from .gametrailers import GametrailersIE | ||||
| from .generic import GenericIE | ||||
| from .googleplus import GooglePlusIE | ||||
| from .googlesearch import GoogleSearchIE | ||||
| from .hark import HarkIE | ||||
| from .hotnewhiphop import HotNewHipHopIE | ||||
| from .howcast import HowcastIE | ||||
| from .hypem import HypemIE | ||||
| @@ -36,50 +71,97 @@ from .ign import IGNIE, OneUPIE | ||||
| from .ina import InaIE | ||||
| from .infoq import InfoQIE | ||||
| from .instagram import InstagramIE | ||||
| from .internetvideoarchive import InternetVideoArchiveIE | ||||
| from .jeuxvideo import JeuxVideoIE | ||||
| from .jukebox import JukeboxIE | ||||
| from .justintv import JustinTVIE | ||||
| from .kankan import KankanIE | ||||
| from .keezmovies import KeezMoviesIE | ||||
| from .kickstarter import KickStarterIE | ||||
| from .keek import KeekIE | ||||
| from .liveleak import LiveLeakIE | ||||
| from .livestream import LivestreamIE | ||||
| from .livestream import LivestreamIE, LivestreamOriginalIE | ||||
| from .metacafe import MetacafeIE | ||||
| from .metacritic import MetacriticIE | ||||
| from .mit import TechTVMITIE, MITIE | ||||
| from .mixcloud import MixcloudIE | ||||
| from .mofosex import MofosexIE | ||||
| from .mtv import MTVIE | ||||
| from .muzu import MuzuTVIE | ||||
| from .myspace import MySpaceIE | ||||
| from .myspass import MySpassIE | ||||
| from .myvideo import MyVideoIE | ||||
| from .naver import NaverIE | ||||
| from .nba import NBAIE | ||||
| from .nbc import NBCNewsIE | ||||
| from .newgrounds import NewgroundsIE | ||||
| from .nhl import NHLIE, NHLVideocenterIE | ||||
| from .nowvideo import NowVideoIE | ||||
| from .ooyala import OoyalaIE | ||||
| from .orf import ORFIE | ||||
| from .pbs import PBSIE | ||||
| from .photobucket import PhotobucketIE | ||||
| from .pornhub import PornHubIE | ||||
| from .pornotube import PornotubeIE | ||||
| from .rbmaradio import RBMARadioIE | ||||
| from .redtube import RedTubeIE | ||||
| from .ringtv import RingTVIE | ||||
| from .ro220 import Ro220IE | ||||
| from .rottentomatoes import RottenTomatoesIE | ||||
| from .roxwel import RoxwelIE | ||||
| from .rtlnow import RTLnowIE | ||||
| from .rutube import RutubeIE | ||||
| from .sina import SinaIE | ||||
| from .soundcloud import SoundcloudIE, SoundcloudSetIE | ||||
| from .slashdot import SlashdotIE | ||||
| from .slideshare import SlideshareIE | ||||
| from .sohu import SohuIE | ||||
| from .soundcloud import SoundcloudIE, SoundcloudSetIE, SoundcloudUserIE | ||||
| from .southparkstudios import ( | ||||
|     SouthParkStudiosIE, | ||||
|     SouthparkDeIE, | ||||
| ) | ||||
| from .space import SpaceIE | ||||
| from .spankwire import SpankwireIE | ||||
| from .spiegel import SpiegelIE | ||||
| from .stanfordoc import StanfordOpenClassroomIE | ||||
| from .statigram import StatigramIE | ||||
| from .steam import SteamIE | ||||
| from .sztvhu import SztvHuIE | ||||
| from .teamcoco import TeamcocoIE | ||||
| from .techtalks import TechTalksIE | ||||
| from .ted import TEDIE | ||||
| from .tf1 import TF1IE | ||||
| from .thisav import ThisAVIE | ||||
| from .traileraddict import TrailerAddictIE | ||||
| from .trilulilu import TriluliluIE | ||||
| from .tube8 import Tube8IE | ||||
| from .tudou import TudouIE | ||||
| from .tumblr import TumblrIE | ||||
| from .tutv import TutvIE | ||||
| from .ustream import UstreamIE | ||||
| from .tvp import TvpIE | ||||
| from .unistra import UnistraIE | ||||
| from .ustream import UstreamIE, UstreamChannelIE | ||||
| from .vbox7 import Vbox7IE | ||||
| from .veehd import VeeHDIE | ||||
| from .veoh import VeohIE | ||||
| from .vevo import VevoIE | ||||
| from .vimeo import VimeoIE | ||||
| from .vice import ViceIE | ||||
| from .viddler import ViddlerIE | ||||
| from .videodetective import VideoDetectiveIE | ||||
| from .videofyme import VideofyMeIE | ||||
| from .videopremium import VideoPremiumIE | ||||
| from .vimeo import VimeoIE, VimeoChannelIE | ||||
| from .vine import VineIE | ||||
| from .c56 import C56IE | ||||
| from .vk import VKIE | ||||
| from .wat import WatIE | ||||
| from .websurg import WeBSurgIE | ||||
| from .weibo import WeiboIE | ||||
| from .wimp import WimpIE | ||||
| from .worldstarhiphop import WorldStarHipHopIE | ||||
| from .xhamster import XHamsterIE | ||||
| from .xnxx import XNXXIE | ||||
| from .xvideos import XVideosIE | ||||
| from .xtube import XTubeIE | ||||
| from .yahoo import YahooIE, YahooSearchIE | ||||
| from .youjizz import YouJizzIE | ||||
| from .youku import YoukuIE | ||||
| @@ -88,11 +170,15 @@ from .youtube import ( | ||||
|     YoutubeIE, | ||||
|     YoutubePlaylistIE, | ||||
|     YoutubeSearchIE, | ||||
|     YoutubeSearchDateIE, | ||||
|     YoutubeUserIE, | ||||
|     YoutubeChannelIE, | ||||
|     YoutubeShowIE, | ||||
|     YoutubeSubscriptionsIE, | ||||
|     YoutubeRecommendedIE, | ||||
|     YoutubeTruncatedURLIE, | ||||
|     YoutubeWatchLaterIE, | ||||
|     YoutubeFavouritesIE, | ||||
| ) | ||||
| from .zdf import ZDFIE | ||||
|  | ||||
| @@ -104,12 +190,14 @@ _ALL_CLASSES = [ | ||||
| ] | ||||
| _ALL_CLASSES.append(GenericIE) | ||||
|  | ||||
|  | ||||
| def gen_extractors(): | ||||
|     """ Return a list of an instance of every supported extractor. | ||||
|     The order does matter; the first extractor matched is the one handling the URL. | ||||
|     """ | ||||
|     return [klass() for klass in _ALL_CLASSES] | ||||
|  | ||||
|  | ||||
| def get_info_extractor(ie_name): | ||||
|     """Returns the info extractor class with the given ie_name""" | ||||
|     return globals()[ie_name+'IE'] | ||||
|   | ||||
							
								
								
									
										86
									
								
								youtube_dl/extractor/addanime.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								youtube_dl/extractor/addanime.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | ||||
| import re | ||||
|  | ||||
| from .common import InfoExtractor | ||||
| from ..utils import ( | ||||
|     compat_HTTPError, | ||||
|     compat_str, | ||||
|     compat_urllib_parse, | ||||
|     compat_urllib_parse_urlparse, | ||||
|  | ||||
|     ExtractorError, | ||||
| ) | ||||
|  | ||||
|  | ||||
| class AddAnimeIE(InfoExtractor): | ||||
|  | ||||
|     _VALID_URL = r'^http://(?:\w+\.)?add-anime\.net/watch_video.php\?(?:.*?)v=(?P<video_id>[\w_]+)(?:.*)' | ||||
|     IE_NAME = u'AddAnime' | ||||
|     _TEST = { | ||||
|         u'url': u'http://www.add-anime.net/watch_video.php?v=24MR3YO5SAS9', | ||||
|         u'file': u'24MR3YO5SAS9.mp4', | ||||
|         u'md5': u'72954ea10bc979ab5e2eb288b21425a0', | ||||
|         u'info_dict': { | ||||
|             u"description": u"One Piece 606", | ||||
|             u"title": u"One Piece 606" | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         try: | ||||
|             mobj = re.match(self._VALID_URL, url) | ||||
|             video_id = mobj.group('video_id') | ||||
|             webpage = self._download_webpage(url, video_id) | ||||
|         except ExtractorError as ee: | ||||
|             if not isinstance(ee.cause, compat_HTTPError) or \ | ||||
|                ee.cause.code != 503: | ||||
|                 raise | ||||
|  | ||||
|             redir_webpage = ee.cause.read().decode('utf-8') | ||||
|             action = self._search_regex( | ||||
|                 r'<form id="challenge-form" action="([^"]+)"', | ||||
|                 redir_webpage, u'Redirect form') | ||||
|             vc = self._search_regex( | ||||
|                 r'<input type="hidden" name="jschl_vc" value="([^"]+)"/>', | ||||
|                 redir_webpage, u'redirect vc value') | ||||
|             av = re.search( | ||||
|                 r'a\.value = ([0-9]+)[+]([0-9]+)[*]([0-9]+);', | ||||
|                 redir_webpage) | ||||
|             if av is None: | ||||
|                 raise ExtractorError(u'Cannot find redirect math task') | ||||
|             av_res = int(av.group(1)) + int(av.group(2)) * int(av.group(3)) | ||||
|  | ||||
|             parsed_url = compat_urllib_parse_urlparse(url) | ||||
|             av_val = av_res + len(parsed_url.netloc) | ||||
|             confirm_url = ( | ||||
|                 parsed_url.scheme + u'://' + parsed_url.netloc + | ||||
|                 action + '?' + | ||||
|                 compat_urllib_parse.urlencode({ | ||||
|                     'jschl_vc': vc, 'jschl_answer': compat_str(av_val)})) | ||||
|             self._download_webpage( | ||||
|                 confirm_url, video_id, | ||||
|                 note=u'Confirming after redirect') | ||||
|             webpage = self._download_webpage(url, video_id) | ||||
|  | ||||
|         formats = [] | ||||
|         for format_id in ('normal', 'hq'): | ||||
|             rex = r"var %s_video_file = '(.*?)';" % re.escape(format_id) | ||||
|             video_url = self._search_regex(rex, webpage, u'video file URLx', | ||||
|                                            fatal=False) | ||||
|             if not video_url: | ||||
|                 continue | ||||
|             formats.append({ | ||||
|                 'format_id': format_id, | ||||
|                 'url': video_url, | ||||
|             }) | ||||
|         if not formats: | ||||
|             raise ExtractorError(u'Cannot find any video format!') | ||||
|         video_title = self._og_search_title(webpage) | ||||
|         video_description = self._og_search_description(webpage) | ||||
|  | ||||
|         return { | ||||
|             '_type': 'video', | ||||
|             'id':  video_id, | ||||
|             'formats': formats, | ||||
|             'title': video_title, | ||||
|             'description': video_description | ||||
|         } | ||||
							
								
								
									
										138
									
								
								youtube_dl/extractor/appletrailers.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								youtube_dl/extractor/appletrailers.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,138 @@ | ||||
| import re | ||||
| import xml.etree.ElementTree | ||||
| import json | ||||
|  | ||||
| from .common import InfoExtractor | ||||
| from ..utils import ( | ||||
|     compat_urlparse, | ||||
|     determine_ext, | ||||
| ) | ||||
|  | ||||
|  | ||||
| class AppleTrailersIE(InfoExtractor): | ||||
|     _VALID_URL = r'https?://(?:www\.)?trailers.apple.com/trailers/(?P<company>[^/]+)/(?P<movie>[^/]+)' | ||||
|     _TEST = { | ||||
|         u"url": u"http://trailers.apple.com/trailers/wb/manofsteel/", | ||||
|         u"playlist": [ | ||||
|             { | ||||
|                 u"file": u"manofsteel-trailer4.mov", | ||||
|                 u"md5": u"d97a8e575432dbcb81b7c3acb741f8a8", | ||||
|                 u"info_dict": { | ||||
|                     u"duration": 111, | ||||
|                     u"title": u"Trailer 4", | ||||
|                     u"upload_date": u"20130523", | ||||
|                     u"uploader_id": u"wb", | ||||
|                 }, | ||||
|             }, | ||||
|             { | ||||
|                 u"file": u"manofsteel-trailer3.mov", | ||||
|                 u"md5": u"b8017b7131b721fb4e8d6f49e1df908c", | ||||
|                 u"info_dict": { | ||||
|                     u"duration": 182, | ||||
|                     u"title": u"Trailer 3", | ||||
|                     u"upload_date": u"20130417", | ||||
|                     u"uploader_id": u"wb", | ||||
|                 }, | ||||
|             }, | ||||
|             { | ||||
|                 u"file": u"manofsteel-trailer.mov", | ||||
|                 u"md5": u"d0f1e1150989b9924679b441f3404d48", | ||||
|                 u"info_dict": { | ||||
|                     u"duration": 148, | ||||
|                     u"title": u"Trailer", | ||||
|                     u"upload_date": u"20121212", | ||||
|                     u"uploader_id": u"wb", | ||||
|                 }, | ||||
|             }, | ||||
|             { | ||||
|                 u"file": u"manofsteel-teaser.mov", | ||||
|                 u"md5": u"5fe08795b943eb2e757fa95cb6def1cb", | ||||
|                 u"info_dict": { | ||||
|                     u"duration": 93, | ||||
|                     u"title": u"Teaser", | ||||
|                     u"upload_date": u"20120721", | ||||
|                     u"uploader_id": u"wb", | ||||
|                 }, | ||||
|             } | ||||
|         ] | ||||
|     } | ||||
|  | ||||
|     _JSON_RE = r'iTunes.playURL\((.*?)\);' | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         movie = mobj.group('movie') | ||||
|         uploader_id = mobj.group('company') | ||||
|  | ||||
|         playlist_url = compat_urlparse.urljoin(url, u'includes/playlists/itunes.inc') | ||||
|         playlist_snippet = self._download_webpage(playlist_url, movie) | ||||
|         playlist_cleaned = re.sub(r'(?s)<script[^<]*?>.*?</script>', u'', playlist_snippet) | ||||
|         playlist_cleaned = re.sub(r'<img ([^<]*?)>', r'<img \1/>', playlist_cleaned) | ||||
|         # The ' in the onClick attributes are not escaped, it couldn't be parsed | ||||
|         # with xml.etree.ElementTree.fromstring | ||||
|         # like: http://trailers.apple.com/trailers/wb/gravity/ | ||||
|         def _clean_json(m): | ||||
|             return u'iTunes.playURL(%s);' % m.group(1).replace('\'', ''') | ||||
|         playlist_cleaned = re.sub(self._JSON_RE, _clean_json, playlist_cleaned) | ||||
|         playlist_html = u'<html>' + playlist_cleaned + u'</html>' | ||||
|  | ||||
|         doc = xml.etree.ElementTree.fromstring(playlist_html) | ||||
|         playlist = [] | ||||
|         for li in doc.findall('./div/ul/li'): | ||||
|             on_click = li.find('.//a').attrib['onClick'] | ||||
|             trailer_info_json = self._search_regex(self._JSON_RE, | ||||
|                 on_click, u'trailer info') | ||||
|             trailer_info = json.loads(trailer_info_json) | ||||
|             title = trailer_info['title'] | ||||
|             video_id = movie + '-' + re.sub(r'[^a-zA-Z0-9]', '', title).lower() | ||||
|             thumbnail = li.find('.//img').attrib['src'] | ||||
|             upload_date = trailer_info['posted'].replace('-', '') | ||||
|  | ||||
|             runtime = trailer_info['runtime'] | ||||
|             m = re.search(r'(?P<minutes>[0-9]+):(?P<seconds>[0-9]{1,2})', runtime) | ||||
|             duration = None | ||||
|             if m: | ||||
|                 duration = 60 * int(m.group('minutes')) + int(m.group('seconds')) | ||||
|  | ||||
|             first_url = trailer_info['url'] | ||||
|             trailer_id = first_url.split('/')[-1].rpartition('_')[0].lower() | ||||
|             settings_json_url = compat_urlparse.urljoin(url, 'includes/settings/%s.json' % trailer_id) | ||||
|             settings_json = self._download_webpage(settings_json_url, trailer_id, u'Downloading settings json') | ||||
|             settings = json.loads(settings_json) | ||||
|  | ||||
|             formats = [] | ||||
|             for format in settings['metadata']['sizes']: | ||||
|                 # The src is a file pointing to the real video file | ||||
|                 format_url = re.sub(r'_(\d*p.mov)', r'_h\1', format['src']) | ||||
|                 formats.append({ | ||||
|                     'url': format_url, | ||||
|                     'ext': determine_ext(format_url), | ||||
|                     'format': format['type'], | ||||
|                     'width': format['width'], | ||||
|                     'height': int(format['height']), | ||||
|                 }) | ||||
|             formats = sorted(formats, key=lambda f: (f['height'], f['width'])) | ||||
|  | ||||
|             info = { | ||||
|                 '_type': 'video', | ||||
|                 'id': video_id, | ||||
|                 'title': title, | ||||
|                 'formats': formats, | ||||
|                 'title': title, | ||||
|                 'duration': duration, | ||||
|                 'thumbnail': thumbnail, | ||||
|                 'upload_date': upload_date, | ||||
|                 'uploader_id': uploader_id, | ||||
|                 'user_agent': 'QuickTime compatible (youtube-dl)', | ||||
|             } | ||||
|             # TODO: Remove when #980 has been merged | ||||
|             info['url'] = formats[-1]['url'] | ||||
|             info['ext'] = formats[-1]['ext'] | ||||
|  | ||||
|             playlist.append(info) | ||||
|  | ||||
|         return { | ||||
|             '_type': 'playlist', | ||||
|             'id': movie, | ||||
|             'entries': playlist, | ||||
|         } | ||||
| @@ -46,6 +46,8 @@ class ArchiveOrgIE(InfoExtractor): | ||||
|             for fn,fdata in data['files'].items() | ||||
|             if 'Video' in fdata['format']] | ||||
|         formats.sort(key=lambda fdata: fdata['file_size']) | ||||
|         for f in formats: | ||||
|             f['ext'] = determine_ext(f['url']) | ||||
|  | ||||
|         info = { | ||||
|             '_type': 'video', | ||||
| @@ -61,7 +63,6 @@ class ArchiveOrgIE(InfoExtractor): | ||||
|             info['thumbnail'] = thumbnail | ||||
|  | ||||
|         # TODO: Remove when #980 has been merged | ||||
|         info['url'] = formats[-1]['url'] | ||||
|         info['ext'] = determine_ext(formats[-1]['url']) | ||||
|         info.update(formats[-1]) | ||||
|  | ||||
|         return info | ||||
|         return info | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| # encoding: utf-8 | ||||
| import re | ||||
| import json | ||||
| import xml.etree.ElementTree | ||||
| @@ -7,23 +8,25 @@ from ..utils import ( | ||||
|     ExtractorError, | ||||
|     find_xpath_attr, | ||||
|     unified_strdate, | ||||
|     determine_ext, | ||||
|     get_element_by_id, | ||||
|     compat_str, | ||||
| ) | ||||
|  | ||||
| # There are different sources of video in arte.tv, the extraction process  | ||||
| # is different for each one. The videos usually expire in 7 days, so we can't | ||||
| # add tests. | ||||
|  | ||||
| class ArteTvIE(InfoExtractor): | ||||
|     """ | ||||
|     There are two sources of video in arte.tv: videos.arte.tv and | ||||
|     www.arte.tv/guide, the extraction process is different for each one. | ||||
|     The videos expire in 7 days, so we can't add tests. | ||||
|     """ | ||||
|     _EMISSION_URL = r'(?:http://)?www\.arte.tv/guide/(?P<lang>fr|de)/(?:(?:sendungen|emissions)/)?(?P<id>.*?)/(?P<name>.*?)(\?.*)?' | ||||
|     _VIDEOS_URL = r'(?:http://)?videos.arte.tv/(?P<lang>fr|de)/.*-(?P<id>.*?).html' | ||||
|     _LIVEWEB_URL = r'(?:http://)?liveweb.arte.tv/(?P<lang>fr|de)/(?P<subpage>.+?)/(?P<name>.+)' | ||||
|     _LIVE_URL = r'index-[0-9]+\.html$' | ||||
|  | ||||
|     IE_NAME = u'arte.tv' | ||||
|  | ||||
|     @classmethod | ||||
|     def suitable(cls, url): | ||||
|         return any(re.match(regex, url) for regex in (cls._EMISSION_URL, cls._VIDEOS_URL)) | ||||
|         return any(re.match(regex, url) for regex in (cls._VIDEOS_URL, cls._LIVEWEB_URL)) | ||||
|  | ||||
|     # TODO implement Live Stream | ||||
|     # from ..utils import compat_urllib_parse | ||||
| @@ -54,66 +57,23 @@ class ArteTvIE(InfoExtractor): | ||||
|     #     video_url = u'%s/%s' % (info.get('url'), info.get('path')) | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._EMISSION_URL, url) | ||||
|         if mobj is not None: | ||||
|             lang = mobj.group('lang') | ||||
|             # This is not a real id, it can be for example AJT for the news | ||||
|             # http://www.arte.tv/guide/fr/emissions/AJT/arte-journal | ||||
|             video_id = mobj.group('id') | ||||
|             return self._extract_emission(url, video_id, lang) | ||||
|  | ||||
|         mobj = re.match(self._VIDEOS_URL, url) | ||||
|         if mobj is not None: | ||||
|             id = mobj.group('id') | ||||
|             lang = mobj.group('lang') | ||||
|             return self._extract_video(url, id, lang) | ||||
|  | ||||
|         if re.search(self._LIVE_URL, video_id) is not None: | ||||
|         mobj = re.match(self._LIVEWEB_URL, url) | ||||
|         if mobj is not None: | ||||
|             name = mobj.group('name') | ||||
|             lang = mobj.group('lang') | ||||
|             return self._extract_liveweb(url, name, lang) | ||||
|  | ||||
|         if re.search(self._LIVE_URL, url) is not None: | ||||
|             raise ExtractorError(u'Arte live streams are not yet supported, sorry') | ||||
|             # self.extractLiveStream(url) | ||||
|             # return | ||||
|  | ||||
|     def _extract_emission(self, url, video_id, lang): | ||||
|         """Extract from www.arte.tv/guide""" | ||||
|         webpage = self._download_webpage(url, video_id) | ||||
|         json_url = self._html_search_regex(r'arte_vp_url="(.*?)"', webpage, 'json url') | ||||
|  | ||||
|         json_info = self._download_webpage(json_url, video_id, 'Downloading info json') | ||||
|         self.report_extraction(video_id) | ||||
|         info = json.loads(json_info) | ||||
|         player_info = info['videoJsonPlayer'] | ||||
|  | ||||
|         info_dict = {'id': player_info['VID'], | ||||
|                      'title': player_info['VTI'], | ||||
|                      'description': player_info['VDE'], | ||||
|                      'upload_date': unified_strdate(player_info['VDA'].split(' ')[0]), | ||||
|                      'thumbnail': player_info['programImage'], | ||||
|                      'ext': 'flv', | ||||
|                      } | ||||
|  | ||||
|         formats = player_info['VSR'].values() | ||||
|         def _match_lang(f): | ||||
|             # Return true if that format is in the language of the url | ||||
|             if lang == 'fr': | ||||
|                 l = 'F' | ||||
|             elif lang == 'de': | ||||
|                 l = 'A' | ||||
|             regexes = [r'VO?%s' % l, r'V%s-ST.' % l] | ||||
|             return any(re.match(r, f['versionCode']) for r in regexes) | ||||
|         # Some formats may not be in the same language as the url | ||||
|         formats = filter(_match_lang, formats) | ||||
|         # We order the formats by quality | ||||
|         formats = sorted(formats, key=lambda f: int(f['height'])) | ||||
|         # Pick the best quality | ||||
|         format_info = formats[-1] | ||||
|         if format_info['mediaType'] == u'rtmp': | ||||
|             info_dict['url'] = format_info['streamer'] | ||||
|             info_dict['play_path'] = 'mp4:' + format_info['url'] | ||||
|         else: | ||||
|             info_dict['url'] = format_info['url'] | ||||
|  | ||||
|         return info_dict | ||||
|  | ||||
|     def _extract_video(self, url, video_id, lang): | ||||
|         """Extract from videos.arte.tv""" | ||||
|         ref_xml_url = url.replace('/videos/', '/do_delegate/videos/') | ||||
| @@ -144,3 +104,159 @@ class ArteTvIE(InfoExtractor): | ||||
|                 'url': video_url, | ||||
|                 'ext': 'flv', | ||||
|                 } | ||||
|  | ||||
|     def _extract_liveweb(self, url, name, lang): | ||||
|         """Extract form http://liveweb.arte.tv/""" | ||||
|         webpage = self._download_webpage(url, name) | ||||
|         video_id = self._search_regex(r'eventId=(\d+?)("|&)', webpage, u'event id') | ||||
|         config_xml = self._download_webpage('http://download.liveweb.arte.tv/o21/liveweb/events/event-%s.xml' % video_id, | ||||
|                                             video_id, u'Downloading information') | ||||
|         config_doc = xml.etree.ElementTree.fromstring(config_xml.encode('utf-8')) | ||||
|         event_doc = config_doc.find('event') | ||||
|         url_node = event_doc.find('video').find('urlHd') | ||||
|         if url_node is None: | ||||
|             url_node = event_doc.find('urlSd') | ||||
|  | ||||
|         return {'id': video_id, | ||||
|                 'title': event_doc.find('name%s' % lang.capitalize()).text, | ||||
|                 'url': url_node.text.replace('MP4', 'mp4'), | ||||
|                 'ext': 'flv', | ||||
|                 'thumbnail': self._og_search_thumbnail(webpage), | ||||
|                 } | ||||
|  | ||||
|  | ||||
| class ArteTVPlus7IE(InfoExtractor): | ||||
|     IE_NAME = u'arte.tv:+7' | ||||
|     _VALID_URL = r'https?://www\.arte.tv/guide/(?P<lang>fr|de)/(?:(?:sendungen|emissions)/)?(?P<id>.*?)/(?P<name>.*?)(\?.*)?' | ||||
|  | ||||
|     @classmethod | ||||
|     def _extract_url_info(cls, url): | ||||
|         mobj = re.match(cls._VALID_URL, url) | ||||
|         lang = mobj.group('lang') | ||||
|         # This is not a real id, it can be for example AJT for the news | ||||
|         # http://www.arte.tv/guide/fr/emissions/AJT/arte-journal | ||||
|         video_id = mobj.group('id') | ||||
|         return video_id, lang | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         video_id, lang = self._extract_url_info(url) | ||||
|         webpage = self._download_webpage(url, video_id) | ||||
|         return self._extract_from_webpage(webpage, video_id, lang) | ||||
|  | ||||
|     def _extract_from_webpage(self, webpage, video_id, lang): | ||||
|         json_url = self._html_search_regex(r'arte_vp_url="(.*?)"', webpage, 'json url') | ||||
|  | ||||
|         json_info = self._download_webpage(json_url, video_id, 'Downloading info json') | ||||
|         self.report_extraction(video_id) | ||||
|         info = json.loads(json_info) | ||||
|         player_info = info['videoJsonPlayer'] | ||||
|  | ||||
|         info_dict = { | ||||
|             'id': player_info['VID'], | ||||
|             'title': player_info['VTI'], | ||||
|             'description': player_info.get('VDE'), | ||||
|             'upload_date': unified_strdate(player_info.get('VDA', '').split(' ')[0]), | ||||
|             'thumbnail': player_info.get('programImage') or player_info.get('VTU', {}).get('IUR'), | ||||
|         } | ||||
|  | ||||
|         all_formats = player_info['VSR'].values() | ||||
|         # Some formats use the m3u8 protocol | ||||
|         all_formats = list(filter(lambda f: f.get('videoFormat') != 'M3U8', all_formats)) | ||||
|         def _match_lang(f): | ||||
|             if f.get('versionCode') is None: | ||||
|                 return True | ||||
|             # Return true if that format is in the language of the url | ||||
|             if lang == 'fr': | ||||
|                 l = 'F' | ||||
|             elif lang == 'de': | ||||
|                 l = 'A' | ||||
|             regexes = [r'VO?%s' % l, r'VO?.-ST%s' % l] | ||||
|             return any(re.match(r, f['versionCode']) for r in regexes) | ||||
|         # Some formats may not be in the same language as the url | ||||
|         formats = filter(_match_lang, all_formats) | ||||
|         formats = list(formats) # in python3 filter returns an iterator | ||||
|         if not formats: | ||||
|             # Some videos are only available in the 'Originalversion' | ||||
|             # they aren't tagged as being in French or German | ||||
|             if all(f['versionCode'] == 'VO' for f in all_formats): | ||||
|                 formats = all_formats | ||||
|             else: | ||||
|                 raise ExtractorError(u'The formats list is empty') | ||||
|  | ||||
|         if re.match(r'[A-Z]Q', formats[0]['quality']) is not None: | ||||
|             def sort_key(f): | ||||
|                 return ['HQ', 'MQ', 'EQ', 'SQ'].index(f['quality']) | ||||
|         else: | ||||
|             def sort_key(f): | ||||
|                 return ( | ||||
|                     # Sort first by quality | ||||
|                     int(f.get('height',-1)), | ||||
|                     int(f.get('bitrate',-1)), | ||||
|                     # The original version with subtitles has lower relevance | ||||
|                     re.match(r'VO-ST(F|A)', f.get('versionCode', '')) is None, | ||||
|                     # The version with sourds/mal subtitles has also lower relevance | ||||
|                     re.match(r'VO?(F|A)-STM\1', f.get('versionCode', '')) is None, | ||||
|                 ) | ||||
|         formats = sorted(formats, key=sort_key) | ||||
|         def _format(format_info): | ||||
|             quality = '' | ||||
|             height = format_info.get('height') | ||||
|             if height is not None: | ||||
|                 quality = compat_str(height) | ||||
|             bitrate = format_info.get('bitrate') | ||||
|             if bitrate is not None: | ||||
|                 quality += '-%d' % bitrate | ||||
|             if format_info.get('versionCode') is not None: | ||||
|                 format_id = u'%s-%s' % (quality, format_info['versionCode']) | ||||
|             else: | ||||
|                 format_id = quality | ||||
|             info = { | ||||
|                 'format_id': format_id, | ||||
|                 'format_note': format_info.get('versionLibelle'), | ||||
|                 'width': format_info.get('width'), | ||||
|                 'height': height, | ||||
|             } | ||||
|             if format_info['mediaType'] == u'rtmp': | ||||
|                 info['url'] = format_info['streamer'] | ||||
|                 info['play_path'] = 'mp4:' + format_info['url'] | ||||
|                 info['ext'] = 'flv' | ||||
|             else: | ||||
|                 info['url'] = format_info['url'] | ||||
|                 info['ext'] = determine_ext(info['url']) | ||||
|             return info | ||||
|         info_dict['formats'] = [_format(f) for f in formats] | ||||
|  | ||||
|         return info_dict | ||||
|  | ||||
|  | ||||
| # It also uses the arte_vp_url url from the webpage to extract the information | ||||
| class ArteTVCreativeIE(ArteTVPlus7IE): | ||||
|     IE_NAME = u'arte.tv:creative' | ||||
|     _VALID_URL = r'https?://creative\.arte\.tv/(?P<lang>fr|de)/magazine?/(?P<id>.+)' | ||||
|  | ||||
|     _TEST = { | ||||
|         u'url': u'http://creative.arte.tv/de/magazin/agentur-amateur-corporate-design', | ||||
|         u'file': u'050489-002.mp4', | ||||
|         u'info_dict': { | ||||
|             u'title': u'Agentur Amateur / Agence Amateur #2 : Corporate Design', | ||||
|         }, | ||||
|     } | ||||
|  | ||||
|  | ||||
| class ArteTVFutureIE(ArteTVPlus7IE): | ||||
|     IE_NAME = u'arte.tv:future' | ||||
|     _VALID_URL = r'https?://future\.arte\.tv/(?P<lang>fr|de)/(thema|sujet)/.*?#article-anchor-(?P<id>\d+)' | ||||
|  | ||||
|     _TEST = { | ||||
|         u'url': u'http://future.arte.tv/fr/sujet/info-sciences#article-anchor-7081', | ||||
|         u'file': u'050940-003.mp4', | ||||
|         u'info_dict': { | ||||
|             u'title': u'Les champignons au secours de la planète', | ||||
|         }, | ||||
|     } | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         anchor_id, lang = self._extract_url_info(url) | ||||
|         webpage = self._download_webpage(url, anchor_id) | ||||
|         row = get_element_by_id(anchor_id, webpage) | ||||
|         return self._extract_from_webpage(row, anchor_id, lang) | ||||
|   | ||||
							
								
								
									
										81
									
								
								youtube_dl/extractor/bambuser.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								youtube_dl/extractor/bambuser.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,81 @@ | ||||
| import re | ||||
| import json | ||||
| import itertools | ||||
|  | ||||
| from .common import InfoExtractor | ||||
| from ..utils import ( | ||||
|     compat_urllib_request, | ||||
| ) | ||||
|  | ||||
|  | ||||
| class BambuserIE(InfoExtractor): | ||||
|     IE_NAME = u'bambuser' | ||||
|     _VALID_URL = r'https?://bambuser\.com/v/(?P<id>\d+)' | ||||
|     _API_KEY = '005f64509e19a868399060af746a00aa' | ||||
|  | ||||
|     _TEST = { | ||||
|         u'url': u'http://bambuser.com/v/4050584', | ||||
|         # MD5 seems to be flaky, see https://travis-ci.org/rg3/youtube-dl/jobs/14051016#L388 | ||||
|         #u'md5': u'fba8f7693e48fd4e8641b3fd5539a641', | ||||
|         u'info_dict': { | ||||
|             u'id': u'4050584', | ||||
|             u'ext': u'flv', | ||||
|             u'title': u'Education engineering days - lightning talks', | ||||
|             u'duration': 3741, | ||||
|             u'uploader': u'pixelversity', | ||||
|             u'uploader_id': u'344706', | ||||
|         }, | ||||
|     } | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         video_id = mobj.group('id') | ||||
|         info_url = ('http://player-c.api.bambuser.com/getVideo.json?' | ||||
|             '&api_key=%s&vid=%s' % (self._API_KEY, video_id)) | ||||
|         info_json = self._download_webpage(info_url, video_id) | ||||
|         info = json.loads(info_json)['result'] | ||||
|  | ||||
|         return { | ||||
|             'id': video_id, | ||||
|             'title': info['title'], | ||||
|             'url': info['url'], | ||||
|             'thumbnail': info.get('preview'), | ||||
|             'duration': int(info['length']), | ||||
|             'view_count': int(info['views_total']), | ||||
|             'uploader': info['username'], | ||||
|             'uploader_id': info['uid'], | ||||
|         } | ||||
|  | ||||
|  | ||||
| class BambuserChannelIE(InfoExtractor): | ||||
|     IE_NAME = u'bambuser:channel' | ||||
|     _VALID_URL = r'http://bambuser.com/channel/(?P<user>.*?)(?:/|#|\?|$)' | ||||
|     # The maximum number we can get with each request | ||||
|     _STEP = 50 | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         user = mobj.group('user') | ||||
|         urls = [] | ||||
|         last_id = '' | ||||
|         for i in itertools.count(1): | ||||
|             req_url = ('http://bambuser.com/xhr-api/index.php?username={user}' | ||||
|                 '&sort=created&access_mode=0%2C1%2C2&limit={count}' | ||||
|                 '&method=broadcast&format=json&vid_older_than={last}' | ||||
|                 ).format(user=user, count=self._STEP, last=last_id) | ||||
|             req = compat_urllib_request.Request(req_url) | ||||
|             # Without setting this header, we wouldn't get any result | ||||
|             req.add_header('Referer', 'http://bambuser.com/channel/%s' % user) | ||||
|             info_json = self._download_webpage(req, user, | ||||
|                 u'Downloading page %d' % i) | ||||
|             results = json.loads(info_json)['result'] | ||||
|             if len(results) == 0: | ||||
|                 break | ||||
|             last_id = results[-1]['vid'] | ||||
|             urls.extend(self.url_result(v['page'], 'Bambuser') for v in results) | ||||
|  | ||||
|         return { | ||||
|             '_type': 'playlist', | ||||
|             'title': user, | ||||
|             'entries': urls, | ||||
|         } | ||||
| @@ -115,7 +115,7 @@ class BlipTVIE(InfoExtractor): | ||||
|                 ext = umobj.group(1) | ||||
|  | ||||
|                 info = { | ||||
|                     'id': data['item_id'], | ||||
|                     'id': compat_str(data['item_id']), | ||||
|                     'url': video_url, | ||||
|                     'uploader': data['display_name'], | ||||
|                     'upload_date': upload_date, | ||||
|   | ||||
							
								
								
									
										27
									
								
								youtube_dl/extractor/bloomberg.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								youtube_dl/extractor/bloomberg.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| import re | ||||
|  | ||||
| from .common import InfoExtractor | ||||
|  | ||||
|  | ||||
| class BloombergIE(InfoExtractor): | ||||
|     _VALID_URL = r'https?://www\.bloomberg\.com/video/(?P<name>.+?).html' | ||||
|  | ||||
|     _TEST = { | ||||
|         u'url': u'http://www.bloomberg.com/video/shah-s-presentation-on-foreign-exchange-strategies-qurhIVlJSB6hzkVi229d8g.html', | ||||
|         u'file': u'12bzhqZTqQHmmlA8I-i0NpzJgcG5NNYX.mp4', | ||||
|         u'info_dict': { | ||||
|             u'title': u'Shah\'s Presentation on Foreign-Exchange Strategies', | ||||
|             u'description': u'md5:abc86e5236f9f0e4866c59ad36736686', | ||||
|         }, | ||||
|         u'params': { | ||||
|             # Requires ffmpeg (m3u8 manifest) | ||||
|             u'skip_download': True, | ||||
|         }, | ||||
|     } | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         name = mobj.group('name') | ||||
|         webpage = self._download_webpage(url, name) | ||||
|         ooyala_url = self._og_search_video_url(webpage) | ||||
|         return self.url_result(ooyala_url, ie='Ooyala') | ||||
| @@ -1,3 +1,5 @@ | ||||
| # encoding: utf-8 | ||||
|  | ||||
| import re | ||||
| import json | ||||
| import xml.etree.ElementTree | ||||
| @@ -7,15 +9,53 @@ from ..utils import ( | ||||
|     compat_urllib_parse, | ||||
|     find_xpath_attr, | ||||
|     compat_urlparse, | ||||
|     compat_str, | ||||
|     compat_urllib_request, | ||||
|  | ||||
|     ExtractorError, | ||||
| ) | ||||
|  | ||||
|  | ||||
| class BrightcoveIE(InfoExtractor): | ||||
|     _VALID_URL = r'https?://.*brightcove\.com/(services|viewer).*\?(?P<query>.*)' | ||||
|     _FEDERATED_URL_TEMPLATE = 'http://c.brightcove.com/services/viewer/htmlFederated?%s' | ||||
|     _PLAYLIST_URL_TEMPLATE = 'http://c.brightcove.com/services/json/experience/runtime/?command=get_programming_for_experience&playerKey=%s' | ||||
|      | ||||
|     # There is a test for Brigtcove in GenericIE, that way we test both the download | ||||
|     # and the detection of videos, and we don't have to find an URL that is always valid | ||||
|  | ||||
|     _TESTS = [ | ||||
|         { | ||||
|             # From http://www.8tv.cat/8aldia/videos/xavier-sala-i-martin-aquesta-tarda-a-8-al-dia/ | ||||
|             u'url': u'http://c.brightcove.com/services/viewer/htmlFederated?playerID=1654948606001&flashID=myExperience&%40videoPlayer=2371591881001', | ||||
|             u'file': u'2371591881001.mp4', | ||||
|             u'md5': u'8eccab865181d29ec2958f32a6a754f5', | ||||
|             u'note': u'Test Brightcove downloads and detection in GenericIE', | ||||
|             u'info_dict': { | ||||
|                 u'title': u'Xavier Sala i Martín: “Un banc que no presta és un banc zombi que no serveix per a res”', | ||||
|                 u'uploader': u'8TV', | ||||
|                 u'description': u'md5:a950cc4285c43e44d763d036710cd9cd', | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             # From http://medianetwork.oracle.com/video/player/1785452137001 | ||||
|             u'url': u'http://c.brightcove.com/services/viewer/htmlFederated?playerID=1217746023001&flashID=myPlayer&%40videoPlayer=1785452137001', | ||||
|             u'file': u'1785452137001.flv', | ||||
|             u'info_dict': { | ||||
|                 u'title': u'JVMLS 2012: Arrays 2.0 - Opportunities and Challenges', | ||||
|                 u'description': u'John Rose speaks at the JVM Language Summit, August 1, 2012.', | ||||
|                 u'uploader': u'Oracle', | ||||
|             }, | ||||
|         }, | ||||
|         { | ||||
|             # From http://mashable.com/2013/10/26/thermoelectric-bracelet-lets-you-control-your-body-temperature/ | ||||
|             u'url': u'http://c.brightcove.com/services/viewer/federated_f9?&playerID=1265504713001&publisherID=AQ%7E%7E%2CAAABBzUwv1E%7E%2CxP-xFHVUstiMFlNYfvF4G9yFnNaqCw_9&videoID=2750934548001', | ||||
|             u'info_dict': { | ||||
|                 u'id': u'2750934548001', | ||||
|                 u'ext': u'mp4', | ||||
|                 u'title': u'This Bracelet Acts as a Personal Thermostat', | ||||
|                 u'description': u'md5:547b78c64f4112766ccf4e151c20b6a0', | ||||
|                 u'uploader': u'Mashable', | ||||
|             }, | ||||
|         }, | ||||
|     ] | ||||
|  | ||||
|     @classmethod | ||||
|     def _build_brighcove_url(cls, object_str): | ||||
| @@ -23,6 +63,13 @@ class BrightcoveIE(InfoExtractor): | ||||
|         Build a Brightcove url from a xml string containing | ||||
|         <object class="BrightcoveExperience">{params}</object> | ||||
|         """ | ||||
|  | ||||
|         # Fix up some stupid HTML, see https://github.com/rg3/youtube-dl/issues/1553 | ||||
|         object_str = re.sub(r'(<param name="[^"]+" value="[^"]+")>', | ||||
|                             lambda m: m.group(1) + '/>', object_str) | ||||
|         # Fix up some stupid XML, see https://github.com/rg3/youtube-dl/issues/1608 | ||||
|         object_str = object_str.replace(u'<--', u'<!--') | ||||
|  | ||||
|         object_doc = xml.etree.ElementTree.fromstring(object_str) | ||||
|         assert u'BrightcoveExperience' in object_doc.attrib['class'] | ||||
|         params = {'flashID': object_doc.attrib['id'], | ||||
| @@ -35,24 +82,48 @@ class BrightcoveIE(InfoExtractor): | ||||
|         videoPlayer = find_xpath_attr(object_doc, './param', 'name', '@videoPlayer') | ||||
|         if videoPlayer is not None: | ||||
|             params['@videoPlayer'] = videoPlayer.attrib['value'] | ||||
|         linkBase = find_xpath_attr(object_doc, './param', 'name', 'linkBaseURL') | ||||
|         if linkBase is not None: | ||||
|             params['linkBaseURL'] = linkBase.attrib['value'] | ||||
|         data = compat_urllib_parse.urlencode(params) | ||||
|         return cls._FEDERATED_URL_TEMPLATE % data | ||||
|  | ||||
|     @classmethod | ||||
|     def _extract_brightcove_url(cls, webpage): | ||||
|         """Try to extract the brightcove url from the wepbage, returns None | ||||
|         if it can't be found | ||||
|         """ | ||||
|         m_brightcove = re.search( | ||||
|             r'<object[^>]+?class=([\'"])[^>]*?BrightcoveExperience.*?\1.+?</object>', | ||||
|             webpage, re.DOTALL) | ||||
|         if m_brightcove is not None: | ||||
|             return cls._build_brighcove_url(m_brightcove.group()) | ||||
|         else: | ||||
|             return None | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         # Change the 'videoId' and others field to '@videoPlayer' | ||||
|         url = re.sub(r'(?<=[?&])(videoI(d|D)|bctid)', '%40videoPlayer', url) | ||||
|         # Change bckey (used by bcove.me urls) to playerKey | ||||
|         url = re.sub(r'(?<=[?&])bckey', 'playerKey', url) | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         query_str = mobj.group('query') | ||||
|         query = compat_urlparse.parse_qs(query_str) | ||||
|  | ||||
|         videoPlayer = query.get('@videoPlayer') | ||||
|         if videoPlayer: | ||||
|             return self._get_video_info(videoPlayer[0], query_str) | ||||
|             return self._get_video_info(videoPlayer[0], query_str, query) | ||||
|         else: | ||||
|             player_key = query['playerKey'] | ||||
|             return self._get_playlist_info(player_key[0]) | ||||
|  | ||||
|     def _get_video_info(self, video_id, query): | ||||
|         request_url = self._FEDERATED_URL_TEMPLATE % query | ||||
|         webpage = self._download_webpage(request_url, video_id) | ||||
|     def _get_video_info(self, video_id, query_str, query): | ||||
|         request_url = self._FEDERATED_URL_TEMPLATE % query_str | ||||
|         req = compat_urllib_request.Request(request_url) | ||||
|         linkBase = query.get('linkBaseURL') | ||||
|         if linkBase is not None: | ||||
|             req.add_header('Referer', linkBase[0]) | ||||
|         webpage = self._download_webpage(req, video_id) | ||||
|  | ||||
|         self.report_extraction(video_id) | ||||
|         info = self._search_regex(r'var experienceJSON = ({.*?});', webpage, 'json') | ||||
| @@ -65,22 +136,36 @@ class BrightcoveIE(InfoExtractor): | ||||
|         playlist_info = self._download_webpage(self._PLAYLIST_URL_TEMPLATE % player_key, | ||||
|                                                player_key, u'Downloading playlist information') | ||||
|  | ||||
|         playlist_info = json.loads(playlist_info)['videoList'] | ||||
|         json_data = json.loads(playlist_info) | ||||
|         if 'videoList' not in json_data: | ||||
|             raise ExtractorError(u'Empty playlist') | ||||
|         playlist_info = json_data['videoList'] | ||||
|         videos = [self._extract_video_info(video_info) for video_info in playlist_info['mediaCollectionDTO']['videoDTOs']] | ||||
|  | ||||
|         return self.playlist_result(videos, playlist_id=playlist_info['id'], | ||||
|                                     playlist_title=playlist_info['mediaCollectionDTO']['displayName']) | ||||
|  | ||||
|     def _extract_video_info(self, video_info): | ||||
|         renditions = video_info['renditions'] | ||||
|         renditions = sorted(renditions, key=lambda r: r['size']) | ||||
|         best_format = renditions[-1] | ||||
|         info = { | ||||
|             'id': compat_str(video_info['id']), | ||||
|             'title': video_info['displayName'], | ||||
|             'description': video_info.get('shortDescription'), | ||||
|             'thumbnail': video_info.get('videoStillURL') or video_info.get('thumbnailURL'), | ||||
|             'uploader': video_info.get('publisherName'), | ||||
|         } | ||||
|  | ||||
|         return {'id': video_info['id'], | ||||
|                 'title': video_info['displayName'], | ||||
|                 'url': best_format['defaultURL'],  | ||||
|                 'ext': 'mp4', | ||||
|                 'description': video_info.get('shortDescription'), | ||||
|                 'thumbnail': video_info.get('videoStillURL') or video_info.get('thumbnailURL'), | ||||
|                 'uploader': video_info.get('publisherName'), | ||||
|                 } | ||||
|         renditions = video_info.get('renditions') | ||||
|         if renditions: | ||||
|             renditions = sorted(renditions, key=lambda r: r['size']) | ||||
|             info['formats'] = [{ | ||||
|                 'url': rend['defaultURL'], | ||||
|                 'height': rend.get('frameHeight'), | ||||
|                 'width': rend.get('frameWidth'), | ||||
|             } for rend in renditions] | ||||
|         elif video_info.get('FLVFullLengthURL') is not None: | ||||
|             info.update({ | ||||
|                 'url': video_info['FLVFullLengthURL'], | ||||
|             }) | ||||
|         else: | ||||
|             raise ExtractorError(u'Unable to extract video url for %s' % info['id']) | ||||
|         return info | ||||
|   | ||||
| @@ -12,8 +12,8 @@ class C56IE(InfoExtractor): | ||||
|  | ||||
|     _TEST ={ | ||||
|         u'url': u'http://www.56.com/u39/v_OTM0NDA3MTY.html', | ||||
|         u'file': u'93440716.mp4', | ||||
|         u'md5': u'9dc07b5c8e978112a6441f9e75d2b59e', | ||||
|         u'file': u'93440716.flv', | ||||
|         u'md5': u'e59995ac63d0457783ea05f93f12a866', | ||||
|         u'info_dict': { | ||||
|             u'title': u'网事知多少 第32期:车怒', | ||||
|         }, | ||||
|   | ||||
							
								
								
									
										37
									
								
								youtube_dl/extractor/canalc2.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								youtube_dl/extractor/canalc2.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| # coding: utf-8 | ||||
| import re | ||||
|  | ||||
| from .common import InfoExtractor | ||||
|  | ||||
|  | ||||
| class Canalc2IE(InfoExtractor): | ||||
|     IE_NAME = 'canalc2.tv' | ||||
|     _VALID_URL = r'http://.*?\.canalc2\.tv/video\.asp\?.*?idVideo=(?P<id>\d+)' | ||||
|  | ||||
|     _TEST = { | ||||
|         u'url': u'http://www.canalc2.tv/video.asp?idVideo=12163&voir=oui', | ||||
|         u'file': u'12163.mp4', | ||||
|         u'md5': u'060158428b650f896c542dfbb3d6487f', | ||||
|         u'info_dict': { | ||||
|             u'title': u'Terrasses du Numérique' | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         video_id = re.match(self._VALID_URL, url).group('id') | ||||
|         # We need to set the voir field for getting the file name | ||||
|         url = 'http://www.canalc2.tv/video.asp?idVideo=%s&voir=oui' % video_id | ||||
|         webpage = self._download_webpage(url, video_id) | ||||
|         file_name = self._search_regex( | ||||
|             r"so\.addVariable\('file','(.*?)'\);", | ||||
|             webpage, 'file name') | ||||
|         video_url = 'http://vod-flash.u-strasbg.fr:8080/' + file_name | ||||
|  | ||||
|         title = self._html_search_regex( | ||||
|             r'class="evenement8">(.*?)</a>', webpage, u'title') | ||||
|          | ||||
|         return {'id': video_id, | ||||
|                 'ext': 'mp4', | ||||
|                 'url': video_url, | ||||
|                 'title': title, | ||||
|                 } | ||||
| @@ -1,3 +1,4 @@ | ||||
| # encoding: utf-8 | ||||
| import re | ||||
| import xml.etree.ElementTree | ||||
|  | ||||
| @@ -5,24 +6,29 @@ from .common import InfoExtractor | ||||
| from ..utils import unified_strdate | ||||
|  | ||||
| class CanalplusIE(InfoExtractor): | ||||
|     _VALID_URL = r'https?://www\.canalplus\.fr/.*?\?vid=(?P<id>\d+)' | ||||
|     _VALID_URL = r'https?://(www\.canalplus\.fr/.*?/(?P<path>.*)|player\.canalplus\.fr/#/(?P<id>\d+))' | ||||
|     _VIDEO_INFO_TEMPLATE = 'http://service.canal-plus.com/video/rest/getVideosLiees/cplus/%s' | ||||
|     IE_NAME = u'canalplus.fr' | ||||
|  | ||||
|     _TEST = { | ||||
|         u'url': u'http://www.canalplus.fr/c-divertissement/pid3351-c-le-petit-journal.html?vid=889861', | ||||
|         u'file': u'889861.flv', | ||||
|         u'md5': u'590a888158b5f0d6832f84001fbf3e99', | ||||
|         u'url': u'http://www.canalplus.fr/c-infos-documentaires/pid1830-c-zapping.html?vid=922470', | ||||
|         u'file': u'922470.flv', | ||||
|         u'info_dict': { | ||||
|             u'title': u'Le Petit Journal 20/06/13 - La guerre des drone', | ||||
|             u'upload_date': u'20130620', | ||||
|             u'title': u'Zapping - 26/08/13', | ||||
|             u'description': u'Le meilleur de toutes les chaînes, tous les jours.\nEmission du 26 août 2013', | ||||
|             u'upload_date': u'20130826', | ||||
|         }, | ||||
|         u'params': { | ||||
|             u'skip_download': True, | ||||
|         }, | ||||
|         u'skip': u'Requires rtmpdump' | ||||
|     } | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         video_id = mobj.group('id') | ||||
|         if video_id is None: | ||||
|             webpage = self._download_webpage(url, mobj.group('path')) | ||||
|             video_id = self._search_regex(r'videoId = "(\d+)";', webpage, u'video id') | ||||
|         info_url = self._VIDEO_INFO_TEMPLATE % video_id | ||||
|         info_page = self._download_webpage(info_url,video_id,  | ||||
|                                            u'Downloading video info') | ||||
| @@ -43,4 +49,6 @@ class CanalplusIE(InfoExtractor): | ||||
|                 'ext': 'flv', | ||||
|                 'upload_date': unified_strdate(infos.find('PUBLICATION/DATE').text), | ||||
|                 'thumbnail': media.find('IMAGES/GRAND').text, | ||||
|                 'description': infos.find('DESCRIPTION').text, | ||||
|                 'view_count': int(infos.find('NB_VUES').text), | ||||
|                 } | ||||
|   | ||||
							
								
								
									
										90
									
								
								youtube_dl/extractor/cinemassacre.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								youtube_dl/extractor/cinemassacre.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,90 @@ | ||||
| # encoding: utf-8 | ||||
| import re | ||||
|  | ||||
| from .common import InfoExtractor | ||||
| from ..utils import ( | ||||
|     ExtractorError, | ||||
| ) | ||||
|  | ||||
|  | ||||
| class CinemassacreIE(InfoExtractor): | ||||
|     _VALID_URL = r'(?:http://)?(?:www\.)?(?P<url>cinemassacre\.com/(?P<date_Y>[0-9]{4})/(?P<date_m>[0-9]{2})/(?P<date_d>[0-9]{2})/.+?)(?:[/?].*)?' | ||||
|     _TESTS = [{ | ||||
|         u'url': u'http://cinemassacre.com/2012/11/10/avgn-the-movie-trailer/', | ||||
|         u'file': u'19911.flv', | ||||
|         u'info_dict': { | ||||
|             u'upload_date': u'20121110', | ||||
|             u'title': u'“Angry Video Game Nerd: The Movie” – Trailer', | ||||
|             u'description': u'md5:fb87405fcb42a331742a0dce2708560b', | ||||
|         }, | ||||
|         u'params': { | ||||
|             # rtmp download | ||||
|             u'skip_download': True, | ||||
|         }, | ||||
|     }, | ||||
|     { | ||||
|         u'url': u'http://cinemassacre.com/2013/10/02/the-mummys-hand-1940', | ||||
|         u'file': u'521be8ef82b16.flv', | ||||
|         u'info_dict': { | ||||
|             u'upload_date': u'20131002', | ||||
|             u'title': u'The Mummy’s Hand (1940)', | ||||
|         }, | ||||
|         u'params': { | ||||
|             # rtmp download | ||||
|             u'skip_download': True, | ||||
|         }, | ||||
|     }] | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|  | ||||
|         webpage_url = u'http://' + mobj.group('url') | ||||
|         webpage = self._download_webpage(webpage_url, None) # Don't know video id yet | ||||
|         video_date = mobj.group('date_Y') + mobj.group('date_m') + mobj.group('date_d') | ||||
|         mobj = re.search(r'src="(?P<embed_url>http://player\.screenwavemedia\.com/play/[a-zA-Z]+\.php\?id=(?:Cinemassacre-)?(?P<video_id>.+?))"', webpage) | ||||
|         if not mobj: | ||||
|             raise ExtractorError(u'Can\'t extract embed url and video id') | ||||
|         playerdata_url = mobj.group(u'embed_url') | ||||
|         video_id = mobj.group(u'video_id') | ||||
|  | ||||
|         video_title = self._html_search_regex(r'<title>(?P<title>.+?)\|', | ||||
|             webpage, u'title') | ||||
|         video_description = self._html_search_regex(r'<div class="entry-content">(?P<description>.+?)</div>', | ||||
|             webpage, u'description', flags=re.DOTALL, fatal=False) | ||||
|         if len(video_description) == 0: | ||||
|             video_description = None | ||||
|  | ||||
|         playerdata = self._download_webpage(playerdata_url, video_id) | ||||
|         url = self._html_search_regex(r'\'streamer\': \'(?P<url>[^\']+)\'', playerdata, u'url') | ||||
|  | ||||
|         sd_file = self._html_search_regex(r'\'file\': \'(?P<sd_file>[^\']+)\'', playerdata, u'sd_file') | ||||
|         hd_file = self._html_search_regex(r'\'?file\'?: "(?P<hd_file>[^"]+)"', playerdata, u'hd_file') | ||||
|         video_thumbnail = self._html_search_regex(r'\'image\': \'(?P<thumbnail>[^\']+)\'', playerdata, u'thumbnail', fatal=False) | ||||
|  | ||||
|         formats = [ | ||||
|             { | ||||
|                 'url': url, | ||||
|                 'play_path': 'mp4:' + sd_file, | ||||
|                 'rtmp_live': True, # workaround | ||||
|                 'ext': 'flv', | ||||
|                 'format': 'sd', | ||||
|                 'format_id': 'sd', | ||||
|             }, | ||||
|             { | ||||
|                 'url': url, | ||||
|                 'play_path': 'mp4:' + hd_file, | ||||
|                 'rtmp_live': True, # workaround | ||||
|                 'ext': 'flv', | ||||
|                 'format': 'hd', | ||||
|                 'format_id': 'hd', | ||||
|             }, | ||||
|         ] | ||||
|  | ||||
|         return { | ||||
|             'id': video_id, | ||||
|             'title': video_title, | ||||
|             'formats': formats, | ||||
|             'description': video_description, | ||||
|             'upload_date': video_date, | ||||
|             'thumbnail': video_thumbnail, | ||||
|         } | ||||
							
								
								
									
										58
									
								
								youtube_dl/extractor/cnn.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								youtube_dl/extractor/cnn.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | ||||
| import re | ||||
| import xml.etree.ElementTree | ||||
|  | ||||
| from .common import InfoExtractor | ||||
| from ..utils import determine_ext | ||||
|  | ||||
|  | ||||
| class CNNIE(InfoExtractor): | ||||
|     _VALID_URL = r'''(?x)https?://((edition|www)\.)?cnn\.com/video/(data/.+?|\?)/ | ||||
|         (?P<path>.+?/(?P<title>[^/]+?)(?:\.cnn|(?=&)))''' | ||||
|  | ||||
|     _TESTS = [{ | ||||
|         u'url': u'http://edition.cnn.com/video/?/video/sports/2013/06/09/nadal-1-on-1.cnn', | ||||
|         u'file': u'sports_2013_06_09_nadal-1-on-1.cnn.mp4', | ||||
|         u'md5': u'3e6121ea48df7e2259fe73a0628605c4', | ||||
|         u'info_dict': { | ||||
|             u'title': u'Nadal wins 8th French Open title', | ||||
|             u'description': u'World Sport\'s Amanda Davies chats with 2013 French Open champion Rafael Nadal.', | ||||
|         }, | ||||
|     }, | ||||
|     { | ||||
|         u"url": u"http://edition.cnn.com/video/?/video/us/2013/08/21/sot-student-gives-epic-speech.georgia-institute-of-technology&utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A+rss%2Fcnn_topstories+%28RSS%3A+Top+Stories%29", | ||||
|         u"file": u"us_2013_08_21_sot-student-gives-epic-speech.georgia-institute-of-technology.mp4", | ||||
|         u"md5": u"b5cc60c60a3477d185af8f19a2a26f4e", | ||||
|         u"info_dict": { | ||||
|             u"title": "Student's epic speech stuns new freshmen", | ||||
|             u"description": "A Georgia Tech student welcomes the incoming freshmen with an epic speech backed by music from \"2001: A Space Odyssey.\"" | ||||
|         } | ||||
|     }] | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         path = mobj.group('path') | ||||
|         page_title = mobj.group('title') | ||||
|         info_url = u'http://cnn.com/video/data/3.0/%s/index.xml' % path | ||||
|         info_xml = self._download_webpage(info_url, page_title) | ||||
|         info = xml.etree.ElementTree.fromstring(info_xml.encode('utf-8')) | ||||
|  | ||||
|         formats = [] | ||||
|         for f in info.findall('files/file'): | ||||
|             mf = re.match(r'(\d+)x(\d+)(?:_(.*)k)?',f.attrib['bitrate']) | ||||
|             if mf is not None: | ||||
|                 formats.append((int(mf.group(1)), int(mf.group(2)), int(mf.group(3) or 0), f.text)) | ||||
|         formats = sorted(formats) | ||||
|         (_,_,_, video_path) = formats[-1] | ||||
|         video_url = 'http://ht.cdn.turner.com/cnn/big%s' % video_path | ||||
|  | ||||
|         thumbnails = sorted([((int(t.attrib['height']),int(t.attrib['width'])), t.text) for t in info.findall('images/image')]) | ||||
|         thumbs_dict = [{'resolution': res, 'url': t_url} for (res, t_url) in thumbnails] | ||||
|  | ||||
|         return {'id': info.attrib['id'], | ||||
|                 'title': info.find('headline').text, | ||||
|                 'url': video_url, | ||||
|                 'ext': determine_ext(video_url), | ||||
|                 'thumbnail': thumbnails[-1][1], | ||||
|                 'thumbnails': thumbs_dict, | ||||
|                 'description': info.find('description').text, | ||||
|                 } | ||||
| @@ -4,15 +4,16 @@ import xml.etree.ElementTree | ||||
| from .common import InfoExtractor | ||||
| from ..utils import ( | ||||
|     compat_urllib_parse_urlparse, | ||||
|     determine_ext, | ||||
|  | ||||
|     ExtractorError, | ||||
| ) | ||||
|  | ||||
|  | ||||
| class CollegeHumorIE(InfoExtractor): | ||||
|     _VALID_URL = r'^(?:https?://)?(?:www\.)?collegehumor\.com/(video|embed)/(?P<videoid>[0-9]+)/(?P<shorttitle>.*)$' | ||||
|     _VALID_URL = r'^(?:https?://)?(?:www\.)?collegehumor\.com/(video|embed|e)/(?P<videoid>[0-9]+)/?(?P<shorttitle>.*)$' | ||||
|  | ||||
|     _TEST = { | ||||
|     _TESTS = [{ | ||||
|         u'url': u'http://www.collegehumor.com/video/6902724/comic-con-cosplay-catastrophe', | ||||
|         u'file': u'6902724.mp4', | ||||
|         u'md5': u'1264c12ad95dca142a9f0bf7968105a0', | ||||
| @@ -20,7 +21,16 @@ class CollegeHumorIE(InfoExtractor): | ||||
|             u'title': u'Comic-Con Cosplay Catastrophe', | ||||
|             u'description': u'Fans get creative this year at San Diego.  Too creative.  And yes, that\'s really Joss Whedon.', | ||||
|         }, | ||||
|     } | ||||
|     }, | ||||
|     { | ||||
|         u'url': u'http://www.collegehumor.com/video/3505939/font-conference', | ||||
|         u'file': u'3505939.mp4', | ||||
|         u'md5': u'c51ca16b82bb456a4397987791a835f5', | ||||
|         u'info_dict': { | ||||
|             u'title': u'Font Conference', | ||||
|             u'description': u'This video wasn\'t long enough, so we made it double-spaced.', | ||||
|         }, | ||||
|     }] | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
| @@ -49,25 +59,29 @@ class CollegeHumorIE(InfoExtractor): | ||||
|             info['description'] = videoNode.findall('./description')[0].text | ||||
|             info['title'] = videoNode.findall('./caption')[0].text | ||||
|             info['thumbnail'] = videoNode.findall('./thumbnail')[0].text | ||||
|             manifest_url = videoNode.findall('./file')[0].text | ||||
|             next_url = videoNode.findall('./file')[0].text | ||||
|         except IndexError: | ||||
|             raise ExtractorError(u'Invalid metadata XML file') | ||||
|  | ||||
|         manifest_url += '?hdcore=2.10.3' | ||||
|         manifestXml = self._download_webpage(manifest_url, video_id, | ||||
|                                              u'Downloading XML manifest', | ||||
|                                              u'Unable to download video info XML') | ||||
|         if next_url.endswith(u'manifest.f4m'): | ||||
|             manifest_url = next_url + '?hdcore=2.10.3' | ||||
|             manifestXml = self._download_webpage(manifest_url, video_id, | ||||
|                                          u'Downloading XML manifest', | ||||
|                                          u'Unable to download video info XML') | ||||
|  | ||||
|         adoc = xml.etree.ElementTree.fromstring(manifestXml) | ||||
|         try: | ||||
|             media_node = adoc.findall('./{http://ns.adobe.com/f4m/1.0}media')[0] | ||||
|             node_id = media_node.attrib['url'] | ||||
|             video_id = adoc.findall('./{http://ns.adobe.com/f4m/1.0}id')[0].text | ||||
|         except IndexError as err: | ||||
|             raise ExtractorError(u'Invalid manifest file') | ||||
|             adoc = xml.etree.ElementTree.fromstring(manifestXml) | ||||
|             try: | ||||
|                 media_node = adoc.findall('./{http://ns.adobe.com/f4m/1.0}media')[0] | ||||
|                 node_id = media_node.attrib['url'] | ||||
|                 video_id = adoc.findall('./{http://ns.adobe.com/f4m/1.0}id')[0].text | ||||
|             except IndexError as err: | ||||
|                 raise ExtractorError(u'Invalid manifest file') | ||||
|             url_pr = compat_urllib_parse_urlparse(info['thumbnail']) | ||||
|             info['url'] = url_pr.scheme + '://' + url_pr.netloc + video_id[:-2].replace('.csmil','').replace(',','') | ||||
|             info['ext'] = 'mp4' | ||||
|         else: | ||||
|             # Old-style direct links | ||||
|             info['url'] = next_url | ||||
|             info['ext'] = determine_ext(info['url']) | ||||
|  | ||||
|         url_pr = compat_urllib_parse_urlparse(info['thumbnail']) | ||||
|  | ||||
|         info['url'] = url_pr.scheme + '://' + url_pr.netloc + video_id[:-2].replace('.csmil','').replace(',','') | ||||
|         info['ext'] = 'mp4' | ||||
|         return [info] | ||||
|         return info | ||||
|   | ||||
| @@ -51,12 +51,12 @@ class ComedyCentralIE(InfoExtractor): | ||||
|         '400': 'mp4', | ||||
|     } | ||||
|     _video_dimensions = { | ||||
|         '3500': '1280x720', | ||||
|         '2200': '960x540', | ||||
|         '1700': '768x432', | ||||
|         '1200': '640x360', | ||||
|         '750': '512x288', | ||||
|         '400': '384x216', | ||||
|         '3500': (1280, 720), | ||||
|         '2200': (960, 540), | ||||
|         '1700': (768, 432), | ||||
|         '1200': (640, 360), | ||||
|         '750': (512, 288), | ||||
|         '400': (384, 216), | ||||
|     } | ||||
|  | ||||
|     @classmethod | ||||
| @@ -64,11 +64,13 @@ class ComedyCentralIE(InfoExtractor): | ||||
|         """Receives a URL and returns True if suitable for this IE.""" | ||||
|         return re.match(cls._VALID_URL, url, re.VERBOSE) is not None | ||||
|  | ||||
|     def _print_formats(self, formats): | ||||
|         print('Available formats:') | ||||
|         for x in formats: | ||||
|             print('%s\t:\t%s\t[%s]' %(x, self._video_extensions.get(x, 'mp4'), self._video_dimensions.get(x, '???'))) | ||||
|  | ||||
|     @staticmethod | ||||
|     def _transform_rtmp_url(rtmp_video_url): | ||||
|         m = re.match(r'^rtmpe?://.*?/(?P<finalid>gsp.comedystor/.*)$', rtmp_video_url) | ||||
|         if not m: | ||||
|             raise ExtractorError(u'Cannot transform RTMP url') | ||||
|         base = 'http://mtvnmobile.vo.llnwd.net/kip0/_pxn=1+_pxI0=Ripod-h264+_pxL0=undefined+_pxM0=+_pxK=18639+_pxE=mp4/44620/mtvnorigin/' | ||||
|         return base + m.group('finalid') | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url, re.VERBOSE) | ||||
| @@ -155,40 +157,31 @@ class ComedyCentralIE(InfoExtractor): | ||||
|                 self._downloader.report_error(u'unable to download ' + mediaId + ': No videos found') | ||||
|                 continue | ||||
|  | ||||
|             if self._downloader.params.get('listformats', None): | ||||
|                 self._print_formats([i[0] for i in turls]) | ||||
|                 return | ||||
|  | ||||
|             # For now, just pick the highest bitrate | ||||
|             format,rtmp_video_url = turls[-1] | ||||
|  | ||||
|             # Get the format arg from the arg stream | ||||
|             req_format = self._downloader.params.get('format', None) | ||||
|  | ||||
|             # Select format if we can find one | ||||
|             for f,v in turls: | ||||
|                 if f == req_format: | ||||
|                     format, rtmp_video_url = f, v | ||||
|                     break | ||||
|  | ||||
|             m = re.match(r'^rtmpe?://.*?/(?P<finalid>gsp.comedystor/.*)$', rtmp_video_url) | ||||
|             if not m: | ||||
|                 raise ExtractorError(u'Cannot transform RTMP url') | ||||
|             base = 'http://mtvnmobile.vo.llnwd.net/kip0/_pxn=1+_pxI0=Ripod-h264+_pxL0=undefined+_pxM0=+_pxK=18639+_pxE=mp4/44620/mtvnorigin/' | ||||
|             video_url = base + m.group('finalid') | ||||
|             formats = [] | ||||
|             for format, rtmp_video_url in turls: | ||||
|                 w, h = self._video_dimensions.get(format, (None, None)) | ||||
|                 formats.append({ | ||||
|                     'url': self._transform_rtmp_url(rtmp_video_url), | ||||
|                     'ext': self._video_extensions.get(format, 'mp4'), | ||||
|                     'format_id': format, | ||||
|                     'height': h, | ||||
|                     'width': w, | ||||
|                 }) | ||||
|  | ||||
|             effTitle = showId + u'-' + epTitle + u' part ' + compat_str(partNum+1) | ||||
|             info = { | ||||
|                 'id': shortMediaId, | ||||
|                 'url': video_url, | ||||
|                 'formats': formats, | ||||
|                 'uploader': showId, | ||||
|                 'upload_date': officialDate, | ||||
|                 'title': effTitle, | ||||
|                 'ext': 'mp4', | ||||
|                 'format': format, | ||||
|                 'thumbnail': None, | ||||
|                 'description': compat_str(officialTitle), | ||||
|             } | ||||
|  | ||||
|             # TODO: Remove when #980 has been merged | ||||
|             info.update(info['formats'][-1]) | ||||
|  | ||||
|             results.append(info) | ||||
|  | ||||
|         return results | ||||
|   | ||||
| @@ -14,6 +14,8 @@ from ..utils import ( | ||||
|     clean_html, | ||||
|     compiled_regex_type, | ||||
|     ExtractorError, | ||||
|     RegexNotFoundError, | ||||
|     sanitize_filename, | ||||
|     unescapeHTML, | ||||
| ) | ||||
|  | ||||
| @@ -35,6 +37,8 @@ class InfoExtractor(object): | ||||
|     title:          Video title, unescaped. | ||||
|     ext:            Video filename extension. | ||||
|  | ||||
|     Instead of url and ext, formats can also specified. | ||||
|  | ||||
|     The following fields are optional: | ||||
|  | ||||
|     format:         The video format, defaults to ext (used for --get-format) | ||||
| @@ -47,12 +51,35 @@ class InfoExtractor(object): | ||||
|     uploader_id:    Nickname or id of the video uploader. | ||||
|     location:       Physical location of the video. | ||||
|     player_url:     SWF Player URL (used for rtmpdump). | ||||
|     subtitles:      The subtitle file contents. | ||||
|     subtitles:      The subtitle file contents as a dictionary in the format | ||||
|                     {language: subtitles}. | ||||
|     view_count:     How many users have watched the video on the platform. | ||||
|     urlhandle:      [internal] The urlHandle to be used to download the file, | ||||
|                     like returned by urllib.request.urlopen | ||||
|     age_limit:      Age restriction for the video, as an integer (years) | ||||
|     formats:        A list of dictionaries for each format available, it must | ||||
|                     be ordered from worst to best quality. Potential fields: | ||||
|                     * url       Mandatory. The URL of the video file | ||||
|                     * ext       Will be calculated from url if missing | ||||
|                     * format    A human-readable description of the format | ||||
|                                 ("mp4 container with h264/opus"). | ||||
|                                 Calculated from the format_id, width, height. | ||||
|                                 and format_note fields if missing. | ||||
|                     * format_id A short description of the format | ||||
|                                 ("mp4_h264_opus" or "19") | ||||
|                     * format_note Additional info about the format | ||||
|                                 ("3D" or "DASH video") | ||||
|                     * width     Width of the video, if known | ||||
|                     * height    Height of the video, if known | ||||
|                     * abr       Average audio bitrate in KBit/s | ||||
|                     * acodec    Name of the audio codec in use | ||||
|                     * vbr       Average video bitrate in KBit/s | ||||
|                     * vcodec    Name of the video codec in use | ||||
|     webpage_url:    The url to the video webpage, if given to youtube-dl it | ||||
|                     should allow to get the same result again. (It will be set | ||||
|                     by YoutubeDL if it's missing) | ||||
|  | ||||
|     The fields should all be Unicode strings. | ||||
|     Unless mentioned otherwise, the fields should be Unicode strings. | ||||
|  | ||||
|     Subclasses of this one should re-define the _real_initialize() and | ||||
|     _real_extract() methods and define a _VALID_URL regexp. | ||||
| @@ -77,7 +104,13 @@ class InfoExtractor(object): | ||||
|     @classmethod | ||||
|     def suitable(cls, url): | ||||
|         """Receives a URL and returns True if suitable for this IE.""" | ||||
|         return re.match(cls._VALID_URL, url) is not None | ||||
|  | ||||
|         # This does not use has/getattr intentionally - we want to know whether | ||||
|         # we have cached the regexp for *this* class, whereas getattr would also | ||||
|         # match the superclass | ||||
|         if '_VALID_URL_RE' not in cls.__dict__: | ||||
|             cls._VALID_URL_RE = re.compile(cls._VALID_URL) | ||||
|         return cls._VALID_URL_RE.match(url) is not None | ||||
|  | ||||
|     @classmethod | ||||
|     def working(cls): | ||||
| @@ -107,6 +140,11 @@ class InfoExtractor(object): | ||||
|         """Real extraction process. Redefine in subclasses.""" | ||||
|         pass | ||||
|  | ||||
|     @classmethod | ||||
|     def ie_key(cls): | ||||
|         """A string for getting the InfoExtractor with get_info_extractor""" | ||||
|         return cls.__name__[:-2] | ||||
|  | ||||
|     @property | ||||
|     def IE_NAME(self): | ||||
|         return type(self).__name__[:-2] | ||||
| @@ -122,7 +160,7 @@ class InfoExtractor(object): | ||||
|         except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err: | ||||
|             if errnote is None: | ||||
|                 errnote = u'Unable to download webpage' | ||||
|             raise ExtractorError(u'%s: %s' % (errnote, compat_str(err)), sys.exc_info()[2]) | ||||
|             raise ExtractorError(u'%s: %s' % (errnote, compat_str(err)), sys.exc_info()[2], cause=err) | ||||
|  | ||||
|     def _download_webpage_handle(self, url_or_request, video_id, note=None, errnote=None): | ||||
|         """ Returns a tuple (page content as string, URL handle) """ | ||||
| @@ -133,12 +171,17 @@ class InfoExtractor(object): | ||||
|  | ||||
|         urlh = self._request_webpage(url_or_request, video_id, note, errnote) | ||||
|         content_type = urlh.headers.get('Content-Type', '') | ||||
|         webpage_bytes = urlh.read() | ||||
|         m = re.match(r'[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+\s*;\s*charset=(.+)', content_type) | ||||
|         if m: | ||||
|             encoding = m.group(1) | ||||
|         else: | ||||
|             encoding = 'utf-8' | ||||
|         webpage_bytes = urlh.read() | ||||
|             m = re.search(br'<meta[^>]+charset=[\'"]?([^\'")]+)[ /\'">]', | ||||
|                           webpage_bytes[:1024]) | ||||
|             if m: | ||||
|                 encoding = m.group(1).decode('ascii') | ||||
|             else: | ||||
|                 encoding = 'utf-8' | ||||
|         if self._downloader.params.get('dump_intermediate_pages', False): | ||||
|             try: | ||||
|                 url = url_or_request.get_full_url() | ||||
| @@ -147,6 +190,17 @@ class InfoExtractor(object): | ||||
|             self.to_screen(u'Dumping request to ' + url) | ||||
|             dump = base64.b64encode(webpage_bytes).decode('ascii') | ||||
|             self._downloader.to_screen(dump) | ||||
|         if self._downloader.params.get('write_pages', False): | ||||
|             try: | ||||
|                 url = url_or_request.get_full_url() | ||||
|             except AttributeError: | ||||
|                 url = url_or_request | ||||
|             raw_filename = ('%s_%s.dump' % (video_id, url)) | ||||
|             filename = sanitize_filename(raw_filename, restricted=True) | ||||
|             self.to_screen(u'Saving request to ' + filename) | ||||
|             with open(filename, 'wb') as outf: | ||||
|                 outf.write(webpage_bytes) | ||||
|  | ||||
|         content = webpage_bytes.decode(encoding, 'replace') | ||||
|         return (content, urlh) | ||||
|  | ||||
| @@ -197,7 +251,7 @@ class InfoExtractor(object): | ||||
|         Perform a regex search on the given string, using a single or a list of | ||||
|         patterns returning the first matching group. | ||||
|         In case of failure return a default value or raise a WARNING or a | ||||
|         ExtractorError, depending on fatal, specifying the field name. | ||||
|         RegexNotFoundError, depending on fatal, specifying the field name. | ||||
|         """ | ||||
|         if isinstance(pattern, (str, compat_str, compiled_regex_type)): | ||||
|             mobj = re.search(pattern, string, flags) | ||||
| @@ -217,7 +271,7 @@ class InfoExtractor(object): | ||||
|         elif default is not None: | ||||
|             return default | ||||
|         elif fatal: | ||||
|             raise ExtractorError(u'Unable to extract %s' % _name) | ||||
|             raise RegexNotFoundError(u'Unable to extract %s' % _name) | ||||
|         else: | ||||
|             self._downloader.report_warning(u'unable to extract %s; ' | ||||
|                 u'please report this issue on http://yt-dl.org/bug' % _name) | ||||
| @@ -265,13 +319,21 @@ class InfoExtractor(object): | ||||
|  | ||||
|     # Helper functions for extracting OpenGraph info | ||||
|     @staticmethod | ||||
|     def _og_regex(prop): | ||||
|         return r'<meta.+?property=[\'"]og:%s[\'"].+?content=(?:"(.+?)"|\'(.+?)\')' % re.escape(prop) | ||||
|     def _og_regexes(prop): | ||||
|         content_re = r'content=(?:"([^>]+?)"|\'(.+?)\')' | ||||
|         property_re = r'property=[\'"]og:%s[\'"]' % re.escape(prop) | ||||
|         template = r'<meta[^>]+?%s[^>]+?%s' | ||||
|         return [ | ||||
|             template % (property_re, content_re), | ||||
|             template % (content_re, property_re), | ||||
|         ] | ||||
|  | ||||
|     def _og_search_property(self, prop, html, name=None, **kargs): | ||||
|         if name is None: | ||||
|             name = 'OpenGraph %s' % prop | ||||
|         escaped = self._search_regex(self._og_regex(prop), html, name, flags=re.DOTALL, **kargs) | ||||
|         escaped = self._search_regex(self._og_regexes(prop), html, name, flags=re.DOTALL, **kargs) | ||||
|         if escaped is None: | ||||
|             return None | ||||
|         return unescapeHTML(escaped) | ||||
|  | ||||
|     def _og_search_thumbnail(self, html, **kargs): | ||||
| @@ -283,10 +345,19 @@ class InfoExtractor(object): | ||||
|     def _og_search_title(self, html, **kargs): | ||||
|         return self._og_search_property('title', html, **kargs) | ||||
|  | ||||
|     def _og_search_video_url(self, html, name='video url', **kargs): | ||||
|         return self._html_search_regex([self._og_regex('video:secure_url'), | ||||
|                                         self._og_regex('video')], | ||||
|                                        html, name, **kargs) | ||||
|     def _og_search_video_url(self, html, name='video url', secure=True, **kargs): | ||||
|         regexes = self._og_regexes('video') | ||||
|         if secure: regexes = self._og_regexes('video:secure_url') + regexes | ||||
|         return self._html_search_regex(regexes, html, name, **kargs) | ||||
|  | ||||
|     def _rta_search(self, html): | ||||
|         # See http://www.rtalabel.org/index.php?content=howtofaq#single | ||||
|         if re.search(r'(?ix)<meta\s+name="rating"\s+' | ||||
|                      r'     content="RTA-5042-1996-1400-1577-RTA"', | ||||
|                      html): | ||||
|             return 18 | ||||
|         return 0 | ||||
|  | ||||
|  | ||||
| class SearchInfoExtractor(InfoExtractor): | ||||
|     """ | ||||
| @@ -325,7 +396,7 @@ class SearchInfoExtractor(InfoExtractor): | ||||
|  | ||||
|     def _get_n_results(self, query, n): | ||||
|         """Get a specified number of results for a query""" | ||||
|         raise NotImplementedError("This method must be implemented by sublclasses") | ||||
|         raise NotImplementedError("This method must be implemented by subclasses") | ||||
|  | ||||
|     @property | ||||
|     def SEARCH_KEY(self): | ||||
|   | ||||
| @@ -1,27 +1,80 @@ | ||||
| import re | ||||
| import json | ||||
| import itertools | ||||
|  | ||||
| from .common import InfoExtractor | ||||
| from .subtitles import SubtitlesInfoExtractor | ||||
|  | ||||
| from ..utils import ( | ||||
|     compat_urllib_request, | ||||
|     compat_str, | ||||
|     get_element_by_attribute, | ||||
|     get_element_by_id, | ||||
|     orderedSet, | ||||
|  | ||||
|     ExtractorError, | ||||
| ) | ||||
|  | ||||
| class DailymotionIE(InfoExtractor): | ||||
| class DailymotionBaseInfoExtractor(InfoExtractor): | ||||
|     @staticmethod | ||||
|     def _build_request(url): | ||||
|         """Build a request with the family filter disabled""" | ||||
|         request = compat_urllib_request.Request(url) | ||||
|         request.add_header('Cookie', 'family_filter=off') | ||||
|         request.add_header('Cookie', 'ff=off') | ||||
|         return request | ||||
|  | ||||
| class DailymotionIE(DailymotionBaseInfoExtractor, SubtitlesInfoExtractor): | ||||
|     """Information Extractor for Dailymotion""" | ||||
|  | ||||
|     _VALID_URL = r'(?i)(?:https?://)?(?:www\.)?dailymotion\.[a-z]{2,3}/video/([^/]+)' | ||||
|     _VALID_URL = r'(?i)(?:https?://)?(?:www\.)?dailymotion\.[a-z]{2,3}/(?:embed/)?video/([^/]+)' | ||||
|     IE_NAME = u'dailymotion' | ||||
|     _TEST = { | ||||
|         u'url': u'http://www.dailymotion.com/video/x33vw9_tutoriel-de-youtubeur-dl-des-video_tech', | ||||
|         u'file': u'x33vw9.mp4', | ||||
|         u'md5': u'392c4b85a60a90dc4792da41ce3144eb', | ||||
|         u'info_dict': { | ||||
|             u"uploader": u"Alex and Van .",  | ||||
|             u"title": u"Tutoriel de Youtubeur\"DL DES VIDEO DE YOUTUBE\"" | ||||
|  | ||||
|     _FORMATS = [ | ||||
|         (u'stream_h264_ld_url', u'ld'), | ||||
|         (u'stream_h264_url', u'standard'), | ||||
|         (u'stream_h264_hq_url', u'hq'), | ||||
|         (u'stream_h264_hd_url', u'hd'), | ||||
|         (u'stream_h264_hd1080_url', u'hd180'), | ||||
|     ] | ||||
|  | ||||
|     _TESTS = [ | ||||
|         { | ||||
|             u'url': u'http://www.dailymotion.com/video/x33vw9_tutoriel-de-youtubeur-dl-des-video_tech', | ||||
|             u'file': u'x33vw9.mp4', | ||||
|             u'md5': u'392c4b85a60a90dc4792da41ce3144eb', | ||||
|             u'info_dict': { | ||||
|                 u"uploader": u"Amphora Alex and Van .",  | ||||
|                 u"title": u"Tutoriel de Youtubeur\"DL DES VIDEO DE YOUTUBE\"" | ||||
|             } | ||||
|         }, | ||||
|         # Vevo video | ||||
|         { | ||||
|             u'url': u'http://www.dailymotion.com/video/x149uew_katy-perry-roar-official_musi', | ||||
|             u'file': u'USUV71301934.mp4', | ||||
|             u'info_dict': { | ||||
|                 u'title': u'Roar (Official)', | ||||
|                 u'uploader': u'Katy Perry', | ||||
|                 u'upload_date': u'20130905', | ||||
|             }, | ||||
|             u'params': { | ||||
|                 u'skip_download': True, | ||||
|             }, | ||||
|             u'skip': u'VEVO is only available in some countries', | ||||
|         }, | ||||
|         # age-restricted video | ||||
|         { | ||||
|             u'url': u'http://www.dailymotion.com/video/xyh2zz_leanna-decker-cyber-girl-of-the-year-desires-nude-playboy-plus_redband', | ||||
|             u'file': u'xyh2zz.mp4', | ||||
|             u'md5': u'0d667a7b9cebecc3c89ee93099c4159d', | ||||
|             u'info_dict': { | ||||
|                 u'title': 'Leanna Decker - Cyber Girl Of The Year Desires Nude [Playboy Plus]', | ||||
|                 u'uploader': 'HotWaves1012', | ||||
|                 u'age_limit': 18, | ||||
|             } | ||||
|  | ||||
|         } | ||||
|     } | ||||
|     ] | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         # Extract id and simplified title from URL | ||||
| @@ -29,20 +82,29 @@ class DailymotionIE(InfoExtractor): | ||||
|  | ||||
|         video_id = mobj.group(1).split('_')[0].split('?')[0] | ||||
|  | ||||
|         video_extension = 'mp4' | ||||
|         url = 'http://www.dailymotion.com/video/%s' % video_id | ||||
|  | ||||
|         # Retrieve video webpage to extract further information | ||||
|         request = compat_urllib_request.Request(url) | ||||
|         request.add_header('Cookie', 'family_filter=off') | ||||
|         request = self._build_request(url) | ||||
|         webpage = self._download_webpage(request, video_id) | ||||
|  | ||||
|         # Extract URL, uploader and title from webpage | ||||
|         self.report_extraction(video_id) | ||||
|  | ||||
|         # It may just embed a vevo video: | ||||
|         m_vevo = re.search( | ||||
|             r'<link rel="video_src" href="[^"]*?vevo.com[^"]*?videoId=(?P<id>[\w]*)', | ||||
|             webpage) | ||||
|         if m_vevo is not None: | ||||
|             vevo_id = m_vevo.group('id') | ||||
|             self.to_screen(u'Vevo video detected: %s' % vevo_id) | ||||
|             return self.url_result(u'vevo:%s' % vevo_id, ie='Vevo') | ||||
|  | ||||
|         video_uploader = self._search_regex([r'(?im)<span class="owner[^\"]+?">[^<]+?<a [^>]+?>([^<]+?)</a>', | ||||
|                                              # Looking for official user | ||||
|                                              r'<(?:span|a) .*?rel="author".*?>([^<]+?)</'], | ||||
|                                             webpage, 'video uploader') | ||||
|                                             webpage, 'video uploader', fatal=False) | ||||
|         age_limit = self._rta_search(webpage) | ||||
|  | ||||
|         video_upload_date = None | ||||
|         mobj = re.search(r'<div class="[^"]*uploaded_cont[^"]*" title="[^"]*">([0-9]{2})-([0-9]{2})-([0-9]{4})</div>', webpage) | ||||
| @@ -52,28 +114,115 @@ class DailymotionIE(InfoExtractor): | ||||
|         embed_url = 'http://www.dailymotion.com/embed/video/%s' % video_id | ||||
|         embed_page = self._download_webpage(embed_url, video_id, | ||||
|                                             u'Downloading embed page') | ||||
|         info = self._search_regex(r'var info = ({.*?}),', embed_page, 'video info') | ||||
|         info = self._search_regex(r'var info = ({.*?}),$', embed_page, | ||||
|             'video info', flags=re.MULTILINE) | ||||
|         info = json.loads(info) | ||||
|         if info.get('error') is not None: | ||||
|             msg = 'Couldn\'t get video, Dailymotion says: %s' % info['error']['title'] | ||||
|             raise ExtractorError(msg, expected=True) | ||||
|  | ||||
|         # TODO: support choosing qualities | ||||
|  | ||||
|         for key in ['stream_h264_hd1080_url','stream_h264_hd_url', | ||||
|                     'stream_h264_hq_url','stream_h264_url', | ||||
|                     'stream_h264_ld_url']: | ||||
|             if info.get(key):#key in info and info[key]: | ||||
|                 max_quality = key | ||||
|                 self.to_screen(u'Using %s' % key) | ||||
|                 break | ||||
|         else: | ||||
|         formats = [] | ||||
|         for (key, format_id) in self._FORMATS: | ||||
|             video_url = info.get(key) | ||||
|             if video_url is not None: | ||||
|                 m_size = re.search(r'H264-(\d+)x(\d+)', video_url) | ||||
|                 if m_size is not None: | ||||
|                     width, height = m_size.group(1), m_size.group(2) | ||||
|                 else: | ||||
|                     width, height = None, None | ||||
|                 formats.append({ | ||||
|                     'url': video_url, | ||||
|                     'ext': 'mp4', | ||||
|                     'format_id': format_id, | ||||
|                     'width': width, | ||||
|                     'height': height, | ||||
|                 }) | ||||
|         if not formats: | ||||
|             raise ExtractorError(u'Unable to extract video URL') | ||||
|         video_url = info[max_quality] | ||||
|  | ||||
|         return [{ | ||||
|         # subtitles | ||||
|         video_subtitles = self.extract_subtitles(video_id, webpage) | ||||
|         if self._downloader.params.get('listsubtitles', False): | ||||
|             self._list_available_subtitles(video_id, webpage) | ||||
|             return | ||||
|  | ||||
|         return { | ||||
|             'id':       video_id, | ||||
|             'url':      video_url, | ||||
|             'formats': formats, | ||||
|             'uploader': video_uploader, | ||||
|             'upload_date':  video_upload_date, | ||||
|             'title':    self._og_search_title(webpage), | ||||
|             'ext':      video_extension, | ||||
|             'thumbnail': info['thumbnail_url'] | ||||
|         }] | ||||
|             'subtitles':    video_subtitles, | ||||
|             'thumbnail': info['thumbnail_url'], | ||||
|             'age_limit': age_limit, | ||||
|         } | ||||
|  | ||||
|     def _get_available_subtitles(self, video_id, webpage): | ||||
|         try: | ||||
|             sub_list = self._download_webpage( | ||||
|                 'https://api.dailymotion.com/video/%s/subtitles?fields=id,language,url' % video_id, | ||||
|                 video_id, note=False) | ||||
|         except ExtractorError as err: | ||||
|             self._downloader.report_warning(u'unable to download video subtitles: %s' % compat_str(err)) | ||||
|             return {} | ||||
|         info = json.loads(sub_list) | ||||
|         if (info['total'] > 0): | ||||
|             sub_lang_list = dict((l['language'], l['url']) for l in info['list']) | ||||
|             return sub_lang_list | ||||
|         self._downloader.report_warning(u'video doesn\'t have subtitles') | ||||
|         return {} | ||||
|  | ||||
|  | ||||
| class DailymotionPlaylistIE(DailymotionBaseInfoExtractor): | ||||
|     IE_NAME = u'dailymotion:playlist' | ||||
|     _VALID_URL = r'(?:https?://)?(?:www\.)?dailymotion\.[a-z]{2,3}/playlist/(?P<id>.+?)/' | ||||
|     _MORE_PAGES_INDICATOR = r'<div class="next">.*?<a.*?href="/playlist/.+?".*?>.*?</a>.*?</div>' | ||||
|     _PAGE_TEMPLATE = 'https://www.dailymotion.com/playlist/%s/%s' | ||||
|  | ||||
|     def _extract_entries(self, id): | ||||
|         video_ids = [] | ||||
|         for pagenum in itertools.count(1): | ||||
|             request = self._build_request(self._PAGE_TEMPLATE % (id, pagenum)) | ||||
|             webpage = self._download_webpage(request, | ||||
|                                              id, u'Downloading page %s' % pagenum) | ||||
|  | ||||
|             playlist_el = get_element_by_attribute(u'class', u'row video_list', webpage) | ||||
|             video_ids.extend(re.findall(r'data-id="(.+?)"', playlist_el)) | ||||
|  | ||||
|             if re.search(self._MORE_PAGES_INDICATOR, webpage, re.DOTALL) is None: | ||||
|                 break | ||||
|         return [self.url_result('http://www.dailymotion.com/video/%s' % video_id, 'Dailymotion') | ||||
|                    for video_id in orderedSet(video_ids)] | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         playlist_id = mobj.group('id') | ||||
|         webpage = self._download_webpage(url, playlist_id) | ||||
|  | ||||
|         return {'_type': 'playlist', | ||||
|                 'id': playlist_id, | ||||
|                 'title': get_element_by_id(u'playlist_name', webpage), | ||||
|                 'entries': self._extract_entries(playlist_id), | ||||
|                 } | ||||
|  | ||||
|  | ||||
| class DailymotionUserIE(DailymotionPlaylistIE): | ||||
|     IE_NAME = u'dailymotion:user' | ||||
|     _VALID_URL = r'(?:https?://)?(?:www\.)?dailymotion\.[a-z]{2,3}/user/(?P<user>[^/]+)' | ||||
|     _MORE_PAGES_INDICATOR = r'<div class="next">.*?<a.*?href="/user/.+?".*?>.*?</a>.*?</div>' | ||||
|     _PAGE_TEMPLATE = 'http://www.dailymotion.com/user/%s/%s' | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         user = mobj.group('user') | ||||
|         webpage = self._download_webpage(url, user) | ||||
|         full_user = self._html_search_regex( | ||||
|             r'<a class="label" href="/%s".*?>(.*?)</' % re.escape(user), | ||||
|             webpage, u'user', flags=re.DOTALL) | ||||
|  | ||||
|         return { | ||||
|             '_type': 'playlist', | ||||
|             'id': user, | ||||
|             'title': full_user, | ||||
|             'entries': self._extract_entries(user), | ||||
|         } | ||||
|   | ||||
							
								
								
									
										74
									
								
								youtube_dl/extractor/daum.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								youtube_dl/extractor/daum.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,74 @@ | ||||
| # encoding: utf-8 | ||||
| import re | ||||
| import xml.etree.ElementTree | ||||
|  | ||||
| from .common import InfoExtractor | ||||
| from ..utils import ( | ||||
|     compat_urllib_parse, | ||||
|     determine_ext, | ||||
| ) | ||||
|  | ||||
|  | ||||
| class DaumIE(InfoExtractor): | ||||
|     _VALID_URL = r'https?://tvpot\.daum\.net/.*?clipid=(?P<id>\d+)' | ||||
|     IE_NAME = u'daum.net' | ||||
|  | ||||
|     _TEST = { | ||||
|         u'url': u'http://tvpot.daum.net/clip/ClipView.do?clipid=52554690', | ||||
|         u'file': u'52554690.mp4', | ||||
|         u'info_dict': { | ||||
|             u'title': u'DOTA 2GETHER 시즌2 6회 - 2부', | ||||
|             u'description': u'DOTA 2GETHER 시즌2 6회 - 2부', | ||||
|             u'upload_date': u'20130831', | ||||
|             u'duration': 3868, | ||||
|         }, | ||||
|     } | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         video_id = mobj.group(1) | ||||
|         canonical_url = 'http://tvpot.daum.net/v/%s' % video_id | ||||
|         webpage = self._download_webpage(canonical_url, video_id) | ||||
|         full_id = self._search_regex(r'<link rel="video_src" href=".+?vid=(.+?)"', | ||||
|             webpage, u'full id') | ||||
|         query = compat_urllib_parse.urlencode({'vid': full_id}) | ||||
|         info_xml = self._download_webpage( | ||||
|             'http://tvpot.daum.net/clip/ClipInfoXml.do?' + query, video_id, | ||||
|             u'Downloading video info') | ||||
|         urls_xml = self._download_webpage( | ||||
|             'http://videofarm.daum.net/controller/api/open/v1_2/MovieData.apixml?' + query, | ||||
|             video_id, u'Downloading video formats info') | ||||
|         info = xml.etree.ElementTree.fromstring(info_xml.encode('utf-8')) | ||||
|         urls = xml.etree.ElementTree.fromstring(urls_xml.encode('utf-8')) | ||||
|  | ||||
|         self.to_screen(u'%s: Getting video urls' % video_id) | ||||
|         formats = [] | ||||
|         for format_el in urls.findall('result/output_list/output_list'): | ||||
|             profile = format_el.attrib['profile'] | ||||
|             format_query = compat_urllib_parse.urlencode({ | ||||
|                 'vid': full_id, | ||||
|                 'profile': profile, | ||||
|             }) | ||||
|             url_xml = self._download_webpage( | ||||
|                 'http://videofarm.daum.net/controller/api/open/v1_2/MovieLocation.apixml?' + format_query, | ||||
|                 video_id, note=False) | ||||
|             url_doc = xml.etree.ElementTree.fromstring(url_xml.encode('utf-8')) | ||||
|             format_url = url_doc.find('result/url').text | ||||
|             formats.append({ | ||||
|                 'url': format_url, | ||||
|                 'ext': determine_ext(format_url), | ||||
|                 'format_id': profile, | ||||
|             }) | ||||
|  | ||||
|         info = { | ||||
|             'id': video_id, | ||||
|             'title': info.find('TITLE').text, | ||||
|             'formats': formats, | ||||
|             'thumbnail': self._og_search_thumbnail(webpage), | ||||
|             'description': info.find('CONTENTS').text, | ||||
|             'duration': int(info.find('DURATION').text), | ||||
|             'upload_date': info.find('REGDTTM').text[:8], | ||||
|         } | ||||
|         # TODO: Remove when #980 has been merged | ||||
|         info.update(formats[-1]) | ||||
|         return info | ||||
							
								
								
									
										39
									
								
								youtube_dl/extractor/defense.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								youtube_dl/extractor/defense.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| import re | ||||
| import json | ||||
|  | ||||
| from .common import InfoExtractor | ||||
|  | ||||
|  | ||||
| class DefenseGouvFrIE(InfoExtractor): | ||||
|     _IE_NAME = 'defense.gouv.fr' | ||||
|     _VALID_URL = (r'http://.*?\.defense\.gouv\.fr/layout/set/' | ||||
|         r'ligthboxvideo/base-de-medias/webtv/(.*)') | ||||
|  | ||||
|     _TEST = { | ||||
|         u'url': (u'http://www.defense.gouv.fr/layout/set/ligthboxvideo/' | ||||
|         u'base-de-medias/webtv/attaque-chimique-syrienne-du-21-aout-2013-1'), | ||||
|         u'file': u'11213.mp4', | ||||
|         u'md5': u'75bba6124da7e63d2d60b5244ec9430c', | ||||
|         "info_dict": { | ||||
|             "title": "attaque-chimique-syrienne-du-21-aout-2013-1" | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         title = re.match(self._VALID_URL, url).group(1) | ||||
|         webpage = self._download_webpage(url, title) | ||||
|         video_id = self._search_regex( | ||||
|             r"flashvars.pvg_id=\"(\d+)\";", | ||||
|             webpage, 'ID') | ||||
|          | ||||
|         json_url = ('http://static.videos.gouv.fr/brightcovehub/export/json/' | ||||
|             + video_id) | ||||
|         info = self._download_webpage(json_url, title, | ||||
|                                                   'Downloading JSON config') | ||||
|         video_url = json.loads(info)['renditions'][0]['url'] | ||||
|          | ||||
|         return {'id': video_id, | ||||
|                 'ext': 'mp4', | ||||
|                 'url': video_url, | ||||
|                 'title': title, | ||||
|                 } | ||||
| @@ -25,7 +25,7 @@ class DepositFilesIE(InfoExtractor): | ||||
|         url = 'http://depositfiles.com/en/files/' + file_id | ||||
|  | ||||
|         # Retrieve file webpage with 'Free download' button pressed | ||||
|         free_download_indication = { 'gateway_result' : '1' } | ||||
|         free_download_indication = {'gateway_result' : '1'} | ||||
|         request = compat_urllib_request.Request(url, compat_urllib_parse.urlencode(free_download_indication)) | ||||
|         try: | ||||
|             self.report_download_webpage(file_id) | ||||
|   | ||||
| @@ -54,6 +54,7 @@ class DreiSatIE(InfoExtractor): | ||||
|             'width': int(fe.find('./width').text), | ||||
|             'height': int(fe.find('./height').text), | ||||
|             'url': fe.find('./url').text, | ||||
|             'ext': determine_ext(fe.find('./url').text), | ||||
|             'filesize': int(fe.find('./filesize').text), | ||||
|             'video_bitrate': int(fe.find('./videoBitrate').text), | ||||
|             '3sat_qualityname': fe.find('./quality').text, | ||||
| @@ -79,7 +80,6 @@ class DreiSatIE(InfoExtractor): | ||||
|         } | ||||
|  | ||||
|         # TODO: Remove when #980 has been merged | ||||
|         info['url'] = formats[-1]['url'] | ||||
|         info['ext'] = determine_ext(formats[-1]['url']) | ||||
|         info.update(formats[-1]) | ||||
|  | ||||
|         return info | ||||
|         return info | ||||
|   | ||||
							
								
								
									
										37
									
								
								youtube_dl/extractor/ebaumsworld.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								youtube_dl/extractor/ebaumsworld.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| import re | ||||
| import xml.etree.ElementTree | ||||
|  | ||||
| from .common import InfoExtractor | ||||
| from ..utils import determine_ext | ||||
|  | ||||
|  | ||||
| class EbaumsWorldIE(InfoExtractor): | ||||
|     _VALID_URL = r'https?://www\.ebaumsworld\.com/video/watch/(?P<id>\d+)' | ||||
|  | ||||
|     _TEST = { | ||||
|         u'url': u'http://www.ebaumsworld.com/video/watch/83367677/', | ||||
|         u'file': u'83367677.mp4', | ||||
|         u'info_dict': { | ||||
|             u'title': u'A Giant Python Opens The Door', | ||||
|             u'description': u'This is how nightmares start...', | ||||
|             u'uploader': u'jihadpizza', | ||||
|         }, | ||||
|     } | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         video_id = mobj.group('id') | ||||
|         config_xml = self._download_webpage( | ||||
|             'http://www.ebaumsworld.com/video/player/%s' % video_id, video_id) | ||||
|         config = xml.etree.ElementTree.fromstring(config_xml.encode('utf-8')) | ||||
|         video_url = config.find('file').text | ||||
|  | ||||
|         return { | ||||
|             'id': video_id, | ||||
|             'title': config.find('title').text, | ||||
|             'url': video_url, | ||||
|             'ext': determine_ext(video_url), | ||||
|             'description': config.find('description').text, | ||||
|             'thumbnail': config.find('image').text, | ||||
|             'uploader': config.find('username').text, | ||||
|         } | ||||
| @@ -101,7 +101,7 @@ class EightTracksIE(InfoExtractor): | ||||
|         first_url = 'http://8tracks.com/sets/%s/play?player=sm&mix_id=%s&format=jsonh' % (session, mix_id) | ||||
|         next_url = first_url | ||||
|         res = [] | ||||
|         for i in itertools.count(): | ||||
|         for i in range(track_count): | ||||
|             api_json = self._download_webpage(next_url, playlist_id, | ||||
|                 note=u'Downloading song information %s/%s' % (str(i+1), track_count), | ||||
|                 errnote=u'Failed to download song information') | ||||
| @@ -116,7 +116,5 @@ class EightTracksIE(InfoExtractor): | ||||
|                 'ext': 'm4a', | ||||
|             } | ||||
|             res.append(info) | ||||
|             if api_data['set']['at_last_track']: | ||||
|                 break | ||||
|             next_url = 'http://8tracks.com/sets/%s/next?player=sm&mix_id=%s&format=jsonh&track_id=%s' % (session, mix_id, track_data['id']) | ||||
|         return res | ||||
|   | ||||
							
								
								
									
										37
									
								
								youtube_dl/extractor/eitb.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								youtube_dl/extractor/eitb.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| # encoding: utf-8 | ||||
| import re | ||||
|  | ||||
| from .common import InfoExtractor | ||||
| from .brightcove import BrightcoveIE | ||||
| from ..utils import ExtractorError | ||||
|  | ||||
|  | ||||
| class EitbIE(InfoExtractor): | ||||
|     IE_NAME = u'eitb.tv' | ||||
|     _VALID_URL = r'https?://www\.eitb\.tv/(eu/bideoa|es/video)/[^/]+/(?P<playlist_id>\d+)/(?P<chapter_id>\d+)' | ||||
|  | ||||
|     _TEST = { | ||||
|         u'add_ie': ['Brightcove'], | ||||
|         u'url': u'http://www.eitb.tv/es/video/60-minutos-60-minutos-2013-2014/2677100210001/2743577154001/lasa-y-zabala-30-anos/', | ||||
|         u'md5': u'edf4436247185adee3ea18ce64c47998', | ||||
|         u'info_dict': { | ||||
|             u'id': u'2743577154001', | ||||
|             u'ext': u'mp4', | ||||
|             u'title': u'60 minutos (Lasa y Zabala, 30 años)', | ||||
|             # All videos from eitb has this description in the brightcove info | ||||
|             u'description': u'.', | ||||
|             u'uploader': u'Euskal Telebista', | ||||
|         }, | ||||
|     } | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         chapter_id = mobj.group('chapter_id') | ||||
|         webpage = self._download_webpage(url, chapter_id) | ||||
|         bc_url = BrightcoveIE._extract_brightcove_url(webpage) | ||||
|         if bc_url is None: | ||||
|             raise ExtractorError(u'Could not extract the Brightcove url') | ||||
|         # The BrightcoveExperience object doesn't contain the video id, we set | ||||
|         # it manually | ||||
|         bc_url += '&%40videoPlayer={0}'.format(chapter_id) | ||||
|         return self.url_result(bc_url, BrightcoveIE.ie_key()) | ||||
| @@ -8,17 +8,32 @@ class ExfmIE(InfoExtractor): | ||||
|     IE_NAME = u'exfm' | ||||
|     IE_DESC = u'ex.fm' | ||||
|     _VALID_URL = r'(?:http://)?(?:www\.)?ex\.fm/song/([^/]+)' | ||||
|     _SOUNDCLOUD_URL_ = r'(?:http://)?(?:www\.)?api\.soundcloud.com/tracks/([^/]+)/stream' | ||||
|     _TEST = { | ||||
|         u'url': u'http://ex.fm/song/1bgtzg', | ||||
|         u'file': u'1bgtzg.mp3', | ||||
|         u'md5': u'8a7967a3fef10e59a1d6f86240fd41cf', | ||||
|         u'info_dict': { | ||||
|             u"title": u"We Can't Stop", | ||||
|             u"uploader": u"Miley Cyrus", | ||||
|             u'thumbnail': u'http://i1.sndcdn.com/artworks-000049666230-w9i7ef-t500x500.jpg?9d68d37' | ||||
|         } | ||||
|     } | ||||
|     _SOUNDCLOUD_URL = r'(?:http://)?(?:www\.)?api\.soundcloud.com/tracks/([^/]+)/stream' | ||||
|     _TESTS = [ | ||||
|         { | ||||
|             u'url': u'http://ex.fm/song/eh359', | ||||
|             u'file': u'44216187.mp3', | ||||
|             u'md5': u'e45513df5631e6d760970b14cc0c11e7', | ||||
|             u'info_dict': { | ||||
|                 u"title": u"Test House \"Love Is Not Enough\" (Extended Mix) DeadJournalist Exclusive", | ||||
|                 u"uploader": u"deadjournalist", | ||||
|                 u'upload_date': u'20120424', | ||||
|                 u'description': u'Test House \"Love Is Not Enough\" (Extended Mix) DeadJournalist Exclusive', | ||||
|             }, | ||||
|             u'note': u'Soundcloud song', | ||||
|             u'skip': u'The site is down too often', | ||||
|         }, | ||||
|         { | ||||
|             u'url': u'http://ex.fm/song/wddt8', | ||||
|             u'file': u'wddt8.mp3', | ||||
|             u'md5': u'966bd70741ac5b8570d8e45bfaed3643', | ||||
|             u'info_dict': { | ||||
|                 u'title': u'Safe and Sound', | ||||
|                 u'uploader': u'Capital Cities', | ||||
|             }, | ||||
|             u'skip': u'The site is down too often', | ||||
|         }, | ||||
|     ] | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
| @@ -26,11 +41,10 @@ class ExfmIE(InfoExtractor): | ||||
|         info_url = "http://ex.fm/api/v3/song/%s" %(song_id) | ||||
|         webpage = self._download_webpage(info_url, song_id) | ||||
|         info = json.loads(webpage) | ||||
|         song_url = re.match(self._SOUNDCLOUD_URL_,info['song']['url']) | ||||
|         if song_url is not None: | ||||
|         	song_url = song_url.group() + "?client_id=b45b1aa10f1ac2941910a7f0d10f8e28" | ||||
|         else: | ||||
|         	song_url = info['song']['url'] | ||||
|         song_url = info['song']['url'] | ||||
|         if re.match(self._SOUNDCLOUD_URL, song_url) is not None: | ||||
|             self.to_screen('Soundcloud song detected') | ||||
|             return self.url_result(song_url.replace('/stream',''), 'Soundcloud') | ||||
|         return [{ | ||||
|             'id':          song_id, | ||||
|             'url':         song_url, | ||||
|   | ||||
							
								
								
									
										50
									
								
								youtube_dl/extractor/extremetube.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								youtube_dl/extractor/extremetube.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | ||||
| import os | ||||
| import re | ||||
|  | ||||
| from .common import InfoExtractor | ||||
| from ..utils import ( | ||||
|     compat_urllib_parse_urlparse, | ||||
|     compat_urllib_request, | ||||
|     compat_urllib_parse, | ||||
| ) | ||||
|  | ||||
| class ExtremeTubeIE(InfoExtractor): | ||||
|     _VALID_URL = r'^(?:https?://)?(?:www\.)?(?P<url>extremetube\.com/video/.+?(?P<videoid>[0-9]+))(?:[/?&]|$)' | ||||
|     _TEST = { | ||||
|         u'url': u'http://www.extremetube.com/video/music-video-14-british-euro-brit-european-cumshots-swallow-652431', | ||||
|         u'file': u'652431.mp4', | ||||
|         u'md5': u'1fb9228f5e3332ec8c057d6ac36f33e0', | ||||
|         u'info_dict': { | ||||
|             u"title": u"Music Video 14 british euro brit european cumshots swallow", | ||||
|             u"uploader": u"unknown", | ||||
|             u"age_limit": 18, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         video_id = mobj.group('videoid') | ||||
|         url = 'http://www.' + mobj.group('url') | ||||
|  | ||||
|         req = compat_urllib_request.Request(url) | ||||
|         req.add_header('Cookie', 'age_verified=1') | ||||
|         webpage = self._download_webpage(req, video_id) | ||||
|  | ||||
|         video_title = self._html_search_regex(r'<h1 [^>]*?title="([^"]+)"[^>]*>\1<', webpage, u'title') | ||||
|         uploader = self._html_search_regex(r'>Posted by:(?=<)(?:\s|<[^>]*>)*(.+?)\|', webpage, u'uploader', fatal=False) | ||||
|         video_url = compat_urllib_parse.unquote(self._html_search_regex(r'video_url=(.+?)&', webpage, u'video_url')) | ||||
|         path = compat_urllib_parse_urlparse(video_url).path | ||||
|         extension = os.path.splitext(path)[1][1:] | ||||
|         format = path.split('/')[5].split('_')[:2] | ||||
|         format = "-".join(format) | ||||
|  | ||||
|         return { | ||||
|             'id': video_id, | ||||
|             'title': video_title, | ||||
|             'uploader': uploader, | ||||
|             'url': video_url, | ||||
|             'ext': extension, | ||||
|             'format': format, | ||||
|             'format_id': format, | ||||
|             'age_limit': 18, | ||||
|         } | ||||
| @@ -19,7 +19,8 @@ class FacebookIE(InfoExtractor): | ||||
|     """Information Extractor for Facebook""" | ||||
|  | ||||
|     _VALID_URL = r'^(?:https?://)?(?:\w+\.)?facebook\.com/(?:video/video|photo)\.php\?(?:.*?)v=(?P<ID>\d+)(?:.*)' | ||||
|     _LOGIN_URL = 'https://login.facebook.com/login.php?m&next=http%3A%2F%2Fm.facebook.com%2Fhome.php&' | ||||
|     _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' | ||||
|     _NETRC_MACHINE = 'facebook' | ||||
|     IE_NAME = u'facebook' | ||||
|     _TEST = { | ||||
| @@ -36,50 +37,56 @@ class FacebookIE(InfoExtractor): | ||||
|         """Report attempt to log in.""" | ||||
|         self.to_screen(u'Logging in') | ||||
|  | ||||
|     def _real_initialize(self): | ||||
|         if self._downloader is None: | ||||
|             return | ||||
|  | ||||
|         useremail = None | ||||
|         password = None | ||||
|         downloader_params = self._downloader.params | ||||
|  | ||||
|         # Attempt to use provided username and password or .netrc data | ||||
|         if downloader_params.get('username', None) is not None: | ||||
|             useremail = downloader_params['username'] | ||||
|             password = downloader_params['password'] | ||||
|         elif downloader_params.get('usenetrc', False): | ||||
|             try: | ||||
|                 info = netrc.netrc().authenticators(self._NETRC_MACHINE) | ||||
|                 if info is not None: | ||||
|                     useremail = info[0] | ||||
|                     password = info[2] | ||||
|                 else: | ||||
|                     raise netrc.NetrcParseError('No authenticators for %s' % self._NETRC_MACHINE) | ||||
|             except (IOError, netrc.NetrcParseError) as err: | ||||
|                 self._downloader.report_warning(u'parsing .netrc: %s' % compat_str(err)) | ||||
|                 return | ||||
|  | ||||
|     def _login(self): | ||||
|         (useremail, password) = self._get_login_info() | ||||
|         if useremail is None: | ||||
|             return | ||||
|  | ||||
|         # Log in | ||||
|         login_page_req = compat_urllib_request.Request(self._LOGIN_URL) | ||||
|         login_page_req.add_header('Cookie', 'locale=en_US') | ||||
|         self.report_login() | ||||
|         login_page = self._download_webpage(login_page_req, None, note=False, | ||||
|             errnote=u'Unable to download login page') | ||||
|         lsd = self._search_regex(r'"lsd":"(\w*?)"', login_page, u'lsd') | ||||
|         lgnrnd = self._search_regex(r'name="lgnrnd" value="([^"]*?)"', login_page, u'lgnrnd') | ||||
|  | ||||
|         login_form = { | ||||
|             'email': useremail, | ||||
|             'pass': password, | ||||
|             'login': 'Log+In' | ||||
|             'lsd': lsd, | ||||
|             'lgnrnd': lgnrnd, | ||||
|             'next': 'http://facebook.com/home.php', | ||||
|             'default_persistent': '0', | ||||
|             'legacy_return': '1', | ||||
|             'timezone': '-60', | ||||
|             'trynum': '1', | ||||
|             } | ||||
|         request = compat_urllib_request.Request(self._LOGIN_URL, compat_urllib_parse.urlencode(login_form)) | ||||
|         request.add_header('Content-Type', 'application/x-www-form-urlencoded') | ||||
|         try: | ||||
|             self.report_login() | ||||
|             login_results = compat_urllib_request.urlopen(request).read() | ||||
|             if re.search(r'<form(.*)name="login"(.*)</form>', login_results) is not None: | ||||
|                 self._downloader.report_warning(u'unable to log in: bad username/password, or exceded login rate limit (~3/min). Check credentials or wait.') | ||||
|                 return | ||||
|  | ||||
|             check_form = { | ||||
|                 'fb_dtsg': self._search_regex(r'"fb_dtsg":"(.*?)"', login_results, u'fb_dtsg'), | ||||
|                 'nh': self._search_regex(r'name="nh" value="(\w*?)"', login_results, u'nh'), | ||||
|                 'name_action_selected': 'dont_save', | ||||
|                 'submit[Continue]': self._search_regex(r'<input value="(.*?)" name="submit\[Continue\]"', login_results, u'continue'), | ||||
|             } | ||||
|             check_req = compat_urllib_request.Request(self._CHECKPOINT_URL, compat_urllib_parse.urlencode(check_form)) | ||||
|             check_req.add_header('Content-Type', 'application/x-www-form-urlencoded') | ||||
|             check_response = compat_urllib_request.urlopen(check_req).read() | ||||
|             if re.search(r'id="checkpointSubmitButton"', check_response) is not None: | ||||
|                 self._downloader.report_warning(u'Unable to confirm login, you have to login in your brower and authorize the login.') | ||||
|         except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err: | ||||
|             self._downloader.report_warning(u'unable to log in: %s' % compat_str(err)) | ||||
|             return | ||||
|  | ||||
|     def _real_initialize(self): | ||||
|         self._login() | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         if mobj is None: | ||||
| @@ -93,7 +100,13 @@ class FacebookIE(InfoExtractor): | ||||
|         AFTER = '.forEach(function(variable) {swf.addVariable(variable[0], variable[1]);});' | ||||
|         m = re.search(re.escape(BEFORE) + '(.*?)' + re.escape(AFTER), webpage) | ||||
|         if not m: | ||||
|             raise ExtractorError(u'Cannot parse data') | ||||
|             m_msg = re.search(r'class="[^"]*uiInterstitialContent[^"]*"><div>(.*?)</div>', webpage) | ||||
|             if m_msg is not None: | ||||
|                 raise ExtractorError( | ||||
|                     u'The video is not available, Facebook said: "%s"' % m_msg.group(1), | ||||
|                     expected=True) | ||||
|             else: | ||||
|                 raise ExtractorError(u'Cannot parse data') | ||||
|         data = dict(json.loads(m.group(1))) | ||||
|         params_raw = compat_urllib_parse.unquote(data['params']) | ||||
|         params = json.loads(params_raw) | ||||
| @@ -106,8 +119,8 @@ class FacebookIE(InfoExtractor): | ||||
|         video_duration = int(video_data['video_duration']) | ||||
|         thumbnail = video_data['thumbnail_src'] | ||||
|  | ||||
|         video_title = self._html_search_regex('<h2 class="uiHeaderTitle">([^<]+)</h2>', | ||||
|             webpage, u'title') | ||||
|         video_title = self._html_search_regex( | ||||
|             r'<h2 class="uiHeaderTitle">([^<]*)</h2>', webpage, u'title') | ||||
|  | ||||
|         info = { | ||||
|             'id': video_id, | ||||
|   | ||||
							
								
								
									
										58
									
								
								youtube_dl/extractor/faz.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								youtube_dl/extractor/faz.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | ||||
| # encoding: utf-8 | ||||
| import re | ||||
| import xml.etree.ElementTree | ||||
|  | ||||
| from .common import InfoExtractor | ||||
| from ..utils import ( | ||||
|     determine_ext, | ||||
| ) | ||||
|  | ||||
|  | ||||
| class FazIE(InfoExtractor): | ||||
|     IE_NAME = u'faz.net' | ||||
|     _VALID_URL = r'https?://www\.faz\.net/multimedia/videos/.*?-(?P<id>\d+).html' | ||||
|  | ||||
|     _TEST = { | ||||
|         u'url': u'http://www.faz.net/multimedia/videos/stockholm-chemie-nobelpreis-fuer-drei-amerikanische-forscher-12610585.html', | ||||
|         u'file': u'12610585.mp4', | ||||
|         u'info_dict': { | ||||
|             u'title': u'Stockholm: Chemie-Nobelpreis für drei amerikanische Forscher', | ||||
|             u'description': u'md5:1453fbf9a0d041d985a47306192ea253', | ||||
|         }, | ||||
|     } | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         video_id = mobj.group('id') | ||||
|         self.to_screen(video_id) | ||||
|         webpage = self._download_webpage(url, video_id) | ||||
|         config_xml_url = self._search_regex(r'writeFLV\(\'(.+?)\',', webpage, | ||||
|             u'config xml url') | ||||
|         config_xml = self._download_webpage(config_xml_url, video_id, | ||||
|             u'Downloading config xml') | ||||
|         config = xml.etree.ElementTree.fromstring(config_xml.encode('utf-8')) | ||||
|  | ||||
|         encodings = config.find('ENCODINGS') | ||||
|         formats = [] | ||||
|         for code in ['LOW', 'HIGH', 'HQ']: | ||||
|             encoding = encodings.find(code) | ||||
|             if encoding is None: | ||||
|                 continue | ||||
|             encoding_url = encoding.find('FILENAME').text | ||||
|             formats.append({ | ||||
|                 'url': encoding_url, | ||||
|                 'ext': determine_ext(encoding_url), | ||||
|                 'format_id': code.lower(), | ||||
|             }) | ||||
|  | ||||
|         descr = self._html_search_regex(r'<p class="Content Copy">(.*?)</p>', webpage, u'description') | ||||
|         info = { | ||||
|             'id': video_id, | ||||
|             'title': self._og_search_title(webpage), | ||||
|             'formats': formats, | ||||
|             'description': descr, | ||||
|             'thumbnail': config.find('STILL/STILL_BIG').text, | ||||
|         } | ||||
|         # TODO: Remove when #980 has been merged | ||||
|         info.update(formats[-1]) | ||||
|         return info | ||||
							
								
								
									
										79
									
								
								youtube_dl/extractor/fktv.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								youtube_dl/extractor/fktv.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | ||||
| import re | ||||
| import random | ||||
| import json | ||||
|  | ||||
| from .common import InfoExtractor | ||||
| from ..utils import ( | ||||
|     determine_ext, | ||||
|     get_element_by_id, | ||||
|     clean_html, | ||||
| ) | ||||
|  | ||||
|  | ||||
| class FKTVIE(InfoExtractor): | ||||
|     IE_NAME = u'fernsehkritik.tv' | ||||
|     _VALID_URL = r'(?:http://)?(?:www\.)?fernsehkritik.tv/folge-(?P<ep>[0-9]+)(?:/.*)?' | ||||
|  | ||||
|     _TEST = { | ||||
|         u'url': u'http://fernsehkritik.tv/folge-1', | ||||
|         u'file': u'00011.flv', | ||||
|         u'info_dict': { | ||||
|             u'title': u'Folge 1 vom 10. April 2007', | ||||
|             u'description': u'md5:fb4818139c7cfe6907d4b83412a6864f', | ||||
|         }, | ||||
|     } | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         episode = int(mobj.group('ep')) | ||||
|  | ||||
|         server = random.randint(2, 4) | ||||
|         video_thumbnail = 'http://fernsehkritik.tv/images/magazin/folge%d.jpg' % episode | ||||
|         start_webpage = self._download_webpage('http://fernsehkritik.tv/folge-%d/Start' % episode, | ||||
|             episode) | ||||
|         playlist = self._search_regex(r'playlist = (\[.*?\]);', start_webpage, | ||||
|             u'playlist', flags=re.DOTALL) | ||||
|         files = json.loads(re.sub('{[^{}]*?}', '{}', playlist)) | ||||
|         # TODO: return a single multipart video | ||||
|         videos = [] | ||||
|         for i, _ in enumerate(files, 1): | ||||
|             video_id = '%04d%d' % (episode, i) | ||||
|             video_url = 'http://dl%d.fernsehkritik.tv/fernsehkritik%d%s.flv' % (server, episode, '' if i == 1 else '-%d' % i) | ||||
|             video_title = 'Fernsehkritik %d.%d' % (episode, i) | ||||
|             videos.append({ | ||||
|                 'id': video_id, | ||||
|                 'url': video_url, | ||||
|                 'ext': determine_ext(video_url), | ||||
|                 'title': clean_html(get_element_by_id('eptitle', start_webpage)), | ||||
|                 'description': clean_html(get_element_by_id('contentlist', start_webpage)), | ||||
|                 'thumbnail': video_thumbnail | ||||
|             }) | ||||
|         return videos | ||||
|  | ||||
|  | ||||
| class FKTVPosteckeIE(InfoExtractor): | ||||
|     IE_NAME = u'fernsehkritik.tv:postecke' | ||||
|     _VALID_URL = r'(?:http://)?(?:www\.)?fernsehkritik.tv/inline-video/postecke.php\?(.*&)?ep=(?P<ep>[0-9]+)(&|$)' | ||||
|     _TEST = { | ||||
|         u'url': u'http://fernsehkritik.tv/inline-video/postecke.php?iframe=true&width=625&height=440&ep=120', | ||||
|         u'file': u'0120.flv', | ||||
|         u'md5': u'262f0adbac80317412f7e57b4808e5c4', | ||||
|         u'info_dict': { | ||||
|             u"title": u"Postecke 120" | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         episode = int(mobj.group('ep')) | ||||
|  | ||||
|         server = random.randint(2, 4) | ||||
|         video_id = '%04d' % episode | ||||
|         video_url = 'http://dl%d.fernsehkritik.tv/postecke/postecke%d.flv' % (server, episode) | ||||
|         video_title = 'Postecke %d' % episode | ||||
|         return { | ||||
|             'id':       video_id, | ||||
|             'url':      video_url, | ||||
|             'ext':      determine_ext(video_url), | ||||
|             'title':    video_title, | ||||
|         } | ||||
| @@ -9,7 +9,7 @@ from ..utils import ( | ||||
|  | ||||
| class FlickrIE(InfoExtractor): | ||||
|     """Information Extractor for Flickr videos""" | ||||
|     _VALID_URL = r'(?:https?://)?(?:www\.)?flickr\.com/photos/(?P<uploader_id>[\w\-_@]+)/(?P<id>\d+).*' | ||||
|     _VALID_URL = r'(?:https?://)?(?:www\.|secure\.)?flickr\.com/photos/(?P<uploader_id>[\w\-_@]+)/(?P<id>\d+).*' | ||||
|     _TEST = { | ||||
|         u'url': u'http://www.flickr.com/photos/forestwander-nature-pictures/5645318632/in/photostream/', | ||||
|         u'file': u'5645318632.mp4', | ||||
|   | ||||
							
								
								
									
										129
									
								
								youtube_dl/extractor/francetv.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								youtube_dl/extractor/francetv.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,129 @@ | ||||
| # encoding: utf-8 | ||||
| import re | ||||
| import xml.etree.ElementTree | ||||
| import json | ||||
|  | ||||
| from .common import InfoExtractor | ||||
| from ..utils import ( | ||||
|     compat_urlparse, | ||||
| ) | ||||
|  | ||||
|  | ||||
| class FranceTVBaseInfoExtractor(InfoExtractor): | ||||
|     def _extract_video(self, video_id): | ||||
|         xml_desc = self._download_webpage( | ||||
|             'http://www.francetvinfo.fr/appftv/webservices/video/' | ||||
|             'getInfosOeuvre.php?id-diffusion=' | ||||
|             + video_id, video_id, 'Downloading XML config') | ||||
|         info = xml.etree.ElementTree.fromstring(xml_desc.encode('utf-8')) | ||||
|  | ||||
|         manifest_url = info.find('videos/video/url').text | ||||
|         video_url = manifest_url.replace('manifest.f4m', 'index_2_av.m3u8') | ||||
|         video_url = video_url.replace('/z/', '/i/') | ||||
|         thumbnail_path = info.find('image').text | ||||
|  | ||||
|         return {'id': video_id, | ||||
|                 'ext': 'mp4', | ||||
|                 'url': video_url, | ||||
|                 'title': info.find('titre').text, | ||||
|                 'thumbnail': compat_urlparse.urljoin('http://pluzz.francetv.fr', thumbnail_path), | ||||
|                 'description': info.find('synopsis').text, | ||||
|                 } | ||||
|  | ||||
|  | ||||
| class PluzzIE(FranceTVBaseInfoExtractor): | ||||
|     IE_NAME = u'pluzz.francetv.fr' | ||||
|     _VALID_URL = r'https?://pluzz\.francetv\.fr/videos/(.*?)\.html' | ||||
|  | ||||
|     # Can't use tests, videos expire in 7 days | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         title = re.match(self._VALID_URL, url).group(1) | ||||
|         webpage = self._download_webpage(url, title) | ||||
|         video_id = self._search_regex( | ||||
|             r'data-diffusion="(\d+)"', webpage, 'ID') | ||||
|         return self._extract_video(video_id) | ||||
|  | ||||
|  | ||||
| class FranceTvInfoIE(FranceTVBaseInfoExtractor): | ||||
|     IE_NAME = u'francetvinfo.fr' | ||||
|     _VALID_URL = r'https?://www\.francetvinfo\.fr/replay.*/(?P<title>.+).html' | ||||
|  | ||||
|     _TEST = { | ||||
|         u'url': u'http://www.francetvinfo.fr/replay-jt/france-3/soir-3/jt-grand-soir-3-lundi-26-aout-2013_393427.html', | ||||
|         u'file': u'84981923.mp4', | ||||
|         u'info_dict': { | ||||
|             u'title': u'Soir 3', | ||||
|         }, | ||||
|         u'params': { | ||||
|             u'skip_download': True, | ||||
|         }, | ||||
|     } | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         page_title = mobj.group('title') | ||||
|         webpage = self._download_webpage(url, page_title) | ||||
|         video_id = self._search_regex(r'id-video=(\d+?)"', webpage, u'video id') | ||||
|         return self._extract_video(video_id) | ||||
|  | ||||
|  | ||||
| class France2IE(FranceTVBaseInfoExtractor): | ||||
|     IE_NAME = u'france2.fr' | ||||
|     _VALID_URL = r'''(?x)https?://www\.france2\.fr/ | ||||
|         (?: | ||||
|             emissions/.*?/videos/(?P<id>\d+) | ||||
|         |   emission/(?P<key>[^/?]+) | ||||
|         )''' | ||||
|  | ||||
|     _TEST = { | ||||
|         u'url': u'http://www.france2.fr/emissions/13h15-le-samedi-le-dimanche/videos/75540104', | ||||
|         u'file': u'75540104.mp4', | ||||
|         u'info_dict': { | ||||
|             u'title': u'13h15, le samedi...', | ||||
|             u'description': u'md5:2e5b58ba7a2d3692b35c792be081a03d', | ||||
|         }, | ||||
|         u'params': { | ||||
|             u'skip_download': True, | ||||
|         }, | ||||
|     } | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         if mobj.group('key'): | ||||
|             webpage = self._download_webpage(url, mobj.group('key')) | ||||
|             video_id = self._html_search_regex( | ||||
|                 r'''(?x)<div\s+class="video-player">\s* | ||||
|                     <a\s+href="http://videos.francetv.fr/video/([0-9]+)"\s+ | ||||
|                     class="francetv-video-player">''', | ||||
|                 webpage, u'video ID') | ||||
|         else: | ||||
|             video_id = mobj.group('id') | ||||
|         return self._extract_video(video_id) | ||||
|  | ||||
|  | ||||
| class GenerationQuoiIE(InfoExtractor): | ||||
|     IE_NAME = u'france2.fr:generation-quoi' | ||||
|     _VALID_URL = r'https?://generation-quoi\.france2\.fr/portrait/(?P<name>.*)(\?|$)' | ||||
|  | ||||
|     _TEST = { | ||||
|         u'url': u'http://generation-quoi.france2.fr/portrait/garde-a-vous', | ||||
|         u'file': u'k7FJX8VBcvvLmX4wA5Q.mp4', | ||||
|         u'info_dict': { | ||||
|             u'title': u'Génération Quoi - Garde à Vous', | ||||
|             u'uploader': u'Génération Quoi', | ||||
|         }, | ||||
|         u'params': { | ||||
|             # It uses Dailymotion | ||||
|             u'skip_download': True, | ||||
|         }, | ||||
|     } | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         name = mobj.group('name') | ||||
|         info_url = compat_urlparse.urljoin(url, '/medias/video/%s.json' % name) | ||||
|         info_json = self._download_webpage(info_url, name) | ||||
|         info = json.loads(info_json) | ||||
|         return self.url_result('http://www.dailymotion.com/video/%s' % info['id'], | ||||
|             ie='Dailymotion') | ||||
| @@ -21,17 +21,15 @@ class FunnyOrDieIE(InfoExtractor): | ||||
|         video_id = mobj.group('id') | ||||
|         webpage = self._download_webpage(url, video_id) | ||||
|  | ||||
|         video_url = self._html_search_regex(r'<video[^>]*>\s*<source[^>]*>\s*<source src="(?P<url>[^"]+)"', | ||||
|         video_url = self._search_regex( | ||||
|             [r'type="video/mp4" src="(.*?)"', r'src="([^>]*?)" type=\'video/mp4\''], | ||||
|             webpage, u'video URL', flags=re.DOTALL) | ||||
|  | ||||
|         title = self._html_search_regex((r"<h1 class='player_page_h1'.*?>(?P<title>.*?)</h1>", | ||||
|             r'<title>(?P<title>[^<]+?)</title>'), webpage, 'title', flags=re.DOTALL) | ||||
|  | ||||
|         info = { | ||||
|             'id': video_id, | ||||
|             'url': video_url, | ||||
|             'ext': 'mp4', | ||||
|             'title': title, | ||||
|             'title': self._og_search_title(webpage), | ||||
|             'description': self._og_search_description(webpage), | ||||
|         } | ||||
|         return [info] | ||||
|   | ||||
							
								
								
									
										38
									
								
								youtube_dl/extractor/gamekings.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								youtube_dl/extractor/gamekings.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| import re | ||||
|  | ||||
| from .common import InfoExtractor | ||||
|  | ||||
|  | ||||
| class GamekingsIE(InfoExtractor): | ||||
|     _VALID_URL = r'http?://www\.gamekings\.tv/videos/(?P<name>[0-9a-z\-]+)' | ||||
|     _TEST = { | ||||
|         u"url": u"http://www.gamekings.tv/videos/phoenix-wright-ace-attorney-dual-destinies-review/", | ||||
|         u'file': u'20130811.mp4', | ||||
|         # MD5 is flaky, seems to change regularly | ||||
|         #u'md5': u'2f32b1f7b80fdc5cb616efb4f387f8a3', | ||||
|         u'info_dict': { | ||||
|             u"title": u"Phoenix Wright: Ace Attorney \u2013 Dual Destinies Review", | ||||
|             u"description": u"Melle en Steven hebben voor de review een week in de rechtbank doorbracht met Phoenix Wright: Ace Attorney - Dual Destinies.", | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|  | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         name = mobj.group('name') | ||||
|         webpage = self._download_webpage(url, name) | ||||
|         video_url = self._og_search_video_url(webpage) | ||||
|  | ||||
|         video = re.search(r'[0-9]+', video_url) | ||||
|         video_id = video.group(0) | ||||
|  | ||||
|         # Todo: add medium format | ||||
|         video_url = video_url.replace(video_id, 'large/' + video_id) | ||||
|  | ||||
|         return { | ||||
|             'id': video_id, | ||||
|             'ext': 'mp4', | ||||
|             'url': video_url, | ||||
|             'title': self._og_search_title(webpage), | ||||
|             'description': self._og_search_description(webpage), | ||||
|         } | ||||
| @@ -1,55 +1,59 @@ | ||||
| import re | ||||
| import xml.etree.ElementTree | ||||
| import json | ||||
|  | ||||
| from .common import InfoExtractor | ||||
| from ..utils import ( | ||||
|     unified_strdate, | ||||
|     compat_urllib_parse, | ||||
|     compat_urlparse, | ||||
|     unescapeHTML, | ||||
|     get_meta_content, | ||||
| ) | ||||
|  | ||||
|  | ||||
| class GameSpotIE(InfoExtractor): | ||||
|     _VALID_URL = r'(?:http://)?(?:www\.)?gamespot\.com/.*-(?P<page_id>\d+)/?' | ||||
|     _TEST = { | ||||
|         u"url": u"http://www.gamespot.com/arma-iii/videos/arma-iii-community-guide-sitrep-i-6410818/", | ||||
|         u"file": u"6410818.mp4", | ||||
|         u"file": u"gs-2300-6410818.mp4", | ||||
|         u"md5": u"b2a30deaa8654fcccd43713a6b6a4825", | ||||
|         u"info_dict": { | ||||
|             u"title": u"Arma III - Community Guide: SITREP I", | ||||
|             u"upload_date": u"20130627",  | ||||
|             u"title": u"Arma 3 - Community Guide: SITREP I", | ||||
|             u'description': u'Check out this video where some of the basics of Arma 3 is explained.', | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         page_id = mobj.group('page_id') | ||||
|         page_id = video_id = mobj.group('page_id') | ||||
|         webpage = self._download_webpage(url, page_id) | ||||
|         video_id = self._html_search_regex([r'"og:video" content=".*?\?id=(\d+)"', | ||||
|                                             r'http://www\.gamespot\.com/videoembed/(\d+)'], | ||||
|                                            webpage, 'video id') | ||||
|         data = compat_urllib_parse.urlencode({'id': video_id, 'newplayer': '1'}) | ||||
|         info_url = 'http://www.gamespot.com/pages/video_player/xml.php?' + data | ||||
|         info_xml = self._download_webpage(info_url, video_id) | ||||
|         doc = xml.etree.ElementTree.fromstring(info_xml) | ||||
|         clip_el = doc.find('./playList/clip') | ||||
|         data_video_json = self._search_regex(r'data-video=\'(.*?)\'', webpage, u'data video') | ||||
|         data_video = json.loads(unescapeHTML(data_video_json)) | ||||
|  | ||||
|         http_urls = [{'url': node.find('filePath').text, | ||||
|                       'rate': int(node.find('rate').text)} | ||||
|             for node in clip_el.find('./httpURI')] | ||||
|         best_quality = sorted(http_urls, key=lambda f: f['rate'])[-1] | ||||
|         video_url = best_quality['url'] | ||||
|         title = clip_el.find('./title').text | ||||
|         ext = video_url.rpartition('.')[2] | ||||
|         thumbnail_url = clip_el.find('./screenGrabURI').text | ||||
|         view_count = int(clip_el.find('./views').text) | ||||
|         upload_date = unified_strdate(clip_el.find('./postDate').text) | ||||
|         # Transform the manifest url to a link to the mp4 files | ||||
|         # they are used in mobile devices. | ||||
|         f4m_url = data_video['videoStreams']['f4m_stream'] | ||||
|         f4m_path = compat_urlparse.urlparse(f4m_url).path | ||||
|         QUALITIES_RE = r'((,\d+)+,?)' | ||||
|         qualities = self._search_regex(QUALITIES_RE, f4m_path, u'qualities').strip(',').split(',') | ||||
|         http_path = f4m_path[1:].split('/', 1)[1] | ||||
|         http_template = re.sub(QUALITIES_RE, r'%s', http_path) | ||||
|         http_template = http_template.replace('.csmil/manifest.f4m', '') | ||||
|         http_template = compat_urlparse.urljoin('http://video.gamespotcdn.com/', http_template) | ||||
|         formats = [] | ||||
|         for q in qualities: | ||||
|             formats.append({ | ||||
|                 'url': http_template % q, | ||||
|                 'ext': 'mp4', | ||||
|                 'format_id': q, | ||||
|             }) | ||||
|  | ||||
|         return [{ | ||||
|             'id'          : video_id, | ||||
|             'url'         : video_url, | ||||
|             'ext'         : ext, | ||||
|             'title'       : title, | ||||
|             'thumbnail'   : thumbnail_url, | ||||
|             'upload_date' : upload_date, | ||||
|             'view_count'  : view_count, | ||||
|         }] | ||||
|         info = { | ||||
|             'id': data_video['guid'], | ||||
|             'title': compat_urllib_parse.unquote(data_video['title']), | ||||
|             'formats': formats, | ||||
|             'description': get_meta_content('description', webpage), | ||||
|             'thumbnail': self._og_search_thumbnail(webpage), | ||||
|         } | ||||
|         # TODO: Remove when #980 has been merged | ||||
|         info.update(formats[-1]) | ||||
|         return info | ||||
|   | ||||
| @@ -8,11 +8,15 @@ from ..utils import ( | ||||
|     compat_urllib_error, | ||||
|     compat_urllib_parse, | ||||
|     compat_urllib_request, | ||||
|     compat_urlparse, | ||||
|  | ||||
|     ExtractorError, | ||||
|     smuggle_url, | ||||
|     unescapeHTML, | ||||
| ) | ||||
| from .brightcove import BrightcoveIE | ||||
|  | ||||
|  | ||||
| class GenericIE(InfoExtractor): | ||||
|     IE_DESC = u'Generic downloader that works on some sites' | ||||
|     _VALID_URL = r'.*' | ||||
| @@ -21,23 +25,52 @@ class GenericIE(InfoExtractor): | ||||
|         { | ||||
|             u'url': u'http://www.hodiho.fr/2013/02/regis-plante-sa-jeep.html', | ||||
|             u'file': u'13601338388002.mp4', | ||||
|             u'md5': u'85b90ccc9d73b4acd9138d3af4c27f89', | ||||
|             u'md5': u'6e15c93721d7ec9e9ca3fdbf07982cfd', | ||||
|             u'info_dict': { | ||||
|                 u"uploader": u"www.hodiho.fr",  | ||||
|                 u"uploader": u"www.hodiho.fr", | ||||
|                 u"title": u"R\u00e9gis plante sa Jeep" | ||||
|             } | ||||
|         }, | ||||
|         # embedded vimeo video | ||||
|         { | ||||
|             u'url': u'http://www.8tv.cat/8aldia/videos/xavier-sala-i-martin-aquesta-tarda-a-8-al-dia/', | ||||
|             u'file': u'2371591881001.mp4', | ||||
|             u'md5': u'9e80619e0a94663f0bdc849b4566af19', | ||||
|             u'note': u'Test Brightcove downloads and detection in GenericIE', | ||||
|             u'add_ie': ['Vimeo'], | ||||
|             u'url': u'http://skillsmatter.com/podcast/home/move-semanticsperfect-forwarding-and-rvalue-references', | ||||
|             u'file': u'22444065.mp4', | ||||
|             u'md5': u'2903896e23df39722c33f015af0666e2', | ||||
|             u'info_dict': { | ||||
|                 u'title': u'Xavier Sala i Martín: “Un banc que no presta és un banc zombi que no serveix per a res”', | ||||
|                 u'uploader': u'8TV', | ||||
|                 u'description': u'md5:a950cc4285c43e44d763d036710cd9cd', | ||||
|                 u'title': u'ACCU 2011: Move Semantics,Perfect Forwarding, and Rvalue references- Scott Meyers- 13/04/2011', | ||||
|                 u"uploader_id": u"skillsmatter", | ||||
|                 u"uploader": u"Skills Matter", | ||||
|             } | ||||
|         }, | ||||
|         # bandcamp page with custom domain | ||||
|         { | ||||
|             u'add_ie': ['Bandcamp'], | ||||
|             u'url': u'http://bronyrock.com/track/the-pony-mash', | ||||
|             u'file': u'3235767654.mp3', | ||||
|             u'info_dict': { | ||||
|                 u'title': u'The Pony Mash', | ||||
|                 u'uploader': u'M_Pallante', | ||||
|             }, | ||||
|             u'skip': u'There is a limit of 200 free downloads / month for the test song', | ||||
|         }, | ||||
|         # embedded brightcove video | ||||
|         # it also tests brightcove videos that need to set the 'Referer' in the | ||||
|         # http requests | ||||
|         { | ||||
|             u'add_ie': ['Brightcove'], | ||||
|             u'url': u'http://www.bfmtv.com/video/bfmbusiness/cours-bourse/cours-bourse-l-analyse-technique-154522/', | ||||
|             u'info_dict': { | ||||
|                 u'id': u'2765128793001', | ||||
|                 u'ext': u'mp4', | ||||
|                 u'title': u'Le cours de bourse : l’analyse technique', | ||||
|                 u'description': u'md5:7e9ad046e968cb2d1114004aba466fd9', | ||||
|                 u'uploader': u'BFM BUSINESS', | ||||
|             }, | ||||
|             u'params': { | ||||
|                 u'skip_download': True, | ||||
|             }, | ||||
|         }, | ||||
|     ] | ||||
|  | ||||
|     def report_download_webpage(self, video_id): | ||||
| @@ -107,8 +140,18 @@ class GenericIE(InfoExtractor): | ||||
|         return new_url | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         new_url = self._test_redirect(url) | ||||
|         if new_url: return [self.url_result(new_url)] | ||||
|         parsed_url = compat_urlparse.urlparse(url) | ||||
|         if not parsed_url.scheme: | ||||
|             self._downloader.report_warning('The url doesn\'t specify the protocol, trying with http') | ||||
|             return self.url_result('http://' + url) | ||||
|  | ||||
|         try: | ||||
|             new_url = self._test_redirect(url) | ||||
|             if new_url: | ||||
|                 return [self.url_result(new_url)] | ||||
|         except compat_urllib_error.HTTPError: | ||||
|             # This may be a stupid server that doesn't like HEAD, our UA, or so | ||||
|             pass | ||||
|  | ||||
|         video_id = url.split('/')[-1] | ||||
|         try: | ||||
| @@ -116,16 +159,36 @@ class GenericIE(InfoExtractor): | ||||
|         except ValueError: | ||||
|             # since this is the last-resort InfoExtractor, if | ||||
|             # this error is thrown, it'll be thrown here | ||||
|             raise ExtractorError(u'Invalid URL: %s' % url) | ||||
|             raise ExtractorError(u'Failed to download URL: %s' % url) | ||||
|  | ||||
|         self.report_extraction(video_id) | ||||
|         # Look for BrigthCove: | ||||
|         m_brightcove = re.search(r'<object.+?class=([\'"]).*?BrightcoveExperience.*?\1.+?</object>', webpage, re.DOTALL) | ||||
|         if m_brightcove is not None: | ||||
|         # Look for BrightCove: | ||||
|         bc_url = BrightcoveIE._extract_brightcove_url(webpage) | ||||
|         if bc_url is not None: | ||||
|             self.to_screen(u'Brightcove video detected.') | ||||
|             bc_url = BrightcoveIE._build_brighcove_url(m_brightcove.group()) | ||||
|             return self.url_result(bc_url, 'Brightcove') | ||||
|  | ||||
|         # Look for embedded Vimeo player | ||||
|         mobj = re.search( | ||||
|             r'<iframe[^>]+?src="(https?://player.vimeo.com/video/.+?)"', webpage) | ||||
|         if mobj: | ||||
|             player_url = unescapeHTML(mobj.group(1)) | ||||
|             surl = smuggle_url(player_url, {'Referer': url}) | ||||
|             return self.url_result(surl, 'Vimeo') | ||||
|  | ||||
|         # Look for embedded YouTube player | ||||
|         mobj = re.search( | ||||
|             r'<iframe[^>]+?src=(["\'])(?P<url>https?://(?:www\.)?youtube.com/embed/.+?)\1', webpage) | ||||
|         if mobj: | ||||
|             surl = unescapeHTML(mobj.group(u'url')) | ||||
|             return self.url_result(surl, 'Youtube') | ||||
|  | ||||
|         # Look for Bandcamp pages with custom domain | ||||
|         mobj = re.search(r'<meta property="og:url"[^>]*?content="(.*?bandcamp\.com.*?)"', webpage) | ||||
|         if mobj is not None: | ||||
|             burl = unescapeHTML(mobj.group(1)) | ||||
|             return self.url_result(burl, 'Bandcamp') | ||||
|  | ||||
|         # Start with something easy: JW Player in SWFObject | ||||
|         mobj = re.search(r'flashvars: [\'"](?:.*&)?file=(http[^\'"&]*)', webpage) | ||||
|         if mobj is None: | ||||
| @@ -145,15 +208,19 @@ class GenericIE(InfoExtractor): | ||||
|             if m_video_type is not None: | ||||
|                 mobj = re.search(r'<meta.*?property="og:video".*?content="(.*?)"', webpage) | ||||
|         if mobj is None: | ||||
|             raise ExtractorError(u'Invalid URL: %s' % url) | ||||
|             # HTML5 video | ||||
|             mobj = re.search(r'<video[^<]*(?:>.*?<source.*?)? src="([^"]+)"', webpage, flags=re.DOTALL) | ||||
|         if mobj is None: | ||||
|             raise ExtractorError(u'Unsupported URL: %s' % url) | ||||
|  | ||||
|         # It's possible that one of the regexes | ||||
|         # matched, but returned an empty group: | ||||
|         if mobj.group(1) is None: | ||||
|             raise ExtractorError(u'Invalid URL: %s' % url) | ||||
|             raise ExtractorError(u'Did not find a valid video URL at %s' % url) | ||||
|  | ||||
|         video_url = compat_urllib_parse.unquote(mobj.group(1)) | ||||
|         video_id = os.path.basename(video_url) | ||||
|         video_url = mobj.group(1) | ||||
|         video_url = compat_urlparse.urljoin(url, video_url) | ||||
|         video_id = compat_urllib_parse.unquote(os.path.basename(video_url)) | ||||
|  | ||||
|         # here's a fun little line of code for you: | ||||
|         video_extension = os.path.splitext(video_id)[1][1:] | ||||
|   | ||||
| @@ -40,8 +40,10 @@ class GooglePlusIE(InfoExtractor): | ||||
|         self.report_extraction(video_id) | ||||
|  | ||||
|         # Extract update date | ||||
|         upload_date = self._html_search_regex('title="Timestamp">(.*?)</a>', | ||||
|             webpage, u'upload date', fatal=False) | ||||
|         upload_date = self._html_search_regex( | ||||
|             r'''(?x)<a.+?class="o-U-s\s[^"]+"\s+style="display:\s*none"\s*> | ||||
|                     ([0-9]{4}-[0-9]{2}-[0-9]{2})</a>''', | ||||
|             webpage, u'upload date', fatal=False, flags=re.VERBOSE) | ||||
|         if upload_date: | ||||
|             # Convert timestring to a format suitable for filename | ||||
|             upload_date = datetime.datetime.strptime(upload_date, "%Y-%m-%d") | ||||
| @@ -57,8 +59,8 @@ class GooglePlusIE(InfoExtractor): | ||||
|             webpage, 'title', default=u'NA') | ||||
|  | ||||
|         # Step 2, Simulate clicking the image box to launch video | ||||
|         DOMAIN = 'https://plus.google.com' | ||||
|         video_page = self._search_regex(r'<a href="((?:%s)?/photos/.*?)"' % re.escape(DOMAIN), | ||||
|         DOMAIN = 'https://plus.google.com/' | ||||
|         video_page = self._search_regex(r'<a href="((?:%s)?photos/.*?)"' % re.escape(DOMAIN), | ||||
|             webpage, u'video page URL') | ||||
|         if not video_page.startswith(DOMAIN): | ||||
|             video_page = DOMAIN + video_page | ||||
|   | ||||
							
								
								
									
										37
									
								
								youtube_dl/extractor/hark.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								youtube_dl/extractor/hark.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| import re | ||||
| import json | ||||
|  | ||||
| from .common import InfoExtractor | ||||
| from ..utils import determine_ext | ||||
|  | ||||
| class HarkIE(InfoExtractor): | ||||
|     _VALID_URL = r'https?://www\.hark\.com/clips/(.+?)-.+' | ||||
|     _TEST = { | ||||
|         u'url': u'http://www.hark.com/clips/mmbzyhkgny-obama-beyond-the-afghan-theater-we-only-target-al-qaeda-on-may-23-2013', | ||||
|         u'file': u'mmbzyhkgny.mp3', | ||||
|         u'md5': u'6783a58491b47b92c7c1af5a77d4cbee', | ||||
|         u'info_dict': { | ||||
|             u'title': u"Obama: 'Beyond The Afghan Theater, We Only Target Al Qaeda' on May 23, 2013", | ||||
|             u'description': u'President Barack Obama addressed the nation live on May 23, 2013 in a speech aimed at addressing counter-terrorism policies including the use of drone strikes, detainees at Guantanamo Bay prison facility, and American citizens who are terrorists.', | ||||
|             u'duration': 11, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         video_id = mobj.group(1) | ||||
|         json_url = "http://www.hark.com/clips/%s.json" %(video_id) | ||||
|         info_json = self._download_webpage(json_url, video_id) | ||||
|         info = json.loads(info_json) | ||||
|         final_url = info['url'] | ||||
|  | ||||
|         return {'id': video_id, | ||||
|                 'url' : final_url, | ||||
|                 'title': info['name'], | ||||
|                 'ext': determine_ext(final_url), | ||||
|                 'description': info['description'], | ||||
|                 'thumbnail': info['image_original'], | ||||
|                 'duration': info['duration'], | ||||
|                 } | ||||
| @@ -7,11 +7,11 @@ from .common import InfoExtractor | ||||
| class HotNewHipHopIE(InfoExtractor): | ||||
|     _VALID_URL = r'http://www\.hotnewhiphop.com/.*\.(?P<id>.*)\.html' | ||||
|     _TEST = { | ||||
|         u'url': u"http://www.hotnewhiphop.com/freddie-gibbs-lay-it-down-song.1435540.html'", | ||||
|         u'url': u"http://www.hotnewhiphop.com/freddie-gibbs-lay-it-down-song.1435540.html", | ||||
|         u'file': u'1435540.mp3', | ||||
|         u'md5': u'2c2cd2f76ef11a9b3b581e8b232f3d96', | ||||
|         u'info_dict': { | ||||
|             u"title": u"Freddie Gibbs Songs - Lay It Down" | ||||
|             u"title": u"Freddie Gibbs - Lay It Down" | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -19,8 +19,7 @@ class HowcastIE(InfoExtractor): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|  | ||||
|         video_id = mobj.group('id') | ||||
|         webpage_url = 'http://www.howcast.com/videos/' + video_id | ||||
|         webpage = self._download_webpage(webpage_url, video_id) | ||||
|         webpage = self._download_webpage(url, video_id) | ||||
|  | ||||
|         self.report_extraction(video_id) | ||||
|  | ||||
|   | ||||
| @@ -30,7 +30,7 @@ class HypemIE(InfoExtractor): | ||||
|             raise ExtractorError(u'Invalid URL: %s' % url) | ||||
|         track_id = mobj.group(1) | ||||
|  | ||||
|         data = { 'ax': 1, 'ts': time.time() } | ||||
|         data = {'ax': 1, 'ts': time.time()} | ||||
|         data_encoded = compat_urllib_parse.urlencode(data) | ||||
|         complete_url = url + "?" + data_encoded | ||||
|         request = compat_urllib_request.Request(complete_url) | ||||
| @@ -68,4 +68,4 @@ class HypemIE(InfoExtractor): | ||||
|             'ext':      "mp3", | ||||
|             'title':    title, | ||||
|             'artist':   artist, | ||||
|         }] | ||||
|         }] | ||||
|   | ||||
| @@ -13,7 +13,7 @@ class IGNIE(InfoExtractor): | ||||
|     Some videos of it.ign.com are also supported | ||||
|     """ | ||||
|  | ||||
|     _VALID_URL = r'https?://.+?\.ign\.com/(?:videos|show_videos)(/.+)?/(?P<name_or_id>.+)' | ||||
|     _VALID_URL = r'https?://.+?\.ign\.com/(?P<type>videos|show_videos|articles|(?:[^/]*/feature))(/.+)?/(?P<name_or_id>.+)' | ||||
|     IE_NAME = u'ign.com' | ||||
|  | ||||
|     _CONFIG_URL_TEMPLATE = 'http://www.ign.com/videos/configs/id/%s.config' | ||||
| @@ -21,15 +21,39 @@ class IGNIE(InfoExtractor): | ||||
|                        r'id="my_show_video">.*?<p>(.*?)</p>', | ||||
|                        ] | ||||
|  | ||||
|     _TEST = { | ||||
|         u'url': u'http://www.ign.com/videos/2013/06/05/the-last-of-us-review', | ||||
|         u'file': u'8f862beef863986b2785559b9e1aa599.mp4', | ||||
|         u'md5': u'eac8bdc1890980122c3b66f14bdd02e9', | ||||
|         u'info_dict': { | ||||
|             u'title': u'The Last of Us Review', | ||||
|             u'description': u'md5:c8946d4260a4d43a00d5ae8ed998870c', | ||||
|         } | ||||
|     } | ||||
|     _TESTS = [ | ||||
|         { | ||||
|             u'url': u'http://www.ign.com/videos/2013/06/05/the-last-of-us-review', | ||||
|             u'file': u'8f862beef863986b2785559b9e1aa599.mp4', | ||||
|             u'md5': u'eac8bdc1890980122c3b66f14bdd02e9', | ||||
|             u'info_dict': { | ||||
|                 u'title': u'The Last of Us Review', | ||||
|                 u'description': u'md5:c8946d4260a4d43a00d5ae8ed998870c', | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             u'url': u'http://me.ign.com/en/feature/15775/100-little-things-in-gta-5-that-will-blow-your-mind', | ||||
|             u'playlist': [ | ||||
|                 { | ||||
|                     u'file': u'5ebbd138523268b93c9141af17bec937.mp4', | ||||
|                     u'info_dict': { | ||||
|                         u'title': u'GTA 5 Video Review', | ||||
|                         u'description': u'Rockstar drops the mic on this generation of games. Watch our review of the masterly Grand Theft Auto V.', | ||||
|                     }, | ||||
|                 }, | ||||
|                 { | ||||
|                     u'file': u'638672ee848ae4ff108df2a296418ee2.mp4', | ||||
|                     u'info_dict': { | ||||
|                         u'title': u'GTA 5\'s Twisted Beauty in Super Slow Motion', | ||||
|                         u'description': u'The twisted beauty of GTA 5 in stunning slow motion.', | ||||
|                     }, | ||||
|                 }, | ||||
|             ], | ||||
|             u'params': { | ||||
|                 u'skip_download': True, | ||||
|             }, | ||||
|         }, | ||||
|     ] | ||||
|  | ||||
|     def _find_video_id(self, webpage): | ||||
|         res_id = [r'data-video-id="(.+?)"', | ||||
| @@ -41,7 +65,18 @@ class IGNIE(InfoExtractor): | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         name_or_id = mobj.group('name_or_id') | ||||
|         page_type = mobj.group('type') | ||||
|         webpage = self._download_webpage(url, name_or_id) | ||||
|         if page_type == 'articles': | ||||
|             video_url = self._search_regex(r'var videoUrl = "(.+?)"', webpage, u'video url') | ||||
|             return self.url_result(video_url, ie='IGN') | ||||
|         elif page_type != 'video': | ||||
|             multiple_urls = re.findall( | ||||
|                 '<param name="flashvars" value="[^"]*?url=(https?://www\.ign\.com/videos/.*?)["&]', | ||||
|                 webpage) | ||||
|             if multiple_urls: | ||||
|                 return [self.url_result(u, ie='IGN') for u in multiple_urls] | ||||
|  | ||||
|         video_id = self._find_video_id(webpage) | ||||
|         result = self._get_video_info(video_id) | ||||
|         description = self._html_search_regex(self._DESCRIPTION_RE, | ||||
| @@ -68,7 +103,7 @@ class IGNIE(InfoExtractor): | ||||
| class OneUPIE(IGNIE): | ||||
|     """Extractor for 1up.com, it uses the ign videos system.""" | ||||
|  | ||||
|     _VALID_URL = r'https?://gamevideos.1up.com/video/id/(?P<name_or_id>.+)' | ||||
|     _VALID_URL = r'https?://gamevideos.1up.com/(?P<type>video)/id/(?P<name_or_id>.+)' | ||||
|     IE_NAME = '1up.com' | ||||
|  | ||||
|     _DESCRIPTION_RE = r'<div id="vid_summary">(.+?)</div>' | ||||
| @@ -83,6 +118,9 @@ class OneUPIE(IGNIE): | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     # Override IGN tests | ||||
|     _TESTS = [] | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         id = mobj.group('name_or_id') | ||||
|   | ||||
| @@ -5,7 +5,7 @@ from .common import InfoExtractor | ||||
|  | ||||
| class InaIE(InfoExtractor): | ||||
|     """Information Extractor for Ina.fr""" | ||||
|     _VALID_URL = r'(?:http://)?(?:www\.)?ina\.fr/video/(?P<id>I[0-9]+)/.*' | ||||
|     _VALID_URL = r'(?:http://)?(?:www\.)?ina\.fr/video/(?P<id>I?[A-F0-9]+)/.*' | ||||
|     _TEST = { | ||||
|         u'url': u'www.ina.fr/video/I12055569/francois-hollande-je-crois-que-c-est-clair-video.html', | ||||
|         u'file': u'I12055569.mp4', | ||||
|   | ||||
| @@ -26,7 +26,7 @@ class InstagramIE(InfoExtractor): | ||||
|  | ||||
|         return [{ | ||||
|             'id':        video_id, | ||||
|             'url':       self._og_search_video_url(webpage), | ||||
|             'url':       self._og_search_video_url(webpage, secure=False), | ||||
|             'ext':       'mp4', | ||||
|             'title':     u'Video by %s' % uploader_id, | ||||
|             'thumbnail': self._og_search_thumbnail(webpage), | ||||
|   | ||||
							
								
								
									
										84
									
								
								youtube_dl/extractor/internetvideoarchive.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								youtube_dl/extractor/internetvideoarchive.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | ||||
| import re | ||||
| import xml.etree.ElementTree | ||||
|  | ||||
| from .common import InfoExtractor | ||||
| from ..utils import ( | ||||
|     compat_urlparse, | ||||
|     compat_urllib_parse, | ||||
|     xpath_with_ns, | ||||
|     determine_ext, | ||||
| ) | ||||
|  | ||||
|  | ||||
| class InternetVideoArchiveIE(InfoExtractor): | ||||
|     _VALID_URL = r'https?://video\.internetvideoarchive\.net/flash/players/.*?\?.*?publishedid.*?' | ||||
|  | ||||
|     _TEST = { | ||||
|         u'url': u'http://video.internetvideoarchive.net/flash/players/flashconfiguration.aspx?customerid=69249&publishedid=452693&playerid=247', | ||||
|         u'file': u'452693.mp4', | ||||
|         u'info_dict': { | ||||
|             u'title': u'SKYFALL', | ||||
|             u'description': u'In SKYFALL, Bond\'s loyalty to M is tested as her past comes back to haunt her. As MI6 comes under attack, 007 must track down and destroy the threat, no matter how personal the cost.', | ||||
|             u'duration': 153, | ||||
|         }, | ||||
|     } | ||||
|  | ||||
|     @staticmethod | ||||
|     def _build_url(query): | ||||
|         return 'http://video.internetvideoarchive.net/flash/players/flashconfiguration.aspx?' + query | ||||
|  | ||||
|     @staticmethod | ||||
|     def _clean_query(query): | ||||
|         NEEDED_ARGS = ['publishedid', 'customerid'] | ||||
|         query_dic = compat_urlparse.parse_qs(query) | ||||
|         cleaned_dic = dict((k,v[0]) for (k,v) in query_dic.items() if k in NEEDED_ARGS) | ||||
|         # Other player ids return m3u8 urls | ||||
|         cleaned_dic['playerid'] = '247' | ||||
|         cleaned_dic['videokbrate'] = '100000' | ||||
|         return compat_urllib_parse.urlencode(cleaned_dic) | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         query = compat_urlparse.urlparse(url).query | ||||
|         query_dic = compat_urlparse.parse_qs(query) | ||||
|         video_id = query_dic['publishedid'][0] | ||||
|         url = self._build_url(query) | ||||
|  | ||||
|         flashconfiguration_xml = self._download_webpage(url, video_id, | ||||
|             u'Downloading flash configuration') | ||||
|         flashconfiguration = xml.etree.ElementTree.fromstring(flashconfiguration_xml.encode('utf-8')) | ||||
|         file_url = flashconfiguration.find('file').text | ||||
|         file_url = file_url.replace('/playlist.aspx', '/mrssplaylist.aspx') | ||||
|         # Replace some of the parameters in the query to get the best quality | ||||
|         # and http links (no m3u8 manifests) | ||||
|         file_url = re.sub(r'(?<=\?)(.+)$', | ||||
|             lambda m: self._clean_query(m.group()), | ||||
|             file_url) | ||||
|         info_xml = self._download_webpage(file_url, video_id, | ||||
|             u'Downloading video info') | ||||
|         info = xml.etree.ElementTree.fromstring(info_xml.encode('utf-8')) | ||||
|         item = info.find('channel/item') | ||||
|  | ||||
|         def _bp(p): | ||||
|             return xpath_with_ns(p, | ||||
|                 {'media': 'http://search.yahoo.com/mrss/', | ||||
|                 'jwplayer': 'http://developer.longtailvideo.com/trac/wiki/FlashFormats'}) | ||||
|         formats = [] | ||||
|         for content in item.findall(_bp('media:group/media:content')): | ||||
|             attr = content.attrib | ||||
|             f_url = attr['url'] | ||||
|             formats.append({ | ||||
|                 'url': f_url, | ||||
|                 'ext': determine_ext(f_url), | ||||
|                 'width': int(attr['width']), | ||||
|                 'bitrate': int(attr['bitrate']), | ||||
|             }) | ||||
|         formats = sorted(formats, key=lambda f: f['bitrate']) | ||||
|  | ||||
|         return { | ||||
|             'id': video_id, | ||||
|             'title': item.find('title').text, | ||||
|             'formats': formats, | ||||
|             'thumbnail': item.find(_bp('media:thumbnail')).attrib['url'], | ||||
|             'description': item.find('description').text, | ||||
|             'duration': int(attr['duration']), | ||||
|         } | ||||
							
								
								
									
										52
									
								
								youtube_dl/extractor/jeuxvideo.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								youtube_dl/extractor/jeuxvideo.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | ||||
| # coding: utf-8 | ||||
|  | ||||
| import json | ||||
| import re | ||||
| import xml.etree.ElementTree | ||||
|  | ||||
| from .common import InfoExtractor | ||||
|  | ||||
|  | ||||
| class JeuxVideoIE(InfoExtractor): | ||||
|     _VALID_URL = r'http://.*?\.jeuxvideo\.com/.*/(.*?)-\d+\.htm' | ||||
|  | ||||
|     _TEST = { | ||||
|         u'url': u'http://www.jeuxvideo.com/reportages-videos-jeux/0004/00046170/tearaway-playstation-vita-gc-2013-tearaway-nous-presente-ses-papiers-d-identite-00115182.htm', | ||||
|         u'file': u'5182.mp4', | ||||
|         u'md5': u'046e491afb32a8aaac1f44dd4ddd54ee', | ||||
|         u'info_dict': { | ||||
|             u'title': u'GC 2013 : Tearaway nous présente ses papiers d\'identité', | ||||
|             u'description': u'Lorsque les développeurs de LittleBigPlanet proposent un nouveau titre, on ne peut que s\'attendre à un résultat original et fort attrayant.\n', | ||||
|         }, | ||||
|     } | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         title = re.match(self._VALID_URL, url).group(1) | ||||
|         webpage = self._download_webpage(url, title) | ||||
|         xml_link = self._html_search_regex( | ||||
|             r'<param name="flashvars" value="config=(.*?)" />', | ||||
|             webpage, u'config URL') | ||||
|          | ||||
|         video_id = self._search_regex( | ||||
|             r'http://www\.jeuxvideo\.com/config/\w+/\d+/(.*?)/\d+_player\.xml', | ||||
|             xml_link, u'video ID') | ||||
|  | ||||
|         xml_config = self._download_webpage( | ||||
|             xml_link, title, u'Downloading XML config') | ||||
|         config = xml.etree.ElementTree.fromstring(xml_config.encode('utf-8')) | ||||
|         info_json = self._search_regex( | ||||
|             r'(?sm)<format\.json>(.*?)</format\.json>', | ||||
|             xml_config, u'JSON information') | ||||
|         info = json.loads(info_json)['versions'][0] | ||||
|          | ||||
|         video_url = 'http://video720.jeuxvideo.com/' + info['file'] | ||||
|  | ||||
|         return { | ||||
|             'id': video_id, | ||||
|             'title': config.find('titre_video').text, | ||||
|             'ext': 'mp4', | ||||
|             'url': video_url, | ||||
|             'description': self._og_search_description(webpage), | ||||
|             'thumbnail': config.find('image').text, | ||||
|         } | ||||
							
								
								
									
										44
									
								
								youtube_dl/extractor/kankan.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								youtube_dl/extractor/kankan.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| import re | ||||
| import hashlib | ||||
|  | ||||
| from .common import InfoExtractor | ||||
| from ..utils import determine_ext | ||||
|  | ||||
| _md5 = lambda s: hashlib.md5(s.encode('utf-8')).hexdigest() | ||||
|  | ||||
| class KankanIE(InfoExtractor): | ||||
|     _VALID_URL = r'https?://(?:.*?\.)?kankan\.com/.+?/(?P<id>\d+)\.shtml' | ||||
|      | ||||
|     _TEST = { | ||||
|         u'url': u'http://yinyue.kankan.com/vod/48/48863.shtml', | ||||
|         u'file': u'48863.flv', | ||||
|         u'md5': u'29aca1e47ae68fc28804aca89f29507e', | ||||
|         u'info_dict': { | ||||
|             u'title': u'Ready To Go', | ||||
|         }, | ||||
|     } | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         video_id = mobj.group('id') | ||||
|         webpage = self._download_webpage(url, video_id) | ||||
|  | ||||
|         title = self._search_regex(r'(?:G_TITLE=|G_MOVIE_TITLE = )[\'"](.+?)[\'"]', webpage, u'video title') | ||||
|         surls = re.search(r'surls:\[\'.+?\'\]|lurl:\'.+?\.flv\'', webpage).group(0) | ||||
|         gcids = re.findall(r"http://.+?/.+?/(.+?)/", surls) | ||||
|         gcid = gcids[-1] | ||||
|  | ||||
|         video_info_page = self._download_webpage('http://p2s.cl.kankan.com/getCdnresource_flv?gcid=%s' % gcid, | ||||
|                                                  video_id, u'Downloading video url info') | ||||
|         ip = self._search_regex(r'ip:"(.+?)"', video_info_page, u'video url ip') | ||||
|         path = self._search_regex(r'path:"(.+?)"', video_info_page, u'video url path') | ||||
|         param1 = self._search_regex(r'param1:(\d+)', video_info_page, u'param1') | ||||
|         param2 = self._search_regex(r'param2:(\d+)', video_info_page, u'param2') | ||||
|         key = _md5('xl_mp43651' + param1 + param2) | ||||
|         video_url = 'http://%s%s?key=%s&key1=%s' % (ip, path, key, param2) | ||||
|  | ||||
|         return {'id': video_id, | ||||
|                 'title': title, | ||||
|                 'url': video_url, | ||||
|                 'ext': determine_ext(video_url), | ||||
|                 } | ||||
| @@ -4,10 +4,10 @@ from .common import InfoExtractor | ||||
|  | ||||
|  | ||||
| class KeekIE(InfoExtractor): | ||||
|     _VALID_URL = r'http://(?:www\.)?keek\.com/(?:!|\w+/keeks/)(?P<videoID>\w+)' | ||||
|     _VALID_URL = r'https?://(?:www\.)?keek\.com/(?:!|\w+/keeks/)(?P<videoID>\w+)' | ||||
|     IE_NAME = u'keek' | ||||
|     _TEST = { | ||||
|         u'url': u'http://www.keek.com/ytdl/keeks/NODfbab', | ||||
|         u'url': u'https://www.keek.com/ytdl/keeks/NODfbab', | ||||
|         u'file': u'NODfbab.mp4', | ||||
|         u'md5': u'9b0636f8c0f7614afa4ea5e4c6e57e83', | ||||
|         u'info_dict': { | ||||
|   | ||||
							
								
								
									
										61
									
								
								youtube_dl/extractor/keezmovies.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								youtube_dl/extractor/keezmovies.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | ||||
| import os | ||||
| import re | ||||
|  | ||||
| from .common import InfoExtractor | ||||
| from ..utils import ( | ||||
|     compat_urllib_parse_urlparse, | ||||
|     compat_urllib_request, | ||||
|     compat_urllib_parse, | ||||
| ) | ||||
| from ..aes import ( | ||||
|     aes_decrypt_text | ||||
| ) | ||||
|  | ||||
| class KeezMoviesIE(InfoExtractor): | ||||
|     _VALID_URL = r'^(?:https?://)?(?:www\.)?(?P<url>keezmovies\.com/video/.+?(?P<videoid>[0-9]+))(?:[/?&]|$)' | ||||
|     _TEST = { | ||||
|         u'url': u'http://www.keezmovies.com/video/petite-asian-lady-mai-playing-in-bathtub-1214711', | ||||
|         u'file': u'1214711.mp4', | ||||
|         u'md5': u'6e297b7e789329923fcf83abb67c9289', | ||||
|         u'info_dict': { | ||||
|             u"title": u"Petite Asian Lady Mai Playing In Bathtub", | ||||
|             u"age_limit": 18, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         video_id = mobj.group('videoid') | ||||
|         url = 'http://www.' + mobj.group('url') | ||||
|  | ||||
|         req = compat_urllib_request.Request(url) | ||||
|         req.add_header('Cookie', 'age_verified=1') | ||||
|         webpage = self._download_webpage(req, video_id) | ||||
|  | ||||
|         # embedded video | ||||
|         mobj = re.search(r'href="([^"]+)"></iframe>', webpage) | ||||
|         if mobj: | ||||
|             embedded_url = mobj.group(1) | ||||
|             return self.url_result(embedded_url) | ||||
|  | ||||
|         video_title = self._html_search_regex(r'<h1 [^>]*>([^<]+)', webpage, u'title') | ||||
|         video_url = compat_urllib_parse.unquote(self._html_search_regex(r'video_url=(.+?)&', webpage, u'video_url')) | ||||
|         if webpage.find('encrypted=true')!=-1: | ||||
|             password = self._html_search_regex(r'video_title=(.+?)&', webpage, u'password') | ||||
|             video_url = aes_decrypt_text(video_url, password, 32).decode('utf-8') | ||||
|         path = compat_urllib_parse_urlparse(video_url).path | ||||
|         extension = os.path.splitext(path)[1][1:] | ||||
|         format = path.split('/')[4].split('_')[:2] | ||||
|         format = "-".join(format) | ||||
|  | ||||
|         age_limit = self._rta_search(webpage) | ||||
|  | ||||
|         return { | ||||
|             'id': video_id, | ||||
|             'title': video_title, | ||||
|             'url': video_url, | ||||
|             'ext': extension, | ||||
|             'format': format, | ||||
|             'format_id': format, | ||||
|             'age_limit': age_limit, | ||||
|         } | ||||
							
								
								
									
										37
									
								
								youtube_dl/extractor/kickstarter.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								youtube_dl/extractor/kickstarter.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| import re | ||||
|  | ||||
| from .common import InfoExtractor | ||||
|  | ||||
|  | ||||
| class KickStarterIE(InfoExtractor): | ||||
|     _VALID_URL = r'https?://www\.kickstarter\.com/projects/(?P<id>\d*)/.*' | ||||
|     _TEST = { | ||||
|         u"url": u"https://www.kickstarter.com/projects/1404461844/intersection-the-story-of-josh-grant?ref=home_location", | ||||
|         u"file": u"1404461844.mp4", | ||||
|         u"md5": u"c81addca81327ffa66c642b5d8b08cab", | ||||
|         u"info_dict": { | ||||
|             u"title": u"Intersection: The Story of Josh Grant by Kyle Cowling", | ||||
|         }, | ||||
|     } | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         m = re.match(self._VALID_URL, url) | ||||
|         video_id = m.group('id') | ||||
|         webpage_src = self._download_webpage(url, video_id) | ||||
|  | ||||
|         video_url = self._search_regex(r'data-video="(.*?)">', | ||||
|             webpage_src, u'video URL') | ||||
|         if 'mp4' in video_url: | ||||
|             ext = 'mp4' | ||||
|         else: | ||||
|             ext = 'flv' | ||||
|         video_title = self._html_search_regex(r"<title>(.*?)</title>", | ||||
|             webpage_src, u'title').rpartition(u'\u2014 Kickstarter')[0].strip() | ||||
|  | ||||
|         results = [{ | ||||
|                     'id': video_id, | ||||
|                     'url': video_url, | ||||
|                     'title': video_title, | ||||
|                     'ext': ext, | ||||
|                     }] | ||||
|         return results | ||||
| @@ -1,11 +1,19 @@ | ||||
| import re | ||||
| import json | ||||
| import xml.etree.ElementTree | ||||
|  | ||||
| from .common import InfoExtractor | ||||
| from ..utils import compat_urllib_parse_urlparse, compat_urlparse | ||||
| from ..utils import ( | ||||
|     compat_urllib_parse_urlparse, | ||||
|     compat_urlparse, | ||||
|     get_meta_content, | ||||
|     xpath_with_ns, | ||||
|     ExtractorError, | ||||
| ) | ||||
|  | ||||
|  | ||||
| class LivestreamIE(InfoExtractor): | ||||
|     IE_NAME = u'livestream' | ||||
|     _VALID_URL = r'http://new.livestream.com/.*?/(?P<event_name>.*?)(/videos/(?P<id>\d+))?/?$' | ||||
|     _TEST = { | ||||
|         u'url': u'http://new.livestream.com/CoheedandCambria/WebsterHall/videos/4719370', | ||||
| @@ -35,10 +43,9 @@ class LivestreamIE(InfoExtractor): | ||||
|  | ||||
|         if video_id is None: | ||||
|             # This is an event page: | ||||
|             api_url = self._search_regex(r'event_design_eventId: \'(.+?)\'', | ||||
|                                          webpage, 'api url') | ||||
|             info = json.loads(self._download_webpage(api_url, event_name, | ||||
|                                                      u'Downloading event info')) | ||||
|             config_json = self._search_regex(r'window.config = ({.*?});', | ||||
|                 webpage, u'window config') | ||||
|             info = json.loads(config_json)['event'] | ||||
|             videos = [self._extract_video_info(video_data['data']) | ||||
|                 for video_data in info['feed']['data'] if video_data['type'] == u'video'] | ||||
|             return self.playlist_result(videos, info['id'], info['full_name']) | ||||
| @@ -50,3 +57,44 @@ class LivestreamIE(InfoExtractor): | ||||
|             info = json.loads(self._download_webpage(api_url, video_id, | ||||
|                                                      u'Downloading video info')) | ||||
|             return self._extract_video_info(info) | ||||
|  | ||||
|  | ||||
| # The original version of Livestream uses a different system | ||||
| class LivestreamOriginalIE(InfoExtractor): | ||||
|     IE_NAME = u'livestream:original' | ||||
|     _VALID_URL = r'https?://www\.livestream\.com/(?P<user>[^/]+)/video\?.*?clipId=(?P<id>.*?)(&|$)' | ||||
|     _TEST = { | ||||
|         u'url': u'http://www.livestream.com/dealbook/video?clipId=pla_8aa4a3f1-ba15-46a4-893b-902210e138fb', | ||||
|         u'info_dict': { | ||||
|             u'id': u'pla_8aa4a3f1-ba15-46a4-893b-902210e138fb', | ||||
|             u'ext': u'flv', | ||||
|             u'title': u'Spark 1 (BitCoin) with Cameron Winklevoss & Tyler Winklevoss of Winklevoss Capital', | ||||
|         }, | ||||
|         u'params': { | ||||
|             # rtmp | ||||
|             u'skip_download': True, | ||||
|         }, | ||||
|     } | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         video_id = mobj.group('id') | ||||
|         user = mobj.group('user') | ||||
|         api_url = 'http://x{0}x.api.channel.livestream.com/2.0/clipdetails?extendedInfo=true&id={1}'.format(user, video_id) | ||||
|  | ||||
|         api_response = self._download_webpage(api_url, video_id) | ||||
|         info = xml.etree.ElementTree.fromstring(api_response.encode('utf-8')) | ||||
|         item = info.find('channel').find('item') | ||||
|         ns = {'media': 'http://search.yahoo.com/mrss'} | ||||
|         thumbnail_url = item.find(xpath_with_ns('media:thumbnail', ns)).attrib['url'] | ||||
|         # Remove the extension and number from the path (like 1.jpg) | ||||
|         path = self._search_regex(r'(user-files/.+)_.*?\.jpg$', thumbnail_url, u'path') | ||||
|  | ||||
|         return { | ||||
|             'id': video_id, | ||||
|             'title': item.find('title').text, | ||||
|             'url': 'rtmp://extondemand.livestream.com/ondemand', | ||||
|             'play_path': 'mp4:trans/dv15/mogulus-{0}.mp4'.format(path), | ||||
|             'ext': 'flv', | ||||
|             'thumbnail': thumbnail_url, | ||||
|         } | ||||
|   | ||||
| @@ -20,10 +20,12 @@ class MetacafeIE(InfoExtractor): | ||||
|     _DISCLAIMER = 'http://www.metacafe.com/family_filter/' | ||||
|     _FILTER_POST = 'http://www.metacafe.com/f/index.php?inputType=filter&controllerGroup=user' | ||||
|     IE_NAME = u'metacafe' | ||||
|     _TESTS = [{ | ||||
|     _TESTS = [ | ||||
|     # Youtube video | ||||
|     { | ||||
|         u"add_ie": ["Youtube"], | ||||
|         u"url":  u"http://metacafe.com/watch/yt-_aUehQsCQtM/the_electric_company_short_i_pbs_kids_go/", | ||||
|         u"file":  u"_aUehQsCQtM.flv", | ||||
|         u"file":  u"_aUehQsCQtM.mp4", | ||||
|         u"info_dict": { | ||||
|             u"upload_date": u"20090102", | ||||
|             u"title": u"The Electric Company | \"Short I\" | PBS KIDS GO!", | ||||
| @@ -32,15 +34,42 @@ class MetacafeIE(InfoExtractor): | ||||
|             u"uploader_id": u"PBS" | ||||
|         } | ||||
|     }, | ||||
|     # Normal metacafe video | ||||
|     { | ||||
|         u'url': u'http://www.metacafe.com/watch/11121940/news_stuff_you_wont_do_with_your_playstation_4/', | ||||
|         u'md5': u'6e0bca200eaad2552e6915ed6fd4d9ad', | ||||
|         u'info_dict': { | ||||
|             u'id': u'11121940', | ||||
|             u'ext': u'mp4', | ||||
|             u'title': u'News: Stuff You Won\'t Do with Your PlayStation 4', | ||||
|             u'uploader': u'ign', | ||||
|             u'description': u'Sony released a massive FAQ on the PlayStation Blog detailing the PS4\'s capabilities and limitations.', | ||||
|         }, | ||||
|     }, | ||||
|     # AnyClip video | ||||
|     { | ||||
|         u"url": u"http://www.metacafe.com/watch/an-dVVXnuY7Jh77J/the_andromeda_strain_1971_stop_the_bomb_part_3/", | ||||
|         u"file": u"an-dVVXnuY7Jh77J.mp4", | ||||
|         u"info_dict": { | ||||
|             u"title": u"The Andromeda Strain (1971): Stop the Bomb Part 3", | ||||
|             u"uploader": u"anyclip", | ||||
|             u"description": u"md5:38c711dd98f5bb87acf973d573442e67" | ||||
|         } | ||||
|     }] | ||||
|             u"description": u"md5:38c711dd98f5bb87acf973d573442e67", | ||||
|         }, | ||||
|     }, | ||||
|     # age-restricted video | ||||
|     { | ||||
|         u'url': u'http://www.metacafe.com/watch/5186653/bbc_internal_christmas_tape_79_uncensored_outtakes_etc/', | ||||
|         u'md5': u'98dde7c1a35d02178e8ab7560fe8bd09', | ||||
|         u'info_dict': { | ||||
|             u'id': u'5186653', | ||||
|             u'ext': u'mp4', | ||||
|             u'title': u'BBC INTERNAL Christmas Tape \'79 - UNCENSORED Outtakes, Etc.', | ||||
|             u'uploader': u'Dwayne Pipe', | ||||
|             u'description': u'md5:950bf4c581e2c059911fa3ffbe377e4b', | ||||
|             u'age_limit': 18, | ||||
|         }, | ||||
|     }, | ||||
|     ] | ||||
|  | ||||
|  | ||||
|     def report_disclaimer(self): | ||||
| @@ -62,6 +91,7 @@ class MetacafeIE(InfoExtractor): | ||||
|             'submit': "Continue - I'm over 18", | ||||
|             } | ||||
|         request = compat_urllib_request.Request(self._FILTER_POST, compat_urllib_parse.urlencode(disclaimer_form)) | ||||
|         request.add_header('Content-Type', 'application/x-www-form-urlencoded') | ||||
|         try: | ||||
|             self.report_age_confirmation() | ||||
|             compat_urllib_request.urlopen(request).read() | ||||
| @@ -83,7 +113,12 @@ class MetacafeIE(InfoExtractor): | ||||
|  | ||||
|         # Retrieve video webpage to extract further information | ||||
|         req = compat_urllib_request.Request('http://www.metacafe.com/watch/%s/' % video_id) | ||||
|         req.headers['Cookie'] = 'flashVersion=0;' | ||||
|  | ||||
|         # AnyClip videos require the flashversion cookie so that we get the link | ||||
|         # to the mp4 file | ||||
|         mobj_an = re.match(r'^an-(.*?)$', video_id) | ||||
|         if mobj_an: | ||||
|             req.headers['Cookie'] = 'flashVersion=0;' | ||||
|         webpage = self._download_webpage(req, video_id) | ||||
|  | ||||
|         # Extract URL, uploader and title from webpage | ||||
| @@ -122,9 +157,14 @@ class MetacafeIE(InfoExtractor): | ||||
|         video_title = self._html_search_regex(r'(?im)<title>(.*) - Video</title>', webpage, u'title') | ||||
|         description = self._og_search_description(webpage) | ||||
|         video_uploader = self._html_search_regex( | ||||
|                 r'submitter=(.*?);|googletag\.pubads\(\)\.setTargeting\("channel","([^"]+)"\);', | ||||
|                 r'submitter=(.*?);|googletag\.pubads\(\)\.setTargeting\("(?:channel|submiter)","([^"]+)"\);', | ||||
|                 webpage, u'uploader nickname', fatal=False) | ||||
|  | ||||
|         if re.search(r'"contentRating":"restricted"', webpage) is not None: | ||||
|             age_limit = 18 | ||||
|         else: | ||||
|             age_limit = 0 | ||||
|  | ||||
|         return { | ||||
|             '_type':    'video', | ||||
|             'id':       video_id, | ||||
| @@ -134,4 +174,5 @@ class MetacafeIE(InfoExtractor): | ||||
|             'upload_date':  None, | ||||
|             'title':    video_title, | ||||
|             'ext':      video_ext, | ||||
|             'age_limit': age_limit, | ||||
|         } | ||||
|   | ||||
							
								
								
									
										55
									
								
								youtube_dl/extractor/metacritic.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								youtube_dl/extractor/metacritic.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| import re | ||||
| import xml.etree.ElementTree | ||||
| import operator | ||||
|  | ||||
| from .common import InfoExtractor | ||||
|  | ||||
|  | ||||
| class MetacriticIE(InfoExtractor): | ||||
|     _VALID_URL = r'https?://www\.metacritic\.com/.+?/trailers/(?P<id>\d+)' | ||||
|  | ||||
|     _TEST = { | ||||
|         u'url': u'http://www.metacritic.com/game/playstation-4/infamous-second-son/trailers/3698222', | ||||
|         u'file': u'3698222.mp4', | ||||
|         u'info_dict': { | ||||
|             u'title': u'inFamous: Second Son - inSide Sucker Punch: Smoke & Mirrors', | ||||
|             u'description': u'Take a peak behind-the-scenes to see how Sucker Punch brings smoke into the universe of inFAMOUS Second Son on the PS4.', | ||||
|             u'duration': 221, | ||||
|         }, | ||||
|     } | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         video_id = mobj.group('id') | ||||
|         webpage = self._download_webpage(url, video_id) | ||||
|         # The xml is not well formatted, there are raw '&' | ||||
|         info_xml = self._download_webpage('http://www.metacritic.com/video_data?video=' + video_id, | ||||
|             video_id, u'Downloading info xml').replace('&', '&') | ||||
|         info = xml.etree.ElementTree.fromstring(info_xml.encode('utf-8')) | ||||
|  | ||||
|         clip = next(c for c in info.findall('playList/clip') if c.find('id').text == video_id) | ||||
|         formats = [] | ||||
|         for videoFile in clip.findall('httpURI/videoFile'): | ||||
|             rate_str = videoFile.find('rate').text | ||||
|             video_url = videoFile.find('filePath').text | ||||
|             formats.append({ | ||||
|                 'url': video_url, | ||||
|                 'ext': 'mp4', | ||||
|                 'format_id': rate_str, | ||||
|                 'rate': int(rate_str), | ||||
|             }) | ||||
|         formats.sort(key=operator.itemgetter('rate')) | ||||
|  | ||||
|         description = self._html_search_regex(r'<b>Description:</b>(.*?)</p>', | ||||
|             webpage, u'description', flags=re.DOTALL) | ||||
|  | ||||
|         info = { | ||||
|             'id': video_id, | ||||
|             'title': clip.find('title').text, | ||||
|             'formats': formats, | ||||
|             'description': description, | ||||
|             'duration': int(clip.find('duration').text), | ||||
|         } | ||||
|         # TODO: Remove when #980 has been merged | ||||
|         info.update(formats[-1]) | ||||
|         return info | ||||
							
								
								
									
										74
									
								
								youtube_dl/extractor/mit.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								youtube_dl/extractor/mit.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,74 @@ | ||||
| import re | ||||
| import json | ||||
|  | ||||
| from .common import InfoExtractor | ||||
| from ..utils import ( | ||||
|     clean_html, | ||||
|     get_element_by_id, | ||||
| ) | ||||
|  | ||||
|  | ||||
| class TechTVMITIE(InfoExtractor): | ||||
|     IE_NAME = u'techtv.mit.edu' | ||||
|     _VALID_URL = r'https?://techtv\.mit\.edu/(videos|embeds)/(?P<id>\d+)' | ||||
|  | ||||
|     _TEST = { | ||||
|         u'url': u'http://techtv.mit.edu/videos/25418-mit-dna-learning-center-set', | ||||
|         u'file': u'25418.mp4', | ||||
|         u'md5': u'1f8cb3e170d41fd74add04d3c9330e5f', | ||||
|         u'info_dict': { | ||||
|             u'title': u'MIT DNA Learning Center Set', | ||||
|             u'description': u'md5:82313335e8a8a3f243351ba55bc1b474', | ||||
|         }, | ||||
|     } | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         video_id = mobj.group('id') | ||||
|         raw_page = self._download_webpage( | ||||
|             'http://techtv.mit.edu/videos/%s' % video_id, video_id) | ||||
|         clean_page = re.compile(u'<!--.*?-->', re.S).sub(u'', raw_page) | ||||
|  | ||||
|         base_url = self._search_regex(r'ipadUrl: \'(.+?cloudfront.net/)', | ||||
|             raw_page, u'base url') | ||||
|         formats_json = self._search_regex(r'bitrates: (\[.+?\])', raw_page, | ||||
|             u'video formats') | ||||
|         formats = json.loads(formats_json) | ||||
|         formats = sorted(formats, key=lambda f: f['bitrate']) | ||||
|  | ||||
|         title = get_element_by_id('edit-title', clean_page) | ||||
|         description = clean_html(get_element_by_id('edit-description', clean_page)) | ||||
|         thumbnail = self._search_regex(r'playlist:.*?url: \'(.+?)\'', | ||||
|             raw_page, u'thumbnail', flags=re.DOTALL) | ||||
|  | ||||
|         return {'id': video_id, | ||||
|                 'title': title, | ||||
|                 'url': base_url + formats[-1]['url'].replace('mp4:', ''), | ||||
|                 'ext': 'mp4', | ||||
|                 'description': description, | ||||
|                 'thumbnail': thumbnail, | ||||
|                 } | ||||
|  | ||||
|  | ||||
| class MITIE(TechTVMITIE): | ||||
|     IE_NAME = u'video.mit.edu' | ||||
|     _VALID_URL = r'https?://video\.mit\.edu/watch/(?P<title>[^/]+)' | ||||
|  | ||||
|     _TEST = { | ||||
|         u'url': u'http://video.mit.edu/watch/the-government-is-profiling-you-13222/', | ||||
|         u'file': u'21783.mp4', | ||||
|         u'md5': u'7db01d5ccc1895fc5010e9c9e13648da', | ||||
|         u'info_dict': { | ||||
|             u'title': u'The Government is Profiling You', | ||||
|             u'description': u'md5:ad5795fe1e1623b73620dbfd47df9afd', | ||||
|         }, | ||||
|     } | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         page_title = mobj.group('title') | ||||
|         webpage = self._download_webpage(url, page_title) | ||||
|         self.to_screen('%s: Extracting %s url' % (page_title, TechTVMITIE.IE_NAME)) | ||||
|         embed_url = self._search_regex(r'<iframe .*?src="(.+?)"', webpage, | ||||
|             u'embed url') | ||||
|         return self.url_result(embed_url, ie='TechTVMIT') | ||||
| @@ -5,34 +5,27 @@ import socket | ||||
| from .common import InfoExtractor | ||||
| from ..utils import ( | ||||
|     compat_http_client, | ||||
|     compat_str, | ||||
|     compat_urllib_error, | ||||
|     compat_urllib_request, | ||||
|  | ||||
|     ExtractorError, | ||||
|     unified_strdate, | ||||
| ) | ||||
|  | ||||
|  | ||||
| class MixcloudIE(InfoExtractor): | ||||
|     _WORKING = False # New API, but it seems good http://www.mixcloud.com/developers/documentation/ | ||||
|     _VALID_URL = r'^(?:https?://)?(?:www\.)?mixcloud\.com/([\w\d-]+)/([\w\d-]+)' | ||||
|     IE_NAME = u'mixcloud' | ||||
|  | ||||
|     def report_download_json(self, file_id): | ||||
|         """Report JSON download.""" | ||||
|         self.to_screen(u'Downloading json') | ||||
|  | ||||
|     def get_urls(self, jsonData, fmt, bitrate='best'): | ||||
|         """Get urls from 'audio_formats' section in json""" | ||||
|         try: | ||||
|             bitrate_list = jsonData[fmt] | ||||
|             if bitrate is None or bitrate == 'best' or bitrate not in bitrate_list: | ||||
|                 bitrate = max(bitrate_list) # select highest | ||||
|  | ||||
|             url_list = jsonData[fmt][bitrate] | ||||
|         except TypeError: # we have no bitrate info. | ||||
|             url_list = jsonData[fmt] | ||||
|         return url_list | ||||
|     _TEST = { | ||||
|         u'url': u'http://www.mixcloud.com/dholbach/cryptkeeper/', | ||||
|         u'file': u'dholbach-cryptkeeper.mp3', | ||||
|         u'info_dict': { | ||||
|             u'title': u'Cryptkeeper', | ||||
|             u'description': u'After quite a long silence from myself, finally another Drum\'n\'Bass mix with my favourite current dance floor bangers.', | ||||
|             u'uploader': u'Daniel Holbach', | ||||
|             u'uploader_id': u'dholbach', | ||||
|             u'upload_date': u'20111115', | ||||
|         }, | ||||
|     } | ||||
|  | ||||
|     def check_urls(self, url_list): | ||||
|         """Returns 1st active url from list""" | ||||
| @@ -45,71 +38,32 @@ class MixcloudIE(InfoExtractor): | ||||
|  | ||||
|         return None | ||||
|  | ||||
|     def _print_formats(self, formats): | ||||
|         print('Available formats:') | ||||
|         for fmt in formats.keys(): | ||||
|             for b in formats[fmt]: | ||||
|                 try: | ||||
|                     ext = formats[fmt][b][0] | ||||
|                     print('%s\t%s\t[%s]' % (fmt, b, ext.split('.')[-1])) | ||||
|                 except TypeError: # we have no bitrate info | ||||
|                     ext = formats[fmt][0] | ||||
|                     print('%s\t%s\t[%s]' % (fmt, '??', ext.split('.')[-1])) | ||||
|                     break | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         if mobj is None: | ||||
|             raise ExtractorError(u'Invalid URL: %s' % url) | ||||
|         # extract uploader & filename from url | ||||
|         uploader = mobj.group(1).decode('utf-8') | ||||
|         file_id = uploader + "-" + mobj.group(2).decode('utf-8') | ||||
|  | ||||
|         # construct API request | ||||
|         file_url = 'http://www.mixcloud.com/api/1/cloudcast/' + '/'.join(url.split('/')[-3:-1]) + '.json' | ||||
|         # retrieve .json file with links to files | ||||
|         request = compat_urllib_request.Request(file_url) | ||||
|         try: | ||||
|             self.report_download_json(file_url) | ||||
|             jsonData = compat_urllib_request.urlopen(request).read() | ||||
|         except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err: | ||||
|             raise ExtractorError(u'Unable to retrieve file: %s' % compat_str(err)) | ||||
|         uploader = mobj.group(1) | ||||
|         cloudcast_name = mobj.group(2) | ||||
|         track_id = '-'.join((uploader, cloudcast_name)) | ||||
|         api_url = 'http://api.mixcloud.com/%s/%s/' % (uploader, cloudcast_name) | ||||
|         webpage = self._download_webpage(url, track_id) | ||||
|         json_data = self._download_webpage(api_url, track_id, | ||||
|             u'Downloading cloudcast info') | ||||
|         info = json.loads(json_data) | ||||
|  | ||||
|         # parse JSON | ||||
|         json_data = json.loads(jsonData) | ||||
|         player_url = json_data['player_swf_url'] | ||||
|         formats = dict(json_data['audio_formats']) | ||||
|         preview_url = self._search_regex(r'data-preview-url="(.+?)"', webpage, u'preview url') | ||||
|         song_url = preview_url.replace('/previews/', '/cloudcasts/originals/') | ||||
|         template_url = re.sub(r'(stream\d*)', 'stream%d', song_url) | ||||
|         final_song_url = self.check_urls(template_url % i for i in range(30)) | ||||
|  | ||||
|         req_format = self._downloader.params.get('format', None) | ||||
|  | ||||
|         if self._downloader.params.get('listformats', None): | ||||
|             self._print_formats(formats) | ||||
|             return | ||||
|  | ||||
|         if req_format is None or req_format == 'best': | ||||
|             for format_param in formats.keys(): | ||||
|                 url_list = self.get_urls(formats, format_param) | ||||
|                 # check urls | ||||
|                 file_url = self.check_urls(url_list) | ||||
|                 if file_url is not None: | ||||
|                     break # got it! | ||||
|         else: | ||||
|             if req_format not in formats: | ||||
|                 raise ExtractorError(u'Format is not available') | ||||
|  | ||||
|             url_list = self.get_urls(formats, req_format) | ||||
|             file_url = self.check_urls(url_list) | ||||
|             format_param = req_format | ||||
|  | ||||
|         return [{ | ||||
|             'id': file_id.decode('utf-8'), | ||||
|             'url': file_url.decode('utf-8'), | ||||
|             'uploader': uploader.decode('utf-8'), | ||||
|             'upload_date': None, | ||||
|             'title': json_data['name'], | ||||
|             'ext': file_url.split('.')[-1].decode('utf-8'), | ||||
|             'format': (format_param is None and u'NA' or format_param.decode('utf-8')), | ||||
|             'thumbnail': json_data['thumbnail_url'], | ||||
|             'description': json_data['description'], | ||||
|             'player_url': player_url.decode('utf-8'), | ||||
|         }] | ||||
|         return { | ||||
|             'id': track_id, | ||||
|             'title': info['name'], | ||||
|             'url': final_song_url, | ||||
|             'ext': 'mp3', | ||||
|             'description': info['description'], | ||||
|             'thumbnail': info['pictures'].get('extra_large'), | ||||
|             'uploader': info['user']['name'], | ||||
|             'uploader_id': info['user']['username'], | ||||
|             'upload_date': unified_strdate(info['created_time']), | ||||
|             'view_count': info['play_count'], | ||||
|         } | ||||
|   | ||||
							
								
								
									
										49
									
								
								youtube_dl/extractor/mofosex.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								youtube_dl/extractor/mofosex.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | ||||
| import os | ||||
| import re | ||||
|  | ||||
| from .common import InfoExtractor | ||||
| from ..utils import ( | ||||
|     compat_urllib_parse_urlparse, | ||||
|     compat_urllib_request, | ||||
|     compat_urllib_parse, | ||||
| ) | ||||
|  | ||||
| class MofosexIE(InfoExtractor): | ||||
|     _VALID_URL = r'^(?:https?://)?(?:www\.)?(?P<url>mofosex\.com/videos/(?P<videoid>[0-9]+)/.*?\.html)' | ||||
|     _TEST = { | ||||
|         u'url': u'http://www.mofosex.com/videos/5018/japanese-teen-music-video.html', | ||||
|         u'file': u'5018.mp4', | ||||
|         u'md5': u'1b2eb47ac33cc75d4a80e3026b613c5a', | ||||
|         u'info_dict': { | ||||
|             u"title": u"Japanese Teen Music Video", | ||||
|             u"age_limit": 18, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         video_id = mobj.group('videoid') | ||||
|         url = 'http://www.' + mobj.group('url') | ||||
|  | ||||
|         req = compat_urllib_request.Request(url) | ||||
|         req.add_header('Cookie', 'age_verified=1') | ||||
|         webpage = self._download_webpage(req, video_id) | ||||
|  | ||||
|         video_title = self._html_search_regex(r'<h1>(.+?)<', webpage, u'title') | ||||
|         video_url = compat_urllib_parse.unquote(self._html_search_regex(r'flashvars.video_url = \'([^\']+)', webpage, u'video_url')) | ||||
|         path = compat_urllib_parse_urlparse(video_url).path | ||||
|         extension = os.path.splitext(path)[1][1:] | ||||
|         format = path.split('/')[5].split('_')[:2] | ||||
|         format = "-".join(format) | ||||
|  | ||||
|         age_limit = self._rta_search(webpage) | ||||
|  | ||||
|         return { | ||||
|             'id': video_id, | ||||
|             'title': video_title, | ||||
|             'url': video_url, | ||||
|             'ext': extension, | ||||
|             'format': format, | ||||
|             'format_id': format, | ||||
|             'age_limit': age_limit, | ||||
|         } | ||||
| @@ -26,6 +26,7 @@ class MTVIE(InfoExtractor): | ||||
|             }, | ||||
|         }, | ||||
|         { | ||||
|             u'add_ie': ['Vevo'], | ||||
|             u'url': u'http://www.mtv.com/videos/taylor-swift/916187/everything-has-changed-ft-ed-sheeran.jhtml', | ||||
|             u'file': u'USCJY1331283.mp4', | ||||
|             u'md5': u'73b4e7fcadd88929292fe52c3ced8caf', | ||||
| @@ -47,53 +48,64 @@ class MTVIE(InfoExtractor): | ||||
|     def _transform_rtmp_url(rtmp_video_url): | ||||
|         m = re.match(r'^rtmpe?://.*?/(?P<finalid>gsp\..+?/.*)$', rtmp_video_url) | ||||
|         if not m: | ||||
|             raise ExtractorError(u'Cannot transform RTMP url') | ||||
|             return rtmp_video_url | ||||
|         base = 'http://mtvnmobile.vo.llnwd.net/kip0/_pxn=1+_pxI0=Ripod-h264+_pxL0=undefined+_pxM0=+_pxK=18639+_pxE=mp4/44620/mtvnorigin/' | ||||
|         return base + m.group('finalid') | ||||
|  | ||||
|     def _get_thumbnail_url(self, uri, itemdoc): | ||||
|         return 'http://mtv.mtvnimages.com/uri/' + uri | ||||
|  | ||||
|     def _extract_video_url(self, metadataXml): | ||||
|     def _extract_video_formats(self, metadataXml): | ||||
|         if '/error_country_block.swf' in metadataXml: | ||||
|             raise ExtractorError(u'This video is not available from your country.', expected=True) | ||||
|         mdoc = xml.etree.ElementTree.fromstring(metadataXml.encode('utf-8')) | ||||
|         renditions = mdoc.findall('.//rendition') | ||||
|  | ||||
|         # For now, always pick the highest quality. | ||||
|         rendition = renditions[-1] | ||||
|  | ||||
|         try: | ||||
|             _,_,ext = rendition.attrib['type'].partition('/') | ||||
|             format = ext + '-' + rendition.attrib['width'] + 'x' + rendition.attrib['height'] + '_' + rendition.attrib['bitrate'] | ||||
|             rtmp_video_url = rendition.find('./src').text | ||||
|         except KeyError: | ||||
|             raise ExtractorError('Invalid rendition field.') | ||||
|         video_url = self._transform_rtmp_url(rtmp_video_url) | ||||
|         return {'ext': ext, 'url': video_url, 'format': format} | ||||
|         formats = [] | ||||
|         for rendition in mdoc.findall('.//rendition'): | ||||
|             try: | ||||
|                 _, _, ext = rendition.attrib['type'].partition('/') | ||||
|                 rtmp_video_url = rendition.find('./src').text | ||||
|                 formats.append({'ext': ext, | ||||
|                                 'url': self._transform_rtmp_url(rtmp_video_url), | ||||
|                                 'format_id': rendition.get('bitrate'), | ||||
|                                 'width': int(rendition.get('width')), | ||||
|                                 'height': int(rendition.get('height')), | ||||
|                                 }) | ||||
|             except (KeyError, TypeError): | ||||
|                 raise ExtractorError('Invalid rendition field.') | ||||
|         return formats | ||||
|  | ||||
|     def _get_video_info(self, itemdoc): | ||||
|         uri = itemdoc.find('guid').text | ||||
|         video_id = self._id_from_uri(uri) | ||||
|         self.report_extraction(video_id) | ||||
|         mediagen_url = itemdoc.find('%s/%s' % (_media_xml_tag('group'), _media_xml_tag('content'))).attrib['url'] | ||||
|         # Remove the templates, like &device={device} | ||||
|         mediagen_url = re.sub(r'&[^=]*?={.*?}(?=(&|$))', u'', mediagen_url) | ||||
|         if 'acceptMethods' not in mediagen_url: | ||||
|             mediagen_url += '&acceptMethods=fms' | ||||
|         mediagen_page = self._download_webpage(mediagen_url, video_id, | ||||
|                                                u'Downloading video urls') | ||||
|         video_info = self._extract_video_url(mediagen_page) | ||||
|  | ||||
|         description_node = itemdoc.find('description') | ||||
|         if description_node is not None: | ||||
|             description = description_node.text | ||||
|             description = description_node.text.strip() | ||||
|         else: | ||||
|             description = None | ||||
|         video_info.update({'title': itemdoc.find('title').text, | ||||
|                            'id': video_id, | ||||
|                            'thumbnail': self._get_thumbnail_url(uri, itemdoc), | ||||
|                            'description': description, | ||||
|                            }) | ||||
|         return video_info | ||||
|  | ||||
|         info = { | ||||
|             'title': itemdoc.find('title').text, | ||||
|             'formats': self._extract_video_formats(mediagen_page), | ||||
|             'id': video_id, | ||||
|             'thumbnail': self._get_thumbnail_url(uri, itemdoc), | ||||
|             'description': description, | ||||
|         } | ||||
|  | ||||
|         # TODO: Remove when #980 has been merged | ||||
|         info.update(info['formats'][-1]) | ||||
|  | ||||
|         return info | ||||
|  | ||||
|     def _get_videos_info(self, uri): | ||||
|         video_id = self._id_from_uri(uri) | ||||
|   | ||||
							
								
								
									
										64
									
								
								youtube_dl/extractor/muzu.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								youtube_dl/extractor/muzu.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | ||||
| import re | ||||
| import json | ||||
|  | ||||
| from .common import InfoExtractor | ||||
| from ..utils import ( | ||||
|     compat_urllib_parse, | ||||
|     determine_ext, | ||||
| ) | ||||
|  | ||||
|  | ||||
| class MuzuTVIE(InfoExtractor): | ||||
|     _VALID_URL = r'https?://www.muzu.tv/(.+?)/(.+?)/(?P<id>\d+)' | ||||
|     IE_NAME = u'muzu.tv' | ||||
|  | ||||
|     _TEST = { | ||||
|         u'url': u'http://www.muzu.tv/defected/marcashken-featuring-sos-cat-walk-original-mix-music-video/1981454/', | ||||
|         u'file': u'1981454.mp4', | ||||
|         u'md5': u'98f8b2c7bc50578d6a0364fff2bfb000', | ||||
|         u'info_dict': { | ||||
|             u'title': u'Cat Walk (Original Mix)', | ||||
|             u'description': u'md5:90e868994de201b2570e4e5854e19420', | ||||
|             u'uploader': u'MarcAshken featuring SOS', | ||||
|         }, | ||||
|     } | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         video_id = mobj.group('id') | ||||
|  | ||||
|         info_data = compat_urllib_parse.urlencode({'format': 'json', | ||||
|                                                    'url': url, | ||||
|                                                    }) | ||||
|         video_info_page = self._download_webpage('http://www.muzu.tv/api/oembed/?%s' % info_data, | ||||
|                                                  video_id, u'Downloading video info') | ||||
|         info = json.loads(video_info_page) | ||||
|  | ||||
|         player_info_page = self._download_webpage('http://player.muzu.tv/player/playerInit?ai=%s' % video_id, | ||||
|                                                   video_id, u'Downloading player info') | ||||
|         video_info = json.loads(player_info_page)['videos'][0] | ||||
|         for quality in ['1080' , '720', '480', '360']: | ||||
|             if video_info.get('v%s' % quality): | ||||
|                 break | ||||
|  | ||||
|         data = compat_urllib_parse.urlencode({'ai': video_id, | ||||
|                                               # Even if each time you watch a video the hash changes, | ||||
|                                               # it seems to work for different videos, and it will work | ||||
|                                               # even if you use any non empty string as a hash | ||||
|                                               'viewhash': 'VBNff6djeV4HV5TRPW5kOHub2k', | ||||
|                                               'device': 'web', | ||||
|                                               'qv': quality, | ||||
|                                               }) | ||||
|         video_url_page = self._download_webpage('http://player.muzu.tv/player/requestVideo?%s' % data, | ||||
|                                                 video_id, u'Downloading video url') | ||||
|         video_url_info = json.loads(video_url_page) | ||||
|         video_url = video_url_info['url'] | ||||
|  | ||||
|         return {'id': video_id, | ||||
|                 'title': info['title'], | ||||
|                 'url': video_url, | ||||
|                 'ext': determine_ext(video_url), | ||||
|                 'thumbnail': info['thumbnail_url'], | ||||
|                 'description': info['description'], | ||||
|                 'uploader': info['author_name'], | ||||
|                 } | ||||
							
								
								
									
										48
									
								
								youtube_dl/extractor/myspace.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								youtube_dl/extractor/myspace.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | ||||
| import re | ||||
| import json | ||||
|  | ||||
| from .common import InfoExtractor | ||||
| from ..utils import ( | ||||
|     compat_str, | ||||
| ) | ||||
|  | ||||
|  | ||||
| class MySpaceIE(InfoExtractor): | ||||
|     _VALID_URL = r'https?://myspace\.com/([^/]+)/video/[^/]+/(?P<id>\d+)' | ||||
|  | ||||
|     _TEST = { | ||||
|         u'url': u'https://myspace.com/coldplay/video/viva-la-vida/100008689', | ||||
|         u'info_dict': { | ||||
|             u'id': u'100008689', | ||||
|             u'ext': u'flv', | ||||
|             u'title': u'Viva La Vida', | ||||
|             u'description': u'The official Viva La Vida video, directed by Hype Williams', | ||||
|             u'uploader': u'Coldplay', | ||||
|             u'uploader_id': u'coldplay', | ||||
|         }, | ||||
|         u'params': { | ||||
|             # rtmp download | ||||
|             u'skip_download': True, | ||||
|         }, | ||||
|     } | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         video_id = mobj.group('id') | ||||
|         webpage = self._download_webpage(url, video_id) | ||||
|         context = json.loads(self._search_regex(r'context = ({.*?});', webpage, | ||||
|             u'context')) | ||||
|         video = context['video'] | ||||
|         rtmp_url, play_path = video['streamUrl'].split(';', 1) | ||||
|  | ||||
|         return { | ||||
|             'id': compat_str(video['mediaId']), | ||||
|             'title': video['title'], | ||||
|             'url': rtmp_url, | ||||
|             'play_path': play_path, | ||||
|             'ext': 'flv', | ||||
|             'description': video['description'], | ||||
|             'thumbnail': video['imageUrl'], | ||||
|             'uploader': video['artistName'], | ||||
|             'uploader_id': video['artistUsername'], | ||||
|         } | ||||
| @@ -2,11 +2,13 @@ import binascii | ||||
| import base64 | ||||
| import hashlib | ||||
| import re | ||||
| import json | ||||
|  | ||||
| from .common import InfoExtractor | ||||
| from ..utils import ( | ||||
|     compat_ord, | ||||
|     compat_urllib_parse, | ||||
|     compat_urllib_request, | ||||
|  | ||||
|     ExtractorError, | ||||
| ) | ||||
| @@ -16,7 +18,7 @@ from ..utils import ( | ||||
| class MyVideoIE(InfoExtractor): | ||||
|     """Information Extractor for myvideo.de.""" | ||||
|  | ||||
|     _VALID_URL = r'(?:http://)?(?:www\.)?myvideo\.de/watch/([0-9]+)/([^?/]+).*' | ||||
|     _VALID_URL = r'(?:http://)?(?:www\.)?myvideo\.de/(?:[^/]+/)?watch/([0-9]+)/([^?/]+).*' | ||||
|     IE_NAME = u'myvideo' | ||||
|     _TEST = { | ||||
|         u'url': u'http://www.myvideo.de/watch/8229274/bowling_fail_or_win', | ||||
| @@ -85,6 +87,20 @@ class MyVideoIE(InfoExtractor): | ||||
|                 'ext':      video_ext, | ||||
|             }] | ||||
|  | ||||
|         mobj = re.search(r'data-video-service="/service/data/video/%s/config' % video_id, webpage) | ||||
|         if mobj is not None: | ||||
|             request = compat_urllib_request.Request('http://www.myvideo.de/service/data/video/%s/config' % video_id, '') | ||||
|             response = self._download_webpage(request, video_id, | ||||
|                                               u'Downloading video info') | ||||
|             info = json.loads(base64.b64decode(response).decode('utf-8')) | ||||
|             return {'id': video_id, | ||||
|                     'title': info['title'], | ||||
|                     'url': info['streaming_url'].replace('rtmpe', 'rtmpt'), | ||||
|                     'play_path': info['filename'], | ||||
|                     'ext': 'flv', | ||||
|                     'thumbnail': info['thumbnail'][0]['url'], | ||||
|                     } | ||||
|  | ||||
|         # try encxml | ||||
|         mobj = re.search('var flashvars={(.+?)}', webpage) | ||||
|         if mobj is None: | ||||
|   | ||||
							
								
								
									
										73
									
								
								youtube_dl/extractor/naver.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								youtube_dl/extractor/naver.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,73 @@ | ||||
| # encoding: utf-8 | ||||
| import re | ||||
| import xml.etree.ElementTree | ||||
|  | ||||
| from .common import InfoExtractor | ||||
| from ..utils import ( | ||||
|     compat_urllib_parse, | ||||
|     ExtractorError, | ||||
| ) | ||||
|  | ||||
|  | ||||
| class NaverIE(InfoExtractor): | ||||
|     _VALID_URL = r'https?://tvcast\.naver\.com/v/(?P<id>\d+)' | ||||
|  | ||||
|     _TEST = { | ||||
|         u'url': u'http://tvcast.naver.com/v/81652', | ||||
|         u'file': u'81652.mp4', | ||||
|         u'info_dict': { | ||||
|             u'title': u'[9월 모의고사 해설강의][수학_김상희] 수학 A형 16~20번', | ||||
|             u'description': u'합격불변의 법칙 메가스터디 | 메가스터디 수학 김상희 선생님이 9월 모의고사 수학A형 16번에서 20번까지 해설강의를 공개합니다.', | ||||
|             u'upload_date': u'20130903', | ||||
|         }, | ||||
|     } | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         video_id = mobj.group(1) | ||||
|         webpage = self._download_webpage(url, video_id) | ||||
|         m_id = re.search(r'var rmcPlayer = new nhn.rmcnmv.RMCVideoPlayer\("(.+?)", "(.+?)"', | ||||
|             webpage) | ||||
|         if m_id is None: | ||||
|             raise ExtractorError(u'couldn\'t extract vid and key') | ||||
|         vid = m_id.group(1) | ||||
|         key = m_id.group(2) | ||||
|         query = compat_urllib_parse.urlencode({'vid': vid, 'inKey': key,}) | ||||
|         query_urls = compat_urllib_parse.urlencode({ | ||||
|             'masterVid': vid, | ||||
|             'protocol': 'p2p', | ||||
|             'inKey': key, | ||||
|         }) | ||||
|         info_xml = self._download_webpage( | ||||
|             'http://serviceapi.rmcnmv.naver.com/flash/videoInfo.nhn?' + query, | ||||
|             video_id, u'Downloading video info') | ||||
|         urls_xml = self._download_webpage( | ||||
|             'http://serviceapi.rmcnmv.naver.com/flash/playableEncodingOption.nhn?' + query_urls, | ||||
|             video_id, u'Downloading video formats info') | ||||
|         info = xml.etree.ElementTree.fromstring(info_xml.encode('utf-8')) | ||||
|         urls = xml.etree.ElementTree.fromstring(urls_xml.encode('utf-8')) | ||||
|  | ||||
|         formats = [] | ||||
|         for format_el in urls.findall('EncodingOptions/EncodingOption'): | ||||
|             domain = format_el.find('Domain').text | ||||
|             if domain.startswith('rtmp'): | ||||
|                 continue | ||||
|             formats.append({ | ||||
|                 'url': domain + format_el.find('uri').text, | ||||
|                 'ext': 'mp4', | ||||
|                 'width': int(format_el.find('width').text), | ||||
|                 'height': int(format_el.find('height').text), | ||||
|             }) | ||||
|  | ||||
|         info = { | ||||
|             'id': video_id, | ||||
|             'title': info.find('Subject').text, | ||||
|             'formats': formats, | ||||
|             'description': self._og_search_description(webpage), | ||||
|             'thumbnail': self._og_search_thumbnail(webpage), | ||||
|             'upload_date': info.find('WriteDate').text.replace('.', ''), | ||||
|             'view_count': int(info.find('PlayCount').text), | ||||
|         } | ||||
|         # TODO: Remove when #980 has been merged | ||||
|         info.update(formats[-1]) | ||||
|         return info | ||||
							
								
								
									
										33
									
								
								youtube_dl/extractor/nbc.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								youtube_dl/extractor/nbc.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| import re | ||||
| import xml.etree.ElementTree | ||||
|  | ||||
| from .common import InfoExtractor | ||||
| from ..utils import find_xpath_attr, compat_str | ||||
|  | ||||
|  | ||||
| class NBCNewsIE(InfoExtractor): | ||||
|     _VALID_URL = r'https?://www\.nbcnews\.com/video/.+?/(?P<id>\d+)' | ||||
|  | ||||
|     _TEST = { | ||||
|         u'url': u'http://www.nbcnews.com/video/nbc-news/52753292', | ||||
|         u'file': u'52753292.flv', | ||||
|         u'md5': u'47abaac93c6eaf9ad37ee6c4463a5179', | ||||
|         u'info_dict': { | ||||
|             u'title': u'Crew emerges after four-month Mars food study', | ||||
|             u'description': u'md5:24e632ffac72b35f8b67a12d1b6ddfc1', | ||||
|         }, | ||||
|     } | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         video_id = mobj.group('id') | ||||
|         info_xml = self._download_webpage('http://www.nbcnews.com/id/%s/displaymode/1219' % video_id, video_id) | ||||
|         info = xml.etree.ElementTree.fromstring(info_xml.encode('utf-8')).find('video') | ||||
|  | ||||
|         return {'id': video_id, | ||||
|                 'title': info.find('headline').text, | ||||
|                 'ext': 'flv', | ||||
|                 'url': find_xpath_attr(info, 'media', 'type', 'flashVideo').text, | ||||
|                 'description': compat_str(info.find('caption').text), | ||||
|                 'thumbnail': find_xpath_attr(info, 'media', 'type', 'thumbnail').text, | ||||
|                 } | ||||
							
								
								
									
										38
									
								
								youtube_dl/extractor/newgrounds.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								youtube_dl/extractor/newgrounds.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| import json | ||||
| import re | ||||
|  | ||||
| from .common import InfoExtractor | ||||
| from ..utils import determine_ext | ||||
|  | ||||
|  | ||||
| class NewgroundsIE(InfoExtractor): | ||||
|     _VALID_URL = r'(?:https?://)?(?:www\.)?newgrounds\.com/audio/listen/(?P<id>\d+)' | ||||
|     _TEST = { | ||||
|         u'url': u'http://www.newgrounds.com/audio/listen/549479', | ||||
|         u'file': u'549479.mp3', | ||||
|         u'md5': u'fe6033d297591288fa1c1f780386f07a', | ||||
|         u'info_dict': { | ||||
|             u"title": u"B7 - BusMode", | ||||
|             u"uploader": u"Burn7", | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         music_id = mobj.group('id') | ||||
|         webpage = self._download_webpage(url, music_id) | ||||
|          | ||||
|         title = self._html_search_regex(r',"name":"([^"]+)",', webpage, u'music title') | ||||
|         uploader = self._html_search_regex(r',"artist":"([^"]+)",', webpage, u'music uploader') | ||||
|          | ||||
|         music_url_json_string = self._html_search_regex(r'({"url":"[^"]+"),', webpage, u'music url') + '}' | ||||
|         music_url_json = json.loads(music_url_json_string) | ||||
|         music_url = music_url_json['url'] | ||||
|  | ||||
|         return { | ||||
|             'id':       music_id, | ||||
|             'title':    title, | ||||
|             'url':      music_url, | ||||
|             'uploader': uploader, | ||||
|             'ext':      determine_ext(music_url), | ||||
|         } | ||||
							
								
								
									
										120
									
								
								youtube_dl/extractor/nhl.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								youtube_dl/extractor/nhl.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,120 @@ | ||||
| import re | ||||
| import json | ||||
| import xml.etree.ElementTree | ||||
|  | ||||
| from .common import InfoExtractor | ||||
| from ..utils import ( | ||||
|     compat_urlparse, | ||||
|     compat_urllib_parse, | ||||
|     determine_ext, | ||||
|     unified_strdate, | ||||
| ) | ||||
|  | ||||
|  | ||||
| class NHLBaseInfoExtractor(InfoExtractor): | ||||
|     @staticmethod | ||||
|     def _fix_json(json_string): | ||||
|         return json_string.replace('\\\'', '\'') | ||||
|  | ||||
|     def _extract_video(self, info): | ||||
|         video_id = info['id'] | ||||
|         self.report_extraction(video_id) | ||||
|  | ||||
|         initial_video_url = info['publishPoint'] | ||||
|         data = compat_urllib_parse.urlencode({ | ||||
|             'type': 'fvod', | ||||
|             'path': initial_video_url.replace('.mp4', '_sd.mp4'), | ||||
|         }) | ||||
|         path_url = 'http://video.nhl.com/videocenter/servlets/encryptvideopath?' + data | ||||
|         path_response = self._download_webpage(path_url, video_id, | ||||
|             u'Downloading final video url') | ||||
|         path_doc = xml.etree.ElementTree.fromstring(path_response) | ||||
|         video_url = path_doc.find('path').text | ||||
|  | ||||
|         join = compat_urlparse.urljoin | ||||
|         return { | ||||
|             'id': video_id, | ||||
|             'title': info['name'], | ||||
|             'url': video_url, | ||||
|             'ext': determine_ext(video_url), | ||||
|             'description': info['description'], | ||||
|             'duration': int(info['duration']), | ||||
|             'thumbnail': join(join(video_url, '/u/'), info['bigImage']), | ||||
|             'upload_date': unified_strdate(info['releaseDate'].split('.')[0]), | ||||
|         } | ||||
|  | ||||
|  | ||||
| class NHLIE(NHLBaseInfoExtractor): | ||||
|     IE_NAME = u'nhl.com' | ||||
|     _VALID_URL = r'https?://video(?P<team>\.[^.]*)?\.nhl\.com/videocenter/console\?.*?(?<=[?&])id=(?P<id>\d+)' | ||||
|  | ||||
|     _TEST = { | ||||
|         u'url': u'http://video.canucks.nhl.com/videocenter/console?catid=6?id=453614', | ||||
|         u'file': u'453614.mp4', | ||||
|         u'info_dict': { | ||||
|             u'title': u'Quick clip: Weise 4-3 goal vs Flames', | ||||
|             u'description': u'Dale Weise scores his first of the season to put the Canucks up 4-3.', | ||||
|             u'duration': 18, | ||||
|             u'upload_date': u'20131006', | ||||
|         }, | ||||
|     } | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         video_id = mobj.group('id') | ||||
|         json_url = 'http://video.nhl.com/videocenter/servlets/playlist?ids=%s&format=json' % video_id | ||||
|         info_json = self._download_webpage(json_url, video_id, | ||||
|             u'Downloading info json') | ||||
|         info_json = self._fix_json(info_json) | ||||
|         info = json.loads(info_json)[0] | ||||
|         return self._extract_video(info) | ||||
|  | ||||
|  | ||||
| class NHLVideocenterIE(NHLBaseInfoExtractor): | ||||
|     IE_NAME = u'nhl.com:videocenter' | ||||
|     IE_DESC = u'Download the first 12 videos from a videocenter category' | ||||
|     _VALID_URL = r'https?://video\.(?P<team>[^.]*)\.nhl\.com/videocenter/(console\?.*?catid=(?P<catid>[^&]+))?' | ||||
|  | ||||
|     @classmethod | ||||
|     def suitable(cls, url): | ||||
|         if NHLIE.suitable(url): | ||||
|             return False | ||||
|         return super(NHLVideocenterIE, cls).suitable(url) | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         team = mobj.group('team') | ||||
|         webpage = self._download_webpage(url, team) | ||||
|         cat_id = self._search_regex( | ||||
|             [r'var defaultCatId = "(.+?)";', | ||||
|              r'{statusIndex:0,index:0,.*?id:(.*?),'], | ||||
|             webpage, u'category id') | ||||
|         playlist_title = self._html_search_regex( | ||||
|             r'tab0"[^>]*?>(.*?)</td>', | ||||
|             webpage, u'playlist title', flags=re.DOTALL).lower().capitalize() | ||||
|  | ||||
|         data = compat_urllib_parse.urlencode({ | ||||
|             'cid': cat_id, | ||||
|             # This is the default value | ||||
|             'count': 12, | ||||
|             'ptrs': 3, | ||||
|             'format': 'json', | ||||
|         }) | ||||
|         path = '/videocenter/servlets/browse?' + data | ||||
|         request_url = compat_urlparse.urljoin(url, path) | ||||
|         response = self._download_webpage(request_url, playlist_title) | ||||
|         response = self._fix_json(response) | ||||
|         if not response.strip(): | ||||
|             self._downloader.report_warning(u'Got an empty reponse, trying ' | ||||
|                                             u'adding the "newvideos" parameter') | ||||
|             response = self._download_webpage(request_url + '&newvideos=true', | ||||
|                 playlist_title) | ||||
|             response = self._fix_json(response) | ||||
|         videos = json.loads(response) | ||||
|  | ||||
|         return { | ||||
|             '_type': 'playlist', | ||||
|             'title': playlist_title, | ||||
|             'id': cat_id, | ||||
|             'entries': [self._extract_video(i) for i in videos], | ||||
|         } | ||||
							
								
								
									
										46
									
								
								youtube_dl/extractor/nowvideo.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								youtube_dl/extractor/nowvideo.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| import re | ||||
|  | ||||
| from .common import InfoExtractor | ||||
| from ..utils import compat_urlparse | ||||
|  | ||||
|  | ||||
| class NowVideoIE(InfoExtractor): | ||||
|     _VALID_URL = r'(?:https?://)?(?:www\.)?nowvideo\.ch/video/(?P<id>\w+)' | ||||
|     _TEST = { | ||||
|         u'url': u'http://www.nowvideo.ch/video/0mw0yow7b6dxa', | ||||
|         u'file': u'0mw0yow7b6dxa.flv', | ||||
|         u'md5': u'f8fbbc8add72bd95b7850c6a02fc8817', | ||||
|         u'info_dict': { | ||||
|             u"title": u"youtubedl test video _BaW_jenozKc.mp4" | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|  | ||||
|         video_id = mobj.group('id') | ||||
|         webpage_url = 'http://www.nowvideo.ch/video/' + video_id | ||||
|         embed_url = 'http://embed.nowvideo.ch/embed.php?v=' + video_id | ||||
|         webpage = self._download_webpage(webpage_url, video_id) | ||||
|         embed_page = self._download_webpage(embed_url, video_id, | ||||
|             u'Downloading embed page') | ||||
|  | ||||
|         self.report_extraction(video_id) | ||||
|  | ||||
|         video_title = self._html_search_regex(r'<h4>(.*)</h4>', | ||||
|             webpage, u'video title') | ||||
|  | ||||
|         video_key = self._search_regex(r'var fkzd="(.*)";', | ||||
|             embed_page, u'video key') | ||||
|  | ||||
|         api_call = "http://www.nowvideo.ch/api/player.api.php?file={0}&numOfErrors=0&cid=1&key={1}".format(video_id, video_key) | ||||
|         api_response = self._download_webpage(api_call, video_id, | ||||
|             u'Downloading API page') | ||||
|         video_url = compat_urlparse.parse_qs(api_response)[u'url'][0] | ||||
|  | ||||
|         return [{ | ||||
|             'id':        video_id, | ||||
|             'url':       video_url, | ||||
|             'ext':       'flv', | ||||
|             'title':     video_title, | ||||
|         }] | ||||
							
								
								
									
										58
									
								
								youtube_dl/extractor/ooyala.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								youtube_dl/extractor/ooyala.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | ||||
| import re | ||||
| import json | ||||
|  | ||||
| from .common import InfoExtractor | ||||
| from ..utils import unescapeHTML | ||||
|  | ||||
| class OoyalaIE(InfoExtractor): | ||||
|     _VALID_URL = r'https?://.+?\.ooyala\.com/.*?embedCode=(?P<id>.+?)(&|$)' | ||||
|  | ||||
|     _TEST = { | ||||
|         # From http://it.slashdot.org/story/13/04/25/178216/recovering-data-from-broken-hard-drives-and-ssds-video | ||||
|         u'url': u'http://player.ooyala.com/player.js?embedCode=pxczE2YjpfHfn1f3M-ykG_AmJRRn0PD8', | ||||
|         u'file': u'pxczE2YjpfHfn1f3M-ykG_AmJRRn0PD8.mp4', | ||||
|         u'md5': u'3f5cceb3a7bf461d6c29dc466cf8033c', | ||||
|         u'info_dict': { | ||||
|             u'title': u'Explaining Data Recovery from Hard Drives and SSDs', | ||||
|             u'description': u'How badly damaged does a drive have to be to defeat Russell and his crew? Apparently, smashed to bits.', | ||||
|         }, | ||||
|     } | ||||
|  | ||||
|     @staticmethod | ||||
|     def _url_for_embed_code(embed_code): | ||||
|         return 'http://player.ooyala.com/player.js?embedCode=%s' % embed_code | ||||
|  | ||||
|     def _extract_result(self, info, more_info): | ||||
|         return {'id': info['embedCode'], | ||||
|                 'ext': 'mp4', | ||||
|                 'title': unescapeHTML(info['title']), | ||||
|                 'url': info.get('ipad_url') or info['url'], | ||||
|                 'description': unescapeHTML(more_info['description']), | ||||
|                 'thumbnail': more_info['promo'], | ||||
|                 } | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
|         embedCode = mobj.group('id') | ||||
|         player_url = 'http://player.ooyala.com/player.js?embedCode=%s' % embedCode | ||||
|         player = self._download_webpage(player_url, embedCode) | ||||
|         mobile_url = self._search_regex(r'mobile_player_url="(.+?)&device="', | ||||
|                                         player, u'mobile player url') | ||||
|         mobile_player = self._download_webpage(mobile_url, embedCode) | ||||
|         videos_info = self._search_regex( | ||||
|             r'var streams=window.oo_testEnv\?\[\]:eval\("\((\[{.*?}\])\)"\);', | ||||
|             mobile_player, u'info').replace('\\"','"') | ||||
|         videos_more_info = self._search_regex(r'eval\("\(({.*?\\"promo\\".*?})\)"', mobile_player, u'more info').replace('\\"','"') | ||||
|         videos_info = json.loads(videos_info) | ||||
|         videos_more_info =json.loads(videos_more_info) | ||||
|  | ||||
|         if videos_more_info.get('lineup'): | ||||
|             videos = [self._extract_result(info, more_info) for (info, more_info) in zip(videos_info, videos_more_info['lineup'])] | ||||
|             return {'_type': 'playlist', | ||||
|                     'id': embedCode, | ||||
|                     'title': unescapeHTML(videos_more_info['title']), | ||||
|                     'entries': videos, | ||||
|                     } | ||||
|         else: | ||||
|             return self._extract_result(videos_info[0], videos_more_info) | ||||
|          | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user