Compare commits
	
		
			1023 Commits
		
	
	
		
			2014.12.16
			...
			2015.03.03
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					07490f8017 | ||
| 
						 | 
					a7440261c5 | ||
| 
						 | 
					76c73715fb | ||
| 
						 | 
					c75f0b361a | ||
| 
						 | 
					295df4edb9 | ||
| 
						 | 
					562ceab13d | ||
| 
						 | 
					2f0f6578c3 | ||
| 
						 | 
					30cbd4e0d6 | ||
| 
						 | 
					549e58069c | ||
| 
						 | 
					7594be85ff | ||
| 
						 | 
					3630034609 | ||
| 
						 | 
					4e01501bbf | ||
| 
						 | 
					1aa5172f56 | ||
| 
						 | 
					f7e2ee8fa6 | ||
| 
						 | 
					66dc9a3701 | ||
| 
						 | 
					31bd39256b | ||
| 
						 | 
					003c69a84b | ||
| 
						 | 
					0134901108 | ||
| 
						 | 
					eee6293d57 | ||
| 
						 | 
					8237bec4f0 | ||
| 
						 | 
					29cad7ad13 | ||
| 
						 | 
					0d103de3b0 | ||
| 
						 | 
					a0090691d0 | ||
| 
						 | 
					6c87c2eea8 | ||
| 
						 | 
					58c2ec6ab3 | ||
| 
						 | 
					df5ae3eb16 | ||
| 
						 | 
					efda2d7854 | ||
| 
						 | 
					e143f5dae9 | ||
| 
						 | 
					48218cdb97 | ||
| 
						 | 
					e9fade72f3 | ||
| 
						 | 
					0f2c0d335b | ||
| 
						 | 
					40b077bc7e | ||
| 
						 | 
					a931092cb3 | ||
| 
						 | 
					bd3749ed69 | ||
| 
						 | 
					4ffbf77886 | ||
| 
						 | 
					781a7ef60a | ||
| 
						 | 
					5b2949ee0b | ||
| 
						 | 
					a0d646135a | ||
| 
						 | 
					7862ad88b7 | ||
| 
						 | 
					f3bff94cf9 | ||
| 
						 | 
					0eba1e1782 | ||
| 
						 | 
					e3216b82bf | ||
| 
						 | 
					da419e2332 | ||
| 
						 | 
					0d97ef43be | ||
| 
						 | 
					1a2313a6f2 | ||
| 
						 | 
					250a9bdfe2 | ||
| 
						 | 
					6317a3e9da | ||
| 
						 | 
					7ab7c9e932 | ||
| 
						 | 
					e129c5bc0d | ||
| 
						 | 
					2e241242a3 | ||
| 
						 | 
					9724e5d336 | ||
| 
						 | 
					63a562f95e | ||
| 
						 | 
					5c340b0387 | ||
| 
						 | 
					1c6510f57a | ||
| 
						 | 
					2a15a98a6a | ||
| 
						 | 
					72a406e7aa | ||
| 
						 | 
					feccc3ff37 | ||
| 
						 | 
					265bfa2c79 | ||
| 
						 | 
					8faf9b9b41 | ||
| 
						 | 
					84be7c230c | ||
| 
						 | 
					3e675fabe0 | ||
| 
						 | 
					cd5b4b0bc2 | ||
| 
						 | 
					7ef822021b | ||
| 
						 | 
					9a48926a57 | ||
| 
						 | 
					13cd97f3df | ||
| 
						 | 
					183139340b | ||
| 
						 | 
					1c69bca258 | ||
| 
						 | 
					c10ea454dc | ||
| 
						 | 
					9504fc21b5 | ||
| 
						 | 
					13d8fbef30 | ||
| 
						 | 
					b8988b63a6 | ||
| 
						 | 
					5eaaeb7c31 | ||
| 
						 | 
					c4f8c453ae | ||
| 
						 | 
					6f4ba54079 | ||
| 
						 | 
					637570326b | ||
| 
						 | 
					37f885650c | ||
| 
						 | 
					c8c34ccb20 | ||
| 
						 | 
					e765ed3a9c | ||
| 
						 | 
					677063594e | ||
| 
						 | 
					59c7cbd482 | ||
| 
						 | 
					570311610e | ||
| 
						 | 
					41b264e77c | ||
| 
						 | 
					df4bd0d53f | ||
| 
						 | 
					7f09a662a0 | ||
| 
						 | 
					4f3b21e1c7 | ||
| 
						 | 
					54233c9080 | ||
| 
						 | 
					db8e13ef71 | ||
| 
						 | 
					5a42414b9c | ||
| 
						 | 
					9c665ab72e | ||
| 
						 | 
					b665ba6aa6 | ||
| 
						 | 
					ec5913b5cd | ||
| 
						 | 
					25ac63ed71 | ||
| 
						 | 
					99209c2916 | ||
| 
						 | 
					1fbaa0a521 | ||
| 
						 | 
					3037b91e05 | ||
| 
						 | 
					ffdf972b91 | ||
| 
						 | 
					459e5fbd5f | ||
| 
						 | 
					bfc993cc91 | ||
| 
						 | 
					4432db35d9 | ||
| 
						 | 
					591ab1dff9 | ||
| 
						 | 
					5bca2424bc | ||
| 
						 | 
					bd61a9e770 | ||
| 
						 | 
					3438e7acd2 | ||
| 
						 | 
					09c200acf2 | ||
| 
						 | 
					716889cab1 | ||
| 
						 | 
					409693984f | ||
| 
						 | 
					04e8c11080 | ||
| 
						 | 
					80af2b73ab | ||
| 
						 | 
					3cc57f9645 | ||
| 
						 | 
					a65d4e7f14 | ||
| 
						 | 
					b531cfc019 | ||
| 
						 | 
					543ec2136b | ||
| 
						 | 
					93b5071f73 | ||
| 
						 | 
					ddc369f073 | ||
| 
						 | 
					1b40dc92eb | ||
| 
						 | 
					fcc3e6138b | ||
| 
						 | 
					9fe6ef7ab2 | ||
| 
						 | 
					c010af6f19 | ||
| 
						 | 
					35b7982303 | ||
| 
						 | 
					f311cfa231 | ||
| 
						 | 
					80970e531b | ||
| 
						 | 
					b7bb76df05 | ||
| 
						 | 
					98c70d6fc7 | ||
| 
						 | 
					ab84349b16 | ||
| 
						 | 
					03091e372f | ||
| 
						 | 
					4d17184817 | ||
| 
						 | 
					e086e0eb6c | ||
| 
						 | 
					314368c822 | ||
| 
						 | 
					c5181ab410 | ||
| 
						 | 
					ea5152cae1 | ||
| 
						 | 
					255fca5eea | ||
| 
						 | 
					4aeccadf4e | ||
| 
						 | 
					93540ee10e | ||
| 
						 | 
					8fb3ac3649 | ||
| 
						 | 
					77b2986b5b | ||
| 
						 | 
					62b013df0d | ||
| 
						 | 
					fad6768bd1 | ||
| 
						 | 
					a78125f925 | ||
| 
						 | 
					a00a8bcc8a | ||
| 
						 | 
					1e9a9e167d | ||
| 
						 | 
					3da0db62e6 | ||
| 
						 | 
					e14ced7918 | ||
| 
						 | 
					ab9d02f53b | ||
| 
						 | 
					a461a11989 | ||
| 
						 | 
					1bd838608f | ||
| 
						 | 
					365577f567 | ||
| 
						 | 
					50efb383f0 | ||
| 
						 | 
					5da6bd0083 | ||
| 
						 | 
					5e9a033e6e | ||
| 
						 | 
					fb7cb6823e | ||
| 
						 | 
					dd0a58f5f0 | ||
| 
						 | 
					a21420389e | ||
| 
						 | 
					6140baf4e1 | ||
| 
						 | 
					8fc642eb5b | ||
| 
						 | 
					e66e1a0046 | ||
| 
						 | 
					d5c69f1da4 | ||
| 
						 | 
					f13b1e7d7f | ||
| 
						 | 
					5c8a3f862a | ||
| 
						 | 
					8807f1277f | ||
| 
						 | 
					a3b9157f49 | ||
| 
						 | 
					b88ba05356 | ||
| 
						 | 
					b74d505577 | ||
| 
						 | 
					9e2d7dca87 | ||
| 
						 | 
					d236b37ac9 | ||
| 
						 | 
					e880c66bd8 | ||
| 
						 | 
					383456aa29 | ||
| 
						 | 
					1a13940c8d | ||
| 
						 | 
					3d54788495 | ||
| 
						 | 
					71d53ace2f | ||
| 
						 | 
					f37e3f99f0 | ||
| 
						 | 
					bd03ffc16e | ||
| 
						 | 
					1ac1af9b47 | ||
| 
						 | 
					3bf5705316 | ||
| 
						 | 
					1c2528c8a3 | ||
| 
						 | 
					7bd15b1a03 | ||
| 
						 | 
					6b961a85fd | ||
| 
						 | 
					7707004043 | ||
| 
						 | 
					a025d3c5a5 | ||
| 
						 | 
					c460bdd56b | ||
| 
						 | 
					b81a359eb6 | ||
| 
						 | 
					d61aefb24c | ||
| 
						 | 
					d305dd73a3 | ||
| 
						 | 
					93a16ba238 | ||
| 
						 | 
					4f7cea6c53 | ||
| 
						 | 
					afbdd3acc3 | ||
| 
						 | 
					85d5866177 | ||
| 
						 | 
					9789d7535d | ||
| 
						 | 
					d8443cd3f7 | ||
| 
						 | 
					d47c26e168 | ||
| 
						 | 
					01561da142 | ||
| 
						 | 
					0af25f784b | ||
| 
						 | 
					b9b42f2ea0 | ||
| 
						 | 
					311c393838 | ||
| 
						 | 
					18c1c42405 | ||
| 
						 | 
					37dd5d4629 | ||
| 
						 | 
					81975f4693 | ||
| 
						 | 
					b8b928d5cb | ||
| 
						 | 
					3eff81fbf7 | ||
| 
						 | 
					785521bf4f | ||
| 
						 | 
					6d1a55a521 | ||
| 
						 | 
					9cad27008b | ||
| 
						 | 
					11e611a7fa | ||
| 
						 | 
					72c1f8de06 | ||
| 
						 | 
					6e99868e4c | ||
| 
						 | 
					4d278fde64 | ||
| 
						 | 
					f21e915fb9 | ||
| 
						 | 
					6f53c63df6 | ||
| 
						 | 
					1def5f359e | ||
| 
						 | 
					15ec669374 | ||
| 
						 | 
					a3fa5da496 | ||
| 
						 | 
					30965ac66a | ||
| 
						 | 
					09ab40b7d1 | ||
| 
						 | 
					edab9dbf4d | ||
| 
						 | 
					9868ea4936 | ||
| 
						 | 
					85920dd01d | ||
| 
						 | 
					fa15607773 | ||
| 
						 | 
					a91a2c1a83 | ||
| 
						 | 
					16e7711e22 | ||
| 
						 | 
					5cda4eda72 | ||
| 
						 | 
					98f000409f | ||
| 
						 | 
					bd7fe0cf66 | ||
| 
						 | 
					48246541da | ||
| 
						 | 
					4a8d4a53b1 | ||
| 
						 | 
					4cd95bcbc3 | ||
| 
						 | 
					be24c8697f | ||
| 
						 | 
					0d93378887 | ||
| 
						 | 
					4069766c52 | ||
| 
						 | 
					7010577720 | ||
| 
						 | 
					8ac27a68e6 | ||
| 
						 | 
					46312e0b46 | ||
| 
						 | 
					f9216ed6ad | ||
| 
						 | 
					65bf37ef83 | ||
| 
						 | 
					f740fae2a4 | ||
| 
						 | 
					fbc503d696 | ||
| 
						 | 
					662435f728 | ||
| 
						 | 
					163d966707 | ||
| 
						 | 
					85729c51af | ||
| 
						 | 
					360e1ca5cc | ||
| 
						 | 
					a1f2a06b34 | ||
| 
						 | 
					c84dd8a90d | ||
| 
						 | 
					65469a7f8b | ||
| 
						 | 
					6b597516c1 | ||
| 
						 | 
					b5857f62e2 | ||
| 
						 | 
					a504ced097 | ||
| 
						 | 
					1db5fbcfe3 | ||
| 
						 | 
					59b8ab5834 | ||
| 
						 | 
					a568180441 | ||
| 
						 | 
					85e80f71cd | ||
| 
						 | 
					bfa6bdcd8b | ||
| 
						 | 
					03cd72b007 | ||
| 
						 | 
					5bfd430f81 | ||
| 
						 | 
					73fac4e911 | ||
| 
						 | 
					8fb474fb17 | ||
| 
						 | 
					f813928e4b | ||
| 
						 | 
					b9c7a97318 | ||
| 
						 | 
					9fb2f1cd6d | ||
| 
						 | 
					6ca7732d5e | ||
| 
						 | 
					b0ab0fac49 | ||
| 
						 | 
					a294bce82f | ||
| 
						 | 
					76d1466b08 | ||
| 
						 | 
					1888d3f7b3 | ||
| 
						 | 
					c2787701cc | ||
| 
						 | 
					52e1d0ccc4 | ||
| 
						 | 
					10e3c4c221 | ||
| 
						 | 
					68f2d273bf | ||
| 
						 | 
					7c86c21662 | ||
| 
						 | 
					ae1580d790 | ||
| 
						 | 
					3215c50f25 | ||
| 
						 | 
					36f73e8044 | ||
| 
						 | 
					a4f3d779db | ||
| 
						 | 
					d9aa2b784d | ||
| 
						 | 
					cffcbc02de | ||
| 
						 | 
					9347fddbfc | ||
| 
						 | 
					037e9437e4 | ||
| 
						 | 
					36e7a4ca2e | ||
| 
						 | 
					ae6423d704 | ||
| 
						 | 
					7105440cec | ||
| 
						 | 
					c80b9cd280 | ||
| 
						 | 
					171ca612af | ||
| 
						 | 
					c3d64fc1b3 | ||
| 
						 | 
					7c24ce225d | ||
| 
						 | 
					08b38d5401 | ||
| 
						 | 
					024c53694d | ||
| 
						 | 
					7e6011101f | ||
| 
						 | 
					c40feaba77 | ||
| 
						 | 
					5277f09dfc | ||
| 
						 | 
					2d30521ab9 | ||
| 
						 | 
					050fa43561 | ||
| 
						 | 
					f36f92f4da | ||
| 
						 | 
					124f3bc67d | ||
| 
						 | 
					d304209a85 | ||
| 
						 | 
					8367d3f3cb | ||
| 
						 | 
					c56d7d899d | ||
| 
						 | 
					ea5db8469e | ||
| 
						 | 
					3811c567e7 | ||
| 
						 | 
					8708d76425 | ||
| 
						 | 
					054fe3cc40 | ||
| 
						 | 
					af0d11f244 | ||
| 
						 | 
					9650885be9 | ||
| 
						 | 
					596ac6e31f | ||
| 
						 | 
					612ee37365 | ||
| 
						 | 
					442c37b7a9 | ||
| 
						 | 
					04bbe41330 | ||
| 
						 | 
					8f84f57183 | ||
| 
						 | 
					6a78740211 | ||
| 
						 | 
					c0e1a415fd | ||
| 
						 | 
					bf8f082a90 | ||
| 
						 | 
					2f543a2142 | ||
| 
						 | 
					7e5db8c930 | ||
| 
						 | 
					f7a211dcc8 | ||
| 
						 | 
					845734773d | ||
| 
						 | 
					347de4931c | ||
| 
						 | 
					8829650513 | ||
| 
						 | 
					c73fae1e2e | ||
| 
						 | 
					834bf069d2 | ||
| 
						 | 
					c06a9fa34f | ||
| 
						 | 
					753fad4adc | ||
| 
						 | 
					34814eb66e | ||
| 
						 | 
					3a5bcd0326 | ||
| 
						 | 
					99c2398bc6 | ||
| 
						 | 
					28f1272870 | ||
| 
						 | 
					f18e3a2fc0 | ||
| 
						 | 
					c4c5dc27cb | ||
| 
						 | 
					2caf182f37 | ||
| 
						 | 
					43f244b6d5 | ||
| 
						 | 
					1309b396d0 | ||
| 
						 | 
					ba61796458 | ||
| 
						 | 
					3255fe7141 | ||
| 
						 | 
					e98b8e79ea | ||
| 
						 | 
					196121c51b | ||
| 
						 | 
					5269028951 | ||
| 
						 | 
					f7bc056b5a | ||
| 
						 | 
					a0f7198544 | ||
| 
						 | 
					dd8930684e | ||
| 
						 | 
					bdb186f3b0 | ||
| 
						 | 
					64f9baa084 | ||
| 
						 | 
					b29231c040 | ||
| 
						 | 
					6128bf07a9 | ||
| 
						 | 
					2ec19e9558 | ||
| 
						 | 
					9ddb6925bf | ||
| 
						 | 
					12931e1c6e | ||
| 
						 | 
					41c23b0da5 | ||
| 
						 | 
					2578ab19e4 | ||
| 
						 | 
					d87ec897e9 | ||
| 
						 | 
					3bd4bffb1c | ||
| 
						 | 
					c36b09a502 | ||
| 
						 | 
					641eb10d34 | ||
| 
						 | 
					955c5505e7 | ||
| 
						 | 
					69319969de | ||
| 
						 | 
					a14292e848 | ||
| 
						 | 
					5d678df64a | ||
| 
						 | 
					8ca8cbe2bd | ||
| 
						 | 
					ba322d8209 | ||
| 
						 | 
					2f38289b79 | ||
| 
						 | 
					f23a3ca699 | ||
| 
						 | 
					77d2b106cc | ||
| 
						 | 
					c0e46412e9 | ||
| 
						 | 
					0161353d7d | ||
| 
						 | 
					2b4ecde2c8 | ||
| 
						 | 
					b3a286d69d | ||
| 
						 | 
					467d3c9a0c | ||
| 
						 | 
					ad5747bad1 | ||
| 
						 | 
					d6eb66ed3c | ||
| 
						 | 
					7f2a9f1b49 | ||
| 
						 | 
					1e1896f2de | ||
| 
						 | 
					c831973366 | ||
| 
						 | 
					1a2548d9e9 | ||
| 
						 | 
					3900eec27c | ||
| 
						 | 
					a02d212638 | ||
| 
						 | 
					9c91a8fa70 | ||
| 
						 | 
					41469f335e | ||
| 
						 | 
					67ce4f8820 | ||
| 
						 | 
					bc63d56cca | ||
| 
						 | 
					c893d70805 | ||
| 
						 | 
					3ee6e02564 | ||
| 
						 | 
					e3aaace400 | ||
| 
						 | 
					300753a069 | ||
| 
						 | 
					f13b88c616 | ||
| 
						 | 
					60ca389c64 | ||
| 
						 | 
					1b0f3919c1 | ||
| 
						 | 
					6a348cf7d5 | ||
| 
						 | 
					9e91449c8d | ||
| 
						 | 
					25e5ebf382 | ||
| 
						 | 
					7dfc356625 | ||
| 
						 | 
					58ba6c0160 | ||
| 
						 | 
					f076b63821 | ||
| 
						 | 
					12f0454cd6 | ||
| 
						 | 
					cd7342755f | ||
| 
						 | 
					9bb8e0a3f9 | ||
| 
						 | 
					1a6373ef39 | ||
| 
						 | 
					f6c24009be | ||
| 
						 | 
					d862042301 | ||
| 
						 | 
					23d9ded655 | ||
| 
						 | 
					4c1a017e69 | ||
| 
						 | 
					ee623d9247 | ||
| 
						 | 
					330537d08a | ||
| 
						 | 
					2cf0ecac7b | ||
| 
						 | 
					d200b11c7e | ||
| 
						 | 
					d0eca21021 | ||
| 
						 | 
					c1147c05e1 | ||
| 
						 | 
					55898ad2cf | ||
| 
						 | 
					a465808592 | ||
| 
						 | 
					5c4862bad4 | ||
| 
						 | 
					995029a142 | ||
| 
						 | 
					a57b562cff | ||
| 
						 | 
					531572578e | ||
| 
						 | 
					3a4cca687f | ||
| 
						 | 
					7d3d06a16c | ||
| 
						 | 
					c21b1fbeeb | ||
| 
						 | 
					f920ce295e | ||
| 
						 | 
					7a7bd19c45 | ||
| 
						 | 
					8f4b58d70e | ||
| 
						 | 
					3fd45e03bf | ||
| 
						 | 
					869b4aeff4 | ||
| 
						 | 
					cc9ca3ba6e | ||
| 
						 | 
					ea71034bd3 | ||
| 
						 | 
					9fffd0469f | ||
| 
						 | 
					ae7773942e | ||
| 
						 | 
					469a64cebf | ||
| 
						 | 
					aae3fdcfae | ||
| 
						 | 
					6a66904f8e | ||
| 
						 | 
					78271e3319 | ||
| 
						 | 
					92bf0bcdf8 | ||
| 
						 | 
					1283204917 | ||
| 
						 | 
					6789defea9 | ||
| 
						 | 
					acf2a6e97b | ||
| 
						 | 
					8cfb6efe6f | ||
| 
						 | 
					04edb9caf5 | ||
| 
						 | 
					044131ba21 | ||
| 
						 | 
					0a7055c90d | ||
| 
						 | 
					9e3f19919a | ||
| 
						 | 
					4a3da4ebdb | ||
| 
						 | 
					027008b14e | ||
| 
						 | 
					c6df692466 | ||
| 
						 | 
					acf757f42e | ||
| 
						 | 
					dd8982f19c | ||
| 
						 | 
					654bd52f58 | ||
| 
						 | 
					a9551e9020 | ||
| 
						 | 
					4e980275b5 | ||
| 
						 | 
					c172440ac5 | ||
| 
						 | 
					e332772531 | ||
| 
						 | 
					437cac8cc1 | ||
| 
						 | 
					9f281cacd2 | ||
| 
						 | 
					748a0fab8a | ||
| 
						 | 
					c1f06d6307 | ||
| 
						 | 
					c4e817ce4a | ||
| 
						 | 
					9a3e5e6955 | ||
| 
						 | 
					228d30ed06 | ||
| 
						 | 
					057c0609fc | ||
| 
						 | 
					17d2712d9c | ||
| 
						 | 
					fc09240e24 | ||
| 
						 | 
					146303136f | ||
| 
						 | 
					96aded8d3d | ||
| 
						 | 
					2886be15aa | ||
| 
						 | 
					ca0f500ecf | ||
| 
						 | 
					29aef5a33c | ||
| 
						 | 
					9158b2b301 | ||
| 
						 | 
					0196149c5b | ||
| 
						 | 
					8f9312c387 | ||
| 
						 | 
					439b9a9e9b | ||
| 
						 | 
					8c72beb25e | ||
| 
						 | 
					1ee94db2d0 | ||
| 
						 | 
					e77d2975af | ||
| 
						 | 
					e41b1f7385 | ||
| 
						 | 
					cd596028d6 | ||
| 
						 | 
					cc57bd33a8 | ||
| 
						 | 
					6d593c3276 | ||
| 
						 | 
					91755ee384 | ||
| 
						 | 
					0692ef86ef | ||
| 
						 | 
					439d9be27d | ||
| 
						 | 
					b80505a409 | ||
| 
						 | 
					e4c17d7274 | ||
| 
						 | 
					2c58674e0e | ||
| 
						 | 
					ef1269fb07 | ||
| 
						 | 
					e525d9a3df | ||
| 
						 | 
					20b4492c71 | ||
| 
						 | 
					dee3f73787 | ||
| 
						 | 
					d543bdc351 | ||
| 
						 | 
					c7ff0c6422 | ||
| 
						 | 
					01c46659c4 | ||
| 
						 | 
					b04b885271 | ||
| 
						 | 
					dc35bfd2d5 | ||
| 
						 | 
					70fca8d694 | ||
| 
						 | 
					a52c633536 | ||
| 
						 | 
					7b6c60393e | ||
| 
						 | 
					83e7a314b4 | ||
| 
						 | 
					749f2ca044 | ||
| 
						 | 
					5468ff4d91 | ||
| 
						 | 
					1d2daaea63 | ||
| 
						 | 
					52585fd6dc | ||
| 
						 | 
					c03844a4ec | ||
| 
						 | 
					6449cd807e | ||
| 
						 | 
					e2a08185c6 | ||
| 
						 | 
					5d6677ca28 | ||
| 
						 | 
					5a8a29cfea | ||
| 
						 | 
					c1708b89c0 | ||
| 
						 | 
					83fddfd493 | ||
| 
						 | 
					1798791df1 | ||
| 
						 | 
					6ebb0dca9f | ||
| 
						 | 
					cf8d6ec865 | ||
| 
						 | 
					f452f72c6b | ||
| 
						 | 
					3198291f26 | ||
| 
						 | 
					02c1d5e285 | ||
| 
						 | 
					ec4161a57d | ||
| 
						 | 
					03d8d4df38 | ||
| 
						 | 
					03d2d6d51b | ||
| 
						 | 
					83fda3c000 | ||
| 
						 | 
					4fe8495a23 | ||
| 
						 | 
					a16f6643f0 | ||
| 
						 | 
					adc0ae3ceb | ||
| 
						 | 
					7bb3ceb4c7 | ||
| 
						 | 
					75a4fc5b72 | ||
| 
						 | 
					87673cd438 | ||
| 
						 | 
					f345fe9db7 | ||
| 
						 | 
					e683a48d0e | ||
| 
						 | 
					a7a14d9586 | ||
| 
						 | 
					219337990b | ||
| 
						 | 
					376a770cc4 | ||
| 
						 | 
					7e500dbd93 | ||
| 
						 | 
					affd04a45d | ||
| 
						 | 
					c84130e865 | ||
| 
						 | 
					4f264c02c7 | ||
| 
						 | 
					d205476103 | ||
| 
						 | 
					367cc95aa7 | ||
| 
						 | 
					206dba27a4 | ||
| 
						 | 
					dcf53d4408 | ||
| 
						 | 
					63be3b8989 | ||
| 
						 | 
					18b4e9e79d | ||
| 
						 | 
					cb454b333d | ||
| 
						 | 
					e0d9f85aee | ||
| 
						 | 
					b04fbd789c | ||
| 
						 | 
					aad9556414 | ||
| 
						 | 
					48a1e5141a | ||
| 
						 | 
					0865f397ae | ||
| 
						 | 
					796df3c631 | ||
| 
						 | 
					a28383834b | ||
| 
						 | 
					3a0d2f520a | ||
| 
						 | 
					6348ad12a0 | ||
| 
						 | 
					fe7710cbcc | ||
| 
						 | 
					2103d038b3 | ||
| 
						 | 
					6ca85be6f8 | ||
| 
						 | 
					9f0df77ab1 | ||
| 
						 | 
					e72c7e4123 | ||
| 
						 | 
					2b1bd292ae | ||
| 
						 | 
					71e7da6533 | ||
| 
						 | 
					80a49d3d7b | ||
| 
						 | 
					d862a4f94f | ||
| 
						 | 
					a57e8ce658 | ||
| 
						 | 
					96a53167fa | ||
| 
						 | 
					6d2749aac4 | ||
| 
						 | 
					b1b0b1ca30 | ||
| 
						 | 
					3dee7826e7 | ||
| 
						 | 
					c9326b38b8 | ||
| 
						 | 
					d4f64cabf4 | ||
| 
						 | 
					fe41ddbb28 | ||
| 
						 | 
					ee69b99af6 | ||
| 
						 | 
					767ff0a2d1 | ||
| 
						 | 
					8604e882a8 | ||
| 
						 | 
					cc1237f484 | ||
| 
						 | 
					37f4ce538a | ||
| 
						 | 
					7d346331b5 | ||
| 
						 | 
					e1ccc04e9f | ||
| 
						 | 
					881e6a1f5c | ||
| 
						 | 
					baeaeffce5 | ||
| 
						 | 
					c14e88f0f5 | ||
| 
						 | 
					8940b8608e | ||
| 
						 | 
					ec82d85acd | ||
| 
						 | 
					cfb56d1af3 | ||
| 
						 | 
					1e10802990 | ||
| 
						 | 
					6695916045 | ||
| 
						 | 
					7906d199a1 | ||
| 
						 | 
					1070711d60 | ||
| 
						 | 
					4b405cfc6e | ||
| 
						 | 
					e5660ee6ae | ||
| 
						 | 
					8011fba3ae | ||
| 
						 | 
					587a9c2749 | ||
| 
						 | 
					e1554a407d | ||
| 
						 | 
					3fcfb8e9fa | ||
| 
						 | 
					384b62028a | ||
| 
						 | 
					b95aab8482 | ||
| 
						 | 
					fc2d6abfe7 | ||
| 
						 | 
					27de5625d4 | ||
| 
						 | 
					6aa4f54d66 | ||
| 
						 | 
					222516d97d | ||
| 
						 | 
					a055469faf | ||
| 
						 | 
					fdaaaaa878 | ||
| 
						 | 
					12d1fb5aa9 | ||
| 
						 | 
					48f00d15b1 | ||
| 
						 | 
					3e055aa5c3 | ||
| 
						 | 
					6896a52721 | ||
| 
						 | 
					5779b3e1fe | ||
| 
						 | 
					62cd676c74 | ||
| 
						 | 
					0c17278843 | ||
| 
						 | 
					d229ee70da | ||
| 
						 | 
					26e274666d | ||
| 
						 | 
					ebd46aed51 | ||
| 
						 | 
					e793f7671c | ||
| 
						 | 
					c2e64f71d0 | ||
| 
						 | 
					0920e5830f | ||
| 
						 | 
					bf7fa94ec7 | ||
| 
						 | 
					6f58db8982 | ||
| 
						 | 
					aa42e87340 | ||
| 
						 | 
					649f7966f7 | ||
| 
						 | 
					5f0d813d93 | ||
| 
						 | 
					501f13fbf3 | ||
| 
						 | 
					ba55168157 | ||
| 
						 | 
					d79323136f | ||
| 
						 | 
					08ff6ab07e | ||
| 
						 | 
					ba655a0e4c | ||
| 
						 | 
					b59c17e543 | ||
| 
						 | 
					61ca9a80b3 | ||
| 
						 | 
					317239b097 | ||
| 
						 | 
					c2a30b250c | ||
| 
						 | 
					c994e6bd63 | ||
| 
						 | 
					3ee2aa7a16 | ||
| 
						 | 
					083c9df93b | ||
| 
						 | 
					50789175ed | ||
| 
						 | 
					dc1b027cd4 | ||
| 
						 | 
					f353cbdb2f | ||
| 
						 | 
					73e449b226 | ||
| 
						 | 
					b4a64c592b | ||
| 
						 | 
					78111136db | ||
| 
						 | 
					650ab5beeb | ||
| 
						 | 
					7932de6352 | ||
| 
						 | 
					240b9b7a5c | ||
| 
						 | 
					bb6e38787d | ||
| 
						 | 
					898c23c03f | ||
| 
						 | 
					b55ee18ff3 | ||
| 
						 | 
					e5763a7a7e | ||
| 
						 | 
					8bb1bdfae9 | ||
| 
						 | 
					c62b449765 | ||
| 
						 | 
					bb0aa4cb3c | ||
| 
						 | 
					d63528c8c7 | ||
| 
						 | 
					c5db6bb32b | ||
| 
						 | 
					c8dc41a6e7 | ||
| 
						 | 
					47e0e1e0e2 | ||
| 
						 | 
					efcddaebe9 | ||
| 
						 | 
					5fe5112589 | ||
| 
						 | 
					564bb5e964 | ||
| 
						 | 
					2df54b4ba8 | ||
| 
						 | 
					030aa5d9e7 | ||
| 
						 | 
					c511f13f22 | ||
| 
						 | 
					bd3cbe0716 | ||
| 
						 | 
					fdb2ed7455 | ||
| 
						 | 
					ba319696a9 | ||
| 
						 | 
					910c552052 | ||
| 
						 | 
					cce81f192c | ||
| 
						 | 
					9d22a7dfb0 | ||
| 
						 | 
					4f4f642822 | ||
| 
						 | 
					2875cf01bb | ||
| 
						 | 
					e205db3bcd | ||
| 
						 | 
					31d4a6e212 | ||
| 
						 | 
					aaeb86f682 | ||
| 
						 | 
					9fa6ea2680 | ||
| 
						 | 
					a9b6b5cd15 | ||
| 
						 | 
					a45c0a5d67 | ||
| 
						 | 
					c8dfe360eb | ||
| 
						 | 
					4cfaf85c65 | ||
| 
						 | 
					be5f2c192c | ||
| 
						 | 
					c9ef44ce29 | ||
| 
						 | 
					e92d4a11f5 | ||
| 
						 | 
					f2cbc96c3e | ||
| 
						 | 
					a69801e2c6 | ||
| 
						 | 
					034206cec1 | ||
| 
						 | 
					04e0bac233 | ||
| 
						 | 
					fbef83f399 | ||
| 
						 | 
					3d5f7a3947 | ||
| 
						 | 
					a5fb718c50 | ||
| 
						 | 
					227d4822ff | ||
| 
						 | 
					5c4a81d934 | ||
| 
						 | 
					263255eb8d | ||
| 
						 | 
					8e2ec95575 | ||
| 
						 | 
					8e7a9016d5 | ||
| 
						 | 
					c85f368370 | ||
| 
						 | 
					a0977064ce | ||
| 
						 | 
					15aecd8711 | ||
| 
						 | 
					20dd0b2d20 | ||
| 
						 | 
					f934860a07 | ||
| 
						 | 
					2aeb06d6dc | ||
| 
						 | 
					6ccbb335d2 | ||
| 
						 | 
					4340decad2 | ||
| 
						 | 
					f3ff1a3696 | ||
| 
						 | 
					aa24de39aa | ||
| 
						 | 
					a798e64c15 | ||
| 
						 | 
					6a5fa75490 | ||
| 
						 | 
					8ad6b5ed9f | ||
| 
						 | 
					d5bb814d34 | ||
| 
						 | 
					d156a1d981 | ||
| 
						 | 
					987493aef3 | ||
| 
						 | 
					8bfa75451b | ||
| 
						 | 
					c071733fd4 | ||
| 
						 | 
					cd3063f3fa | ||
| 
						 | 
					58b1f00d19 | ||
| 
						 | 
					149f05c7b6 | ||
| 
						 | 
					8a1b9b068e | ||
| 
						 | 
					c5a59d9391 | ||
| 
						 | 
					500b8b41c1 | ||
| 
						 | 
					be4a824d74 | ||
| 
						 | 
					ed3958d714 | ||
| 
						 | 
					6ce08764a1 | ||
| 
						 | 
					c80ede5f13 | ||
| 
						 | 
					bc694039e4 | ||
| 
						 | 
					3462af03e6 | ||
| 
						 | 
					ea1d5bdcdd | ||
| 
						 | 
					121c09c7be | ||
| 
						 | 
					76bfaf6daf | ||
| 
						 | 
					d89c6e336a | ||
| 
						 | 
					776dc3992a | ||
| 
						 | 
					27ca82ebc6 | ||
| 
						 | 
					385f8ae468 | ||
| 
						 | 
					b9f030cc26 | ||
| 
						 | 
					52afb2ac1b | ||
| 
						 | 
					43bc88903d | ||
| 
						 | 
					6ef9f88299 | ||
| 
						 | 
					f71fdb0acc | ||
| 
						 | 
					c24dfef63c | ||
| 
						 | 
					6271f1cad9 | ||
| 
						 | 
					fb4b030aaf | ||
| 
						 | 
					ff21a8e0ee | ||
| 
						 | 
					904fffffeb | ||
| 
						 | 
					51897bb77c | ||
| 
						 | 
					bd1a281ede | ||
| 
						 | 
					45598f1578 | ||
| 
						 | 
					d02115f837 | ||
| 
						 | 
					34c781a24d | ||
| 
						 | 
					1302394603 | ||
| 
						 | 
					dd622d7c4e | ||
| 
						 | 
					d120e9013f | ||
| 
						 | 
					b8da6b9fc6 | ||
| 
						 | 
					4baea47c42 | ||
| 
						 | 
					176cf9e0c3 | ||
| 
						 | 
					7b6faddfc8 | ||
| 
						 | 
					f90ad27375 | ||
| 
						 | 
					230b2287dd | ||
| 
						 | 
					754c838903 | ||
| 
						 | 
					aa2fd59857 | ||
| 
						 | 
					9932a65370 | ||
| 
						 | 
					5e4166478d | ||
| 
						 | 
					b0e87c3110 | ||
| 
						 | 
					ff0813313a | ||
| 
						 | 
					c0bdf32a3c | ||
| 
						 | 
					92b065dc53 | ||
| 
						 | 
					9298d4e3df | ||
| 
						 | 
					740a7fcbc8 | ||
| 
						 | 
					5fbf25a681 | ||
| 
						 | 
					db6e625005 | ||
| 
						 | 
					811cacdc2c | ||
| 
						 | 
					ce08a86462 | ||
| 
						 | 
					11497d5bba | ||
| 
						 | 
					0217c78377 | ||
| 
						 | 
					bd6b25ce0e | ||
| 
						 | 
					d51a853d5c | ||
| 
						 | 
					9ed99402f5 | ||
| 
						 | 
					ec3a6a3137 | ||
| 
						 | 
					796858a53f | ||
| 
						 | 
					5b78caca94 | ||
| 
						 | 
					bec2248141 | ||
| 
						 | 
					211503c39f | ||
| 
						 | 
					adb1307b9a | ||
| 
						 | 
					99673f04bc | ||
| 
						 | 
					e9a537774d | ||
| 
						 | 
					367f539769 | ||
| 
						 | 
					398133cf55 | ||
| 
						 | 
					52fc3ba405 | ||
| 
						 | 
					fdd6e18b75 | ||
| 
						 | 
					58a84b8cb6 | ||
| 
						 | 
					c5d666d374 | ||
| 
						 | 
					5d8993b06a | ||
| 
						 | 
					c758bf9fd7 | ||
| 
						 | 
					900813a328 | ||
| 
						 | 
					2bad0e5d20 | ||
| 
						 | 
					ccc5842bc9 | ||
| 
						 | 
					fd86c2026d | ||
| 
						 | 
					e4a8eae701 | ||
| 
						 | 
					75e51819d0 | ||
| 
						 | 
					8ee341500d | ||
| 
						 | 
					0590062925 | ||
| 
						 | 
					799d88d3d8 | ||
| 
						 | 
					760aea9a96 | ||
| 
						 | 
					d6a31b1766 | ||
| 
						 | 
					0b54a5b10a | ||
| 
						 | 
					6309cb9b41 | ||
| 
						 | 
					27a82a1b93 | ||
| 
						 | 
					ecd1936695 | ||
| 
						 | 
					76b3c61012 | ||
| 
						 | 
					0df2dea73b | ||
| 
						 | 
					f8bb576c4f | ||
| 
						 | 
					ee61f6f3e2 | ||
| 
						 | 
					f14f2a6d79 | ||
| 
						 | 
					2c322cc5d6 | ||
| 
						 | 
					3b8f3a1504 | ||
| 
						 | 
					8f9529cd05 | ||
| 
						 | 
					f4bca0b348 | ||
| 
						 | 
					6291438073 | ||
| 
						 | 
					18c3c15391 | ||
| 
						 | 
					dda620e88c | ||
| 
						 | 
					d7cc31b63e | ||
| 
						 | 
					5e3e1c82d8 | ||
| 
						 | 
					aa80652f47 | ||
| 
						 | 
					9d247bbd2d | ||
| 
						 | 
					93e40a7b2f | ||
| 
						 | 
					03ff2cc1c4 | ||
| 
						 | 
					a285b6377b | ||
| 
						 | 
					cd791a5ea0 | ||
| 
						 | 
					87830900a9 | ||
| 
						 | 
					dfc9d9f50a | ||
| 
						 | 
					75311a7e16 | ||
| 
						 | 
					628bc4d1e7 | ||
| 
						 | 
					a4c3f48639 | ||
| 
						 | 
					bdf80aa542 | ||
| 
						 | 
					adf3c58ad3 | ||
| 
						 | 
					caf90bfaa5 | ||
| 
						 | 
					2f985f4bb4 | ||
| 
						 | 
					67c2bcdf4c | ||
| 
						 | 
					1d2d0e3ff2 | ||
| 
						 | 
					9fda6ee39f | ||
| 
						 | 
					bc3e582fe4 | ||
| 
						 | 
					bc1fc5ddbc | ||
| 
						 | 
					63948fc62c | ||
| 
						 | 
					f4858a7103 | ||
| 
						 | 
					26886e6140 | ||
| 
						 | 
					7a1818c99b | ||
| 
						 | 
					2ccd1b10e5 | ||
| 
						 | 
					788fa208c8 | ||
| 
						 | 
					8848314c08 | ||
| 
						 | 
					c11125f9ed | ||
| 
						 | 
					95ceeec722 | ||
| 
						 | 
					b68ff25917 | ||
| 
						 | 
					3e3327ea17 | ||
| 
						 | 
					b158bb8693 | ||
| 
						 | 
					2bf098eda4 | ||
| 
						 | 
					382e05fa56 | ||
| 
						 | 
					19b05d886e | ||
| 
						 | 
					e65566a9cc | ||
| 
						 | 
					baa3c3f0f6 | ||
| 
						 | 
					f4f339529c | ||
| 
						 | 
					7d02fae85b | ||
| 
						 | 
					6e46c3f1fd | ||
| 
						 | 
					c7e675940c | ||
| 
						 | 
					d26b1317ed | ||
| 
						 | 
					a221f22969 | ||
| 
						 | 
					817f786fbb | ||
| 
						 | 
					62420c73cb | ||
| 
						 | 
					2522a0b7da | ||
| 
						 | 
					46d32a12c9 | ||
| 
						 | 
					c491418526 | ||
| 
						 | 
					c067545c17 | ||
| 
						 | 
					823a155293 | ||
| 
						 | 
					324b2c78fa | ||
| 
						 | 
					d34f98289b | ||
| 
						 | 
					644096b15c | ||
| 
						 | 
					15cebcc363 | ||
| 
						 | 
					faa4ea68c0 | ||
| 
						 | 
					29a9385ff0 | ||
| 
						 | 
					476eae0c2a | ||
| 
						 | 
					8399267671 | ||
| 
						 | 
					db546cf87f | ||
| 
						 | 
					317639758a | ||
| 
						 | 
					fdbabca85f | ||
| 
						 | 
					6f790e5821 | ||
| 
						 | 
					6f5cdeb611 | ||
| 
						 | 
					9eb4f404cb | ||
| 
						 | 
					f58487b392 | ||
| 
						 | 
					5b9aefef77 | ||
| 
						 | 
					defaf19f5d | ||
| 
						 | 
					772fd5cc44 | ||
| 
						 | 
					50a0f6df7e | ||
| 
						 | 
					9f435c5f1c | ||
| 
						 | 
					931e2d1d26 | ||
| 
						 | 
					a42419da42 | ||
| 
						 | 
					9a237b776c | ||
| 
						 | 
					02ec32a1ef | ||
| 
						 | 
					a1e9e6440f | ||
| 
						 | 
					5878e6398c | ||
| 
						 | 
					6c6f1408f2 | ||
| 
						 | 
					b7a7319c38 | ||
| 
						 | 
					68f705cac5 | ||
| 
						 | 
					079d1dcd80 | ||
| 
						 | 
					7b24bbdf49 | ||
| 
						 | 
					f86d543ebb | ||
| 
						 | 
					60e47a2699 | ||
| 
						 | 
					b8bc7a696b | ||
| 
						 | 
					7d900ef1bf | ||
| 
						 | 
					1931a73f39 | ||
| 
						 | 
					966ea3aebd | ||
| 
						 | 
					b3013681ff | ||
| 
						 | 
					416c7fcbce | ||
| 
						 | 
					e83eebb12f | ||
| 
						 | 
					a349873226 | ||
| 
						 | 
					fccae2b911 | ||
| 
						 | 
					3ee08848db | ||
| 
						 | 
					0129b4dd45 | ||
| 
						 | 
					1c57e7f1f4 | ||
| 
						 | 
					d0caf3a11e | ||
| 
						 | 
					a87bb090d9 | ||
| 
						 | 
					beb95e7781 | ||
| 
						 | 
					5435d7af91 | ||
| 
						 | 
					0c0a70f4c6 | ||
| 
						 | 
					e3947e2b7f | ||
| 
						 | 
					da3f7fb7f8 | ||
| 
						 | 
					429ddfd38d | ||
| 
						 | 
					479514d015 | ||
| 
						 | 
					355e41466d | ||
| 
						 | 
					03d9aad87c | ||
| 
						 | 
					3e2bcf530b | ||
| 
						 | 
					6343a5f68e | ||
| 
						 | 
					00de9a9828 | ||
| 
						 | 
					7fc2cd819e | ||
| 
						 | 
					974739aab5 | ||
| 
						 | 
					0cc4f8e385 | ||
| 
						 | 
					513fd2a872 | ||
| 
						 | 
					ae6986fb89 | ||
| 
						 | 
					e8e28989eb | ||
| 
						 | 
					0fa629d05b | ||
| 
						 | 
					ff7a07d5c4 | ||
| 
						 | 
					5a18403057 | ||
| 
						 | 
					1b7b1d6eac | ||
| 
						 | 
					23cfa4ae45 | ||
| 
						 | 
					e82def52a9 | ||
| 
						 | 
					bcfe9db299 | ||
| 
						 | 
					cf00ae7640 | ||
| 
						 | 
					f9b9e88646 | ||
| 
						 | 
					c2500434c3 | ||
| 
						 | 
					f74b341dde | ||
| 
						 | 
					461b00f34a | ||
| 
						 | 
					4cda41ac7b | ||
| 
						 | 
					6a1c4fbfcb | ||
| 
						 | 
					31424c126f | ||
| 
						 | 
					53096539dc | ||
| 
						 | 
					2c0b475235 | ||
| 
						 | 
					a542405200 | ||
| 
						 | 
					3e2b085ef9 | ||
| 
						 | 
					885e4384a1 | ||
| 
						 | 
					2b8f151094 | ||
| 
						 | 
					5ac71f0b27 | ||
| 
						 | 
					39ac7c9435 | ||
| 
						 | 
					ed7bdc8a90 | ||
| 
						 | 
					55f0cab3a3 | ||
| 
						 | 
					544dec6298 | ||
| 
						 | 
					e0ae1814b1 | ||
| 
						 | 
					9532d72371 | ||
| 
						 | 
					1362bbbb4b | ||
| 
						 | 
					f00fd51dae | ||
| 
						 | 
					a8896c5ac2 | ||
| 
						 | 
					5d3808524d | ||
| 
						 | 
					c8f167823f | ||
| 
						 | 
					70f6796e7d | ||
| 
						 | 
					85d253af6b | ||
| 
						 | 
					a86cbf5876 | ||
| 
						 | 
					3f1399de8a | ||
| 
						 | 
					1f809a8560 | ||
| 
						 | 
					653d14e2f9 | ||
| 
						 | 
					85fab7e47b | ||
| 
						 | 
					3aa9176f08 | ||
| 
						 | 
					33b53b6021 | ||
| 
						 | 
					3f7421b71b | ||
| 
						 | 
					ee45625290 | ||
| 
						 | 
					2c2a42587b | ||
| 
						 | 
					e2f65efcf9 | ||
| 
						 | 
					081d6e4784 | ||
| 
						 | 
					1d4247f64e | ||
| 
						 | 
					1ff30d7b79 | ||
| 
						 | 
					16ea817968 | ||
| 
						 | 
					a2a4bae929 | ||
| 
						 | 
					c58843b3a1 | ||
| 
						 | 
					a22524b004 | ||
| 
						 | 
					87c4c21e75 | ||
| 
						 | 
					b9465395cb | ||
| 
						 | 
					edf41477f0 | ||
| 
						 | 
					5f627b4448 | ||
| 
						 | 
					60e5428925 | ||
| 
						 | 
					748ec66725 | ||
| 
						 | 
					e54a3a2f01 | ||
| 
						 | 
					0e4cb4f406 | ||
| 
						 | 
					f7ffe72ac7 | ||
| 
						 | 
					cd58dc3e56 | ||
| 
						 | 
					c33bcf2051 | ||
| 
						 | 
					7642c08763 | ||
| 
						 | 
					fdc8000810 | ||
| 
						 | 
					a91c9b15e3 | ||
| 
						 | 
					27d67ea2ba | ||
| 
						 | 
					d6a8160902 | ||
| 
						 | 
					6e1b9395c6 | ||
| 
						 | 
					b1ccbed3d4 | ||
| 
						 | 
					37381350f8 | ||
| 
						 | 
					7af808a5ef | ||
| 
						 | 
					876bef5937 | ||
| 
						 | 
					a16af51873 | ||
| 
						 | 
					dc9a441bfa | ||
| 
						 | 
					ee6dfe8308 | ||
| 
						 | 
					2cb5b03e53 | ||
| 
						 | 
					964b190350 | ||
| 
						 | 
					13d27a42cc | ||
| 
						 | 
					ec05fee43a | ||
| 
						 | 
					b50e3bc67f | ||
| 
						 | 
					754f0008ec | ||
| 
						 | 
					2415951ead | ||
| 
						 | 
					995ad69c54 | ||
| 
						 | 
					225e4b9633 | ||
| 
						 | 
					6ce2c6783b | ||
| 
						 | 
					29f400b97d | ||
| 
						 | 
					0cd64bd077 | ||
| 
						 | 
					0551a02b82 | ||
| 
						 | 
					25fadd06d0 | ||
| 
						 | 
					7a47d07c6d | ||
| 
						 | 
					34e48bed3b | ||
| 
						 | 
					5a000b45b3 | ||
| 
						 | 
					40b1cbafac | ||
| 
						 | 
					4231235cda | ||
| 
						 | 
					7b61ac3ddf | ||
| 
						 | 
					c816336cbd | ||
| 
						 | 
					ca7a9c1bf7 | ||
| 
						 | 
					247a5da704 | ||
| 
						 | 
					d1b4617e1d | ||
| 
						 | 
					74dcf42a85 | ||
| 
						 | 
					a42c921598 | ||
| 
						 | 
					f96252b913 | ||
| 
						 | 
					04b89c9026 | ||
| 
						 | 
					0c72eb9060 | ||
| 
						 | 
					f9f86b0c64 | ||
| 
						 | 
					0aed8df2bf | ||
| 
						 | 
					2f61fe4ccc | ||
| 
						 | 
					03359e9864 | 
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -31,3 +31,5 @@ updates_key.pem
 | 
				
			|||||||
test/testdata
 | 
					test/testdata
 | 
				
			||||||
.tox
 | 
					.tox
 | 
				
			||||||
youtube-dl.zsh
 | 
					youtube-dl.zsh
 | 
				
			||||||
 | 
					.idea
 | 
				
			||||||
 | 
					.idea/*
 | 
				
			||||||
@@ -4,12 +4,14 @@ python:
 | 
				
			|||||||
  - "2.7"
 | 
					  - "2.7"
 | 
				
			||||||
  - "3.3"
 | 
					  - "3.3"
 | 
				
			||||||
  - "3.4"
 | 
					  - "3.4"
 | 
				
			||||||
 | 
					before_install:
 | 
				
			||||||
 | 
					  - sudo apt-get update -qq
 | 
				
			||||||
 | 
					  - sudo apt-get install -yqq rtmpdump
 | 
				
			||||||
script: nosetests test --verbose
 | 
					script: nosetests test --verbose
 | 
				
			||||||
notifications:
 | 
					notifications:
 | 
				
			||||||
  email:
 | 
					  email:
 | 
				
			||||||
    - filippo.valsorda@gmail.com
 | 
					    - filippo.valsorda@gmail.com
 | 
				
			||||||
    - phihag@phihag.de
 | 
					    - phihag@phihag.de
 | 
				
			||||||
    - jaime.marquinez.ferrandiz+travis@gmail.com
 | 
					 | 
				
			||||||
    - yasoob.khld@gmail.com
 | 
					    - yasoob.khld@gmail.com
 | 
				
			||||||
#  irc:
 | 
					#  irc:
 | 
				
			||||||
#    channels:
 | 
					#    channels:
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										20
									
								
								AUTHORS
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								AUTHORS
									
									
									
									
									
								
							@@ -93,3 +93,23 @@ Zack Fernandes
 | 
				
			|||||||
cryptonaut
 | 
					cryptonaut
 | 
				
			||||||
Adrian Kretz
 | 
					Adrian Kretz
 | 
				
			||||||
Mathias Rav
 | 
					Mathias Rav
 | 
				
			||||||
 | 
					Petr Kutalek
 | 
				
			||||||
 | 
					Will Glynn
 | 
				
			||||||
 | 
					Max Reimann
 | 
				
			||||||
 | 
					Cédric Luthi
 | 
				
			||||||
 | 
					Thijs Vermeir
 | 
				
			||||||
 | 
					Joel Leclerc
 | 
				
			||||||
 | 
					Christopher Krooss
 | 
				
			||||||
 | 
					Ondřej Caletka
 | 
				
			||||||
 | 
					Dinesh S
 | 
				
			||||||
 | 
					Johan K. Jensen
 | 
				
			||||||
 | 
					Yen Chi Hsuan
 | 
				
			||||||
 | 
					Enam Mijbah Noor
 | 
				
			||||||
 | 
					David Luhmer
 | 
				
			||||||
 | 
					Shaya Goldberg
 | 
				
			||||||
 | 
					Paul Hartmann
 | 
				
			||||||
 | 
					Frans de Jonge
 | 
				
			||||||
 | 
					Robin de Rooij
 | 
				
			||||||
 | 
					Ryan Schmidt
 | 
				
			||||||
 | 
					Leslie P. Polzer
 | 
				
			||||||
 | 
					Duncan Keall
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,6 @@
 | 
				
			|||||||
Please include the full output of the command when run with `--verbose`. The output (including the first lines) contain important debugging information. Issues without the full output are often not reproducible and therefore do not get solved in short order, if ever.
 | 
					**Please include the full output of youtube-dl when run with `-v`**.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The output (including the first lines) contain important debugging information. Issues without the full output are often not reproducible and therefore do not get solved in short order, if ever.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Please re-read your issue once again to avoid a couple of common mistakes (you can and should use this as a checklist):
 | 
					Please re-read your issue once again to avoid a couple of common mistakes (you can and should use this as a checklist):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -44,7 +46,7 @@ In particular, every site support request issue should only pertain to services
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
###  Is anyone going to need the feature?
 | 
					###  Is anyone going to need the feature?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Only post features that you (or an incapicated friend you can personally talk to) require. Do not post features because they seem like a good idea. If they are really useful, they will be requested by someone who requires them.
 | 
					Only post features that you (or an incapacitated friend you can personally talk to) require. Do not post features because they seem like a good idea. If they are really useful, they will be requested by someone who requires them.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
###  Is your question about youtube-dl?
 | 
					###  Is your question about youtube-dl?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -122,7 +124,7 @@ If you want to add support for a new site, you can follow this quick list (assum
 | 
				
			|||||||
5. Add an import in [`youtube_dl/extractor/__init__.py`](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/__init__.py).
 | 
					5. Add an import in [`youtube_dl/extractor/__init__.py`](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/__init__.py).
 | 
				
			||||||
6. Run `python test/test_download.py TestDownload.test_YourExtractor`. This *should fail* at first, but you can continually re-run it until you're done. If you decide to add more than one test, then rename ``_TEST`` to ``_TESTS`` and make it into a list of dictionaries. The tests will be then be named `TestDownload.test_YourExtractor`, `TestDownload.test_YourExtractor_1`, `TestDownload.test_YourExtractor_2`, etc.
 | 
					6. Run `python test/test_download.py TestDownload.test_YourExtractor`. This *should fail* at first, but you can continually re-run it until you're done. If you decide to add more than one test, then rename ``_TEST`` to ``_TESTS`` and make it into a list of dictionaries. The tests will be then be named `TestDownload.test_YourExtractor`, `TestDownload.test_YourExtractor_1`, `TestDownload.test_YourExtractor_2`, etc.
 | 
				
			||||||
7. Have a look at [`youtube_dl/common/extractor/common.py`](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/common.py) for possible helper methods and a [detailed description of what your extractor should return](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/common.py#L38). Add tests and code for as many as you want.
 | 
					7. Have a look at [`youtube_dl/common/extractor/common.py`](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/common.py) for possible helper methods and a [detailed description of what your extractor should return](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/common.py#L38). Add tests and code for as many as you want.
 | 
				
			||||||
8. If you can, check the code with [pyflakes](https://pypi.python.org/pypi/pyflakes) (a good idea) and [pep8](https://pypi.python.org/pypi/pep8) (optional, ignore E501).
 | 
					8. If you can, check the code with [flake8](https://pypi.python.org/pypi/flake8).
 | 
				
			||||||
9. When the tests pass, [add](http://git-scm.com/docs/git-add) the new files and [commit](http://git-scm.com/docs/git-commit) them and [push](http://git-scm.com/docs/git-push) the result, like this:
 | 
					9. When the tests pass, [add](http://git-scm.com/docs/git-add) the new files and [commit](http://git-scm.com/docs/git-commit) them and [push](http://git-scm.com/docs/git-push) the result, like this:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $ git add youtube_dl/extractor/__init__.py
 | 
					        $ git add youtube_dl/extractor/__init__.py
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										24
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								Makefile
									
									
									
									
									
								
							@@ -1,10 +1,8 @@
 | 
				
			|||||||
all: youtube-dl README.md CONTRIBUTING.md README.txt youtube-dl.1 youtube-dl.bash-completion youtube-dl.zsh youtube-dl.fish
 | 
					all: youtube-dl README.md CONTRIBUTING.md README.txt youtube-dl.1 youtube-dl.bash-completion youtube-dl.zsh youtube-dl.fish supportedsites
 | 
				
			||||||
 | 
					
 | 
				
			||||||
clean:
 | 
					clean:
 | 
				
			||||||
	rm -rf youtube-dl.1.temp.md youtube-dl.1 youtube-dl.bash-completion README.txt MANIFEST build/ dist/ .coverage cover/ youtube-dl.tar.gz youtube-dl.zsh youtube-dl.fish *.dump *.part *.info.json CONTRIBUTING.md.tmp
 | 
						rm -rf youtube-dl.1.temp.md youtube-dl.1 youtube-dl.bash-completion README.txt MANIFEST build/ dist/ .coverage cover/ youtube-dl.tar.gz youtube-dl.zsh youtube-dl.fish *.dump *.part *.info.json *.mp4 *.flv *.mp3 *.avi CONTRIBUTING.md.tmp youtube-dl youtube-dl.exe
 | 
				
			||||||
 | 
						find -name "*.pyc" -delete
 | 
				
			||||||
cleanall: clean
 | 
					 | 
				
			||||||
	rm -f youtube-dl youtube-dl.exe
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
PREFIX ?= /usr/local
 | 
					PREFIX ?= /usr/local
 | 
				
			||||||
BINDIR ?= $(PREFIX)/bin
 | 
					BINDIR ?= $(PREFIX)/bin
 | 
				
			||||||
@@ -36,12 +34,7 @@ install: youtube-dl youtube-dl.1 youtube-dl.bash-completion youtube-dl.zsh youtu
 | 
				
			|||||||
	install -m 644 youtube-dl.fish $(DESTDIR)$(SYSCONFDIR)/fish/completions/youtube-dl.fish
 | 
						install -m 644 youtube-dl.fish $(DESTDIR)$(SYSCONFDIR)/fish/completions/youtube-dl.fish
 | 
				
			||||||
 | 
					
 | 
				
			||||||
codetest:
 | 
					codetest:
 | 
				
			||||||
	PYFLAKES_OUT=$$(pyflakes youtube_dl | grep -v youtube_dl/extractor/__init__.py); \
 | 
						flake8 .
 | 
				
			||||||
	if test -n "$$PYFLAKES_OUT"; then \
 | 
					 | 
				
			||||||
		echo "$$PYFLAKES_OUT"; \
 | 
					 | 
				
			||||||
		exit 1; \
 | 
					 | 
				
			||||||
	fi
 | 
					 | 
				
			||||||
	pep8 . --ignore E501
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
test:
 | 
					test:
 | 
				
			||||||
	#nosetests --with-coverage --cover-package=youtube_dl --cover-html --verbose --processes 4 test
 | 
						#nosetests --with-coverage --cover-package=youtube_dl --cover-html --verbose --processes 4 test
 | 
				
			||||||
@@ -51,11 +44,11 @@ test:
 | 
				
			|||||||
ot: offlinetest
 | 
					ot: offlinetest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
offlinetest: codetest
 | 
					offlinetest: codetest
 | 
				
			||||||
	nosetests --verbose test --exclude test_download --exclude test_age_restriction --exclude test_subtitles --exclude test_write_annotations
 | 
						nosetests --verbose test --exclude test_download.py --exclude test_age_restriction.py --exclude test_subtitles.py --exclude test_write_annotations.py --exclude test_youtube_lists.py
 | 
				
			||||||
 | 
					
 | 
				
			||||||
tar: youtube-dl.tar.gz
 | 
					tar: youtube-dl.tar.gz
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.PHONY: all clean install test tar bash-completion pypi-files zsh-completion fish-completion ot offlinetest codetest
 | 
					.PHONY: all clean install test tar bash-completion pypi-files zsh-completion fish-completion ot offlinetest codetest supportedsites
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pypi-files: youtube-dl.bash-completion README.txt youtube-dl.1 youtube-dl.fish
 | 
					pypi-files: youtube-dl.bash-completion README.txt youtube-dl.1 youtube-dl.fish
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -68,11 +61,14 @@ youtube-dl: youtube_dl/*.py youtube_dl/*/*.py
 | 
				
			|||||||
	chmod a+x youtube-dl
 | 
						chmod a+x youtube-dl
 | 
				
			||||||
 | 
					
 | 
				
			||||||
README.md: youtube_dl/*.py youtube_dl/*/*.py
 | 
					README.md: youtube_dl/*.py youtube_dl/*/*.py
 | 
				
			||||||
	COLUMNS=80 python -m youtube_dl --help | python devscripts/make_readme.py
 | 
						COLUMNS=80 python youtube_dl/__main__.py --help | python devscripts/make_readme.py
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CONTRIBUTING.md: README.md
 | 
					CONTRIBUTING.md: README.md
 | 
				
			||||||
	python devscripts/make_contributing.py README.md CONTRIBUTING.md
 | 
						python devscripts/make_contributing.py README.md CONTRIBUTING.md
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					supportedsites:
 | 
				
			||||||
 | 
						python devscripts/make_supportedsites.py docs/supportedsites.md
 | 
				
			||||||
 | 
					
 | 
				
			||||||
README.txt: README.md
 | 
					README.txt: README.md
 | 
				
			||||||
	pandoc -f markdown -t plain README.md -o README.txt
 | 
						pandoc -f markdown -t plain README.md -o README.txt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										423
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										423
									
								
								README.md
									
									
									
									
									
								
							@@ -1,7 +1,15 @@
 | 
				
			|||||||
youtube-dl - download videos from youtube.com or other video platforms
 | 
					youtube-dl - download videos from youtube.com or other video platforms
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# SYNOPSIS
 | 
					- [INSTALLATION](#installation)
 | 
				
			||||||
**youtube-dl** [OPTIONS] URL [URL...]
 | 
					- [DESCRIPTION](#description)
 | 
				
			||||||
 | 
					- [OPTIONS](#options)
 | 
				
			||||||
 | 
					- [CONFIGURATION](#configuration)
 | 
				
			||||||
 | 
					- [OUTPUT TEMPLATE](#output-template)
 | 
				
			||||||
 | 
					- [VIDEO SELECTION](#video-selection)
 | 
				
			||||||
 | 
					- [FAQ](#faq)
 | 
				
			||||||
 | 
					- [DEVELOPER INSTRUCTIONS](#developer-instructions)
 | 
				
			||||||
 | 
					- [BUGS](#bugs)
 | 
				
			||||||
 | 
					- [COPYRIGHT](#copyright)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# INSTALLATION
 | 
					# INSTALLATION
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -34,165 +42,112 @@ YouTube.com and a few more sites. It requires the Python interpreter, version
 | 
				
			|||||||
your Unix box, on Windows or on Mac OS X. It is released to the public domain,
 | 
					your Unix box, on Windows or on Mac OS X. It is released to the public domain,
 | 
				
			||||||
which means you can modify it, redistribute it or use it however you like.
 | 
					which means you can modify it, redistribute it or use it however you like.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    youtube-dl [OPTIONS] URL [URL...]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# OPTIONS
 | 
					# OPTIONS
 | 
				
			||||||
    -h, --help                       print this help text and exit
 | 
					    -h, --help                       print this help text and exit
 | 
				
			||||||
    --version                        print program version and exit
 | 
					    --version                        print program version and exit
 | 
				
			||||||
    -U, --update                     update this program to latest version. Make
 | 
					    -U, --update                     update this program to latest version. Make sure that you have sufficient permissions (run with sudo if needed)
 | 
				
			||||||
                                     sure that you have sufficient permissions
 | 
					    -i, --ignore-errors              continue on download errors, for example to skip unavailable videos in a playlist
 | 
				
			||||||
                                     (run with sudo if needed)
 | 
					    --abort-on-error                 Abort downloading of further videos (in the playlist or the command line) if an error occurs
 | 
				
			||||||
    -i, --ignore-errors              continue on download errors, for example to
 | 
					 | 
				
			||||||
                                     skip unavailable videos in a playlist
 | 
					 | 
				
			||||||
    --abort-on-error                 Abort downloading of further videos (in the
 | 
					 | 
				
			||||||
                                     playlist or the command line) if an error
 | 
					 | 
				
			||||||
                                     occurs
 | 
					 | 
				
			||||||
    --dump-user-agent                display the current browser identification
 | 
					    --dump-user-agent                display the current browser identification
 | 
				
			||||||
    --list-extractors                List all supported extractors and the URLs
 | 
					    --list-extractors                List all supported extractors and the URLs they would handle
 | 
				
			||||||
                                     they would handle
 | 
					    --extractor-descriptions         Output descriptions of all supported extractors
 | 
				
			||||||
    --extractor-descriptions         Output descriptions of all supported
 | 
					    --default-search PREFIX          Use this prefix for unqualified URLs. For example "gvsearch2:" downloads two videos from google videos for  youtube-dl "large apple".
 | 
				
			||||||
                                     extractors
 | 
					                                     Use the value "auto" to let youtube-dl guess ("auto_warning" to emit a warning when guessing). "error" just throws an error. The
 | 
				
			||||||
    --proxy URL                      Use the specified HTTP/HTTPS proxy. Pass in
 | 
					                                     default value "fixup_error" repairs broken URLs, but emits an error if this is not possible instead of searching.
 | 
				
			||||||
                                     an empty string (--proxy "") for direct
 | 
					    --ignore-config                  Do not read configuration files. When given in the global configuration file /etc/youtube-dl.conf: Do not read the user configuration
 | 
				
			||||||
                                     connection
 | 
					                                     in ~/.config/youtube-dl/config (%APPDATA%/youtube-dl/config.txt on Windows)
 | 
				
			||||||
    --socket-timeout None            Time to wait before giving up, in seconds
 | 
					    --flat-playlist                  Do not extract the videos of a playlist, only list them.
 | 
				
			||||||
    --default-search PREFIX          Use this prefix for unqualified URLs. For
 | 
					    --no-color                       Do not emit color codes in output.
 | 
				
			||||||
                                     example "gvsearch2:" downloads two videos
 | 
					
 | 
				
			||||||
                                     from google videos for  youtube-dl "large
 | 
					## Network Options:
 | 
				
			||||||
                                     apple". Use the value "auto" to let
 | 
					    --proxy URL                      Use the specified HTTP/HTTPS proxy. Pass in an empty string (--proxy "") for direct connection
 | 
				
			||||||
                                     youtube-dl guess ("auto_warning" to emit a
 | 
					    --socket-timeout SECONDS         Time to wait before giving up, in seconds
 | 
				
			||||||
                                     warning when guessing). "error" just throws
 | 
					    --source-address IP              Client-side IP address to bind to (experimental)
 | 
				
			||||||
                                     an error. The default value "fixup_error"
 | 
					    -4, --force-ipv4                 Make all connections via IPv4 (experimental)
 | 
				
			||||||
                                     repairs broken URLs, but emits an error if
 | 
					    -6, --force-ipv6                 Make all connections via IPv6 (experimental)
 | 
				
			||||||
                                     this is not possible instead of searching.
 | 
					 | 
				
			||||||
    --ignore-config                  Do not read configuration files. When given
 | 
					 | 
				
			||||||
                                     in the global configuration file /etc
 | 
					 | 
				
			||||||
                                     /youtube-dl.conf: Do not read the user
 | 
					 | 
				
			||||||
                                     configuration in ~/.config/youtube-
 | 
					 | 
				
			||||||
                                     dl/config (%APPDATA%/youtube-dl/config.txt
 | 
					 | 
				
			||||||
                                     on Windows)
 | 
					 | 
				
			||||||
    --flat-playlist                  Do not extract the videos of a playlist,
 | 
					 | 
				
			||||||
                                     only list them.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Video Selection:
 | 
					## Video Selection:
 | 
				
			||||||
    --playlist-start NUMBER          playlist video to start at (default is 1)
 | 
					    --playlist-start NUMBER          playlist video to start at (default is 1)
 | 
				
			||||||
    --playlist-end NUMBER            playlist video to end at (default is last)
 | 
					    --playlist-end NUMBER            playlist video to end at (default is last)
 | 
				
			||||||
    --match-title REGEX              download only matching titles (regex or
 | 
					    --playlist-items ITEM_SPEC       playlist video items to download. Specify indices of the videos in the playlist seperated by commas like: "--playlist-items 1,2,5,8"
 | 
				
			||||||
                                     caseless sub-string)
 | 
					                                     if you want to download videos indexed 1, 2, 5, 8 in the playlist. You can specify range: "--playlist-items 1-3,7,10-13", it will
 | 
				
			||||||
    --reject-title REGEX             skip download for matching titles (regex or
 | 
					                                     download the videos at index 1, 2, 3, 7, 10, 11, 12 and 13.
 | 
				
			||||||
                                     caseless sub-string)
 | 
					    --match-title REGEX              download only matching titles (regex or caseless sub-string)
 | 
				
			||||||
 | 
					    --reject-title REGEX             skip download for matching titles (regex or caseless sub-string)
 | 
				
			||||||
    --max-downloads NUMBER           Abort after downloading NUMBER files
 | 
					    --max-downloads NUMBER           Abort after downloading NUMBER files
 | 
				
			||||||
    --min-filesize SIZE              Do not download any videos smaller than
 | 
					    --min-filesize SIZE              Do not download any videos smaller than SIZE (e.g. 50k or 44.6m)
 | 
				
			||||||
                                     SIZE (e.g. 50k or 44.6m)
 | 
					    --max-filesize SIZE              Do not download any videos larger than SIZE (e.g. 50k or 44.6m)
 | 
				
			||||||
    --max-filesize SIZE              Do not download any videos larger than SIZE
 | 
					 | 
				
			||||||
                                     (e.g. 50k or 44.6m)
 | 
					 | 
				
			||||||
    --date DATE                      download only videos uploaded in this date
 | 
					    --date DATE                      download only videos uploaded in this date
 | 
				
			||||||
    --datebefore DATE                download only videos uploaded on or before
 | 
					    --datebefore DATE                download only videos uploaded on or before this date (i.e. inclusive)
 | 
				
			||||||
                                     this date (i.e. inclusive)
 | 
					    --dateafter DATE                 download only videos uploaded on or after this date (i.e. inclusive)
 | 
				
			||||||
    --dateafter DATE                 download only videos uploaded on or after
 | 
					    --min-views COUNT                Do not download any videos with less than COUNT views
 | 
				
			||||||
                                     this date (i.e. inclusive)
 | 
					    --max-views COUNT                Do not download any videos with more than COUNT views
 | 
				
			||||||
    --min-views COUNT                Do not download any videos with less than
 | 
					    --match-filter FILTER            (Experimental) Generic video filter. Specify any key (see help for -o for a list of available keys) to match if the key is present,
 | 
				
			||||||
                                     COUNT views
 | 
					                                     !key to check if the key is not present,key > NUMBER (like "comment_count > 12", also works with >=, <, <=, !=, =) to compare against
 | 
				
			||||||
    --max-views COUNT                Do not download any videos with more than
 | 
					                                     a number, and & to require multiple matches. Values which are not known are excluded unless you put a question mark (?) after the
 | 
				
			||||||
                                     COUNT views
 | 
					                                     operator.For example, to only match videos that have been liked more than 100 times and disliked less than 50 times (or the dislike
 | 
				
			||||||
    --no-playlist                    If the URL refers to a video and a
 | 
					                                     functionality is not available at the given service), but who also have a description, use  --match-filter "like_count > 100 &
 | 
				
			||||||
                                     playlist, download only the video.
 | 
					                                     dislike_count <? 50 & description" .
 | 
				
			||||||
    --age-limit YEARS                download only videos suitable for the given
 | 
					    --no-playlist                    If the URL refers to a video and a playlist, download only the video.
 | 
				
			||||||
                                     age
 | 
					    --yes-playlist                   If the URL refers to a video and a playlist, download the playlist.
 | 
				
			||||||
    --download-archive FILE          Download only videos not listed in the
 | 
					    --age-limit YEARS                download only videos suitable for the given age
 | 
				
			||||||
                                     archive file. Record the IDs of all
 | 
					    --download-archive FILE          Download only videos not listed in the archive file. Record the IDs of all downloaded videos in it.
 | 
				
			||||||
                                     downloaded videos in it.
 | 
					    --include-ads                    Download advertisements as well (experimental)
 | 
				
			||||||
    --include-ads                    Download advertisements as well
 | 
					 | 
				
			||||||
                                     (experimental)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Download Options:
 | 
					## Download Options:
 | 
				
			||||||
    -r, --rate-limit LIMIT           maximum download rate in bytes per second
 | 
					    -r, --rate-limit LIMIT           maximum download rate in bytes per second (e.g. 50K or 4.2M)
 | 
				
			||||||
                                     (e.g. 50K or 4.2M)
 | 
					    -R, --retries RETRIES            number of retries (default is 10), or "infinite".
 | 
				
			||||||
    -R, --retries RETRIES            number of retries (default is 10)
 | 
					    --buffer-size SIZE               size of download buffer (e.g. 1024 or 16K) (default is 1024)
 | 
				
			||||||
    --buffer-size SIZE               size of download buffer (e.g. 1024 or 16K)
 | 
					    --no-resize-buffer               do not automatically adjust the buffer size. By default, the buffer size is automatically resized from an initial value of SIZE.
 | 
				
			||||||
                                     (default is 1024)
 | 
					 | 
				
			||||||
    --no-resize-buffer               do not automatically adjust the buffer
 | 
					 | 
				
			||||||
                                     size. By default, the buffer size is
 | 
					 | 
				
			||||||
                                     automatically resized from an initial value
 | 
					 | 
				
			||||||
                                     of SIZE.
 | 
					 | 
				
			||||||
    --playlist-reverse               Download playlist videos in reverse order
 | 
					    --playlist-reverse               Download playlist videos in reverse order
 | 
				
			||||||
 | 
					    --xattr-set-filesize             (experimental) set file xattribute ytdl.filesize with expected filesize
 | 
				
			||||||
 | 
					    --hls-prefer-native              (experimental) Use the native HLS downloader instead of ffmpeg.
 | 
				
			||||||
 | 
					    --external-downloader COMMAND    Use the specified external downloader. Currently supports aria2c,curl,wget
 | 
				
			||||||
 | 
					    --external-downloader-args ARGS  Give these arguments to the external downloader.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Filesystem Options:
 | 
					## Filesystem Options:
 | 
				
			||||||
    -a, --batch-file FILE            file containing URLs to download ('-' for
 | 
					    -a, --batch-file FILE            file containing URLs to download ('-' for stdin)
 | 
				
			||||||
                                     stdin)
 | 
					 | 
				
			||||||
    --id                             use only video ID in file name
 | 
					    --id                             use only video ID in file name
 | 
				
			||||||
    -o, --output TEMPLATE            output filename template. Use %(title)s to
 | 
					    -o, --output TEMPLATE            output filename template. Use %(title)s to get the title, %(uploader)s for the uploader name, %(uploader_id)s for the uploader
 | 
				
			||||||
                                     get the title, %(uploader)s for the
 | 
					                                     nickname if different, %(autonumber)s to get an automatically incremented number, %(ext)s for the filename extension, %(format)s for
 | 
				
			||||||
                                     uploader name, %(uploader_id)s for the
 | 
					                                     the format description (like "22 - 1280x720" or "HD"), %(format_id)s for the unique id of the format (like Youtube's itags: "137"),
 | 
				
			||||||
                                     uploader nickname if different,
 | 
					                                     %(upload_date)s for the upload date (YYYYMMDD), %(extractor)s for the provider (youtube, metacafe, etc), %(id)s for the video id,
 | 
				
			||||||
                                     %(autonumber)s to get an automatically
 | 
					                                     %(playlist_title)s, %(playlist_id)s, or %(playlist)s (=title if present, ID otherwise) for the playlist the video is in,
 | 
				
			||||||
                                     incremented number, %(ext)s for the
 | 
					                                     %(playlist_index)s for the position in the playlist. %(height)s and %(width)s for the width and height of the video format.
 | 
				
			||||||
                                     filename extension, %(format)s for the
 | 
					                                     %(resolution)s for a textual description of the resolution of the video format. %% for a literal percent. Use - to output to stdout.
 | 
				
			||||||
                                     format description (like "22 - 1280x720" or
 | 
					                                     Can also be used to download to a different directory, for example with -o '/my/downloads/%(uploader)s/%(title)s-%(id)s.%(ext)s' .
 | 
				
			||||||
                                     "HD"), %(format_id)s for the unique id of
 | 
					    --autonumber-size NUMBER         Specifies the number of digits in %(autonumber)s when it is present in output filename template or --auto-number option is given
 | 
				
			||||||
                                     the format (like Youtube's itags: "137"),
 | 
					    --restrict-filenames             Restrict filenames to only ASCII characters, and avoid "&" and spaces in filenames
 | 
				
			||||||
                                     %(upload_date)s for the upload date
 | 
					    -A, --auto-number                [deprecated; use  -o "%(autonumber)s-%(title)s.%(ext)s" ] number downloaded files starting from 00000
 | 
				
			||||||
                                     (YYYYMMDD), %(extractor)s for the provider
 | 
					    -t, --title                      [deprecated] use title in file name (default)
 | 
				
			||||||
                                     (youtube, metacafe, etc), %(id)s for the
 | 
					 | 
				
			||||||
                                     video id, %(playlist_title)s,
 | 
					 | 
				
			||||||
                                     %(playlist_id)s, or %(playlist)s (=title if
 | 
					 | 
				
			||||||
                                     present, ID otherwise) for the playlist the
 | 
					 | 
				
			||||||
                                     video is in, %(playlist_index)s for the
 | 
					 | 
				
			||||||
                                     position in the playlist. %(height)s and
 | 
					 | 
				
			||||||
                                     %(width)s for the width and height of the
 | 
					 | 
				
			||||||
                                     video format. %(resolution)s for a textual
 | 
					 | 
				
			||||||
                                     description of the resolution of the video
 | 
					 | 
				
			||||||
                                     format. %% for a literal percent. Use - to
 | 
					 | 
				
			||||||
                                     output to stdout. Can also be used to
 | 
					 | 
				
			||||||
                                     download to a different directory, for
 | 
					 | 
				
			||||||
                                     example with -o '/my/downloads/%(uploader)s
 | 
					 | 
				
			||||||
                                     /%(title)s-%(id)s.%(ext)s' .
 | 
					 | 
				
			||||||
    --autonumber-size NUMBER         Specifies the number of digits in
 | 
					 | 
				
			||||||
                                     %(autonumber)s when it is present in output
 | 
					 | 
				
			||||||
                                     filename template or --auto-number option
 | 
					 | 
				
			||||||
                                     is given
 | 
					 | 
				
			||||||
    --restrict-filenames             Restrict filenames to only ASCII
 | 
					 | 
				
			||||||
                                     characters, and avoid "&" and spaces in
 | 
					 | 
				
			||||||
                                     filenames
 | 
					 | 
				
			||||||
    -A, --auto-number                [deprecated; use  -o
 | 
					 | 
				
			||||||
                                     "%(autonumber)s-%(title)s.%(ext)s" ] number
 | 
					 | 
				
			||||||
                                     downloaded files starting from 00000
 | 
					 | 
				
			||||||
    -t, --title                      [deprecated] use title in file name
 | 
					 | 
				
			||||||
                                     (default)
 | 
					 | 
				
			||||||
    -l, --literal                    [deprecated] alias of --title
 | 
					    -l, --literal                    [deprecated] alias of --title
 | 
				
			||||||
    -w, --no-overwrites              do not overwrite files
 | 
					    -w, --no-overwrites              do not overwrite files
 | 
				
			||||||
    -c, --continue                   force resume of partially downloaded files.
 | 
					    -c, --continue                   force resume of partially downloaded files. By default, youtube-dl will resume downloads if possible.
 | 
				
			||||||
                                     By default, youtube-dl will resume
 | 
					    --no-continue                    do not resume partially downloaded files (restart from beginning)
 | 
				
			||||||
                                     downloads if possible.
 | 
					    --no-part                        do not use .part files - write directly into output file
 | 
				
			||||||
    --no-continue                    do not resume partially downloaded files
 | 
					    --no-mtime                       do not use the Last-modified header to set the file modification time
 | 
				
			||||||
                                     (restart from beginning)
 | 
					    --write-description              write video description to a .description file
 | 
				
			||||||
    --no-part                        do not use .part files - write directly
 | 
					 | 
				
			||||||
                                     into output file
 | 
					 | 
				
			||||||
    --no-mtime                       do not use the Last-modified header to set
 | 
					 | 
				
			||||||
                                     the file modification time
 | 
					 | 
				
			||||||
    --write-description              write video description to a .description
 | 
					 | 
				
			||||||
                                     file
 | 
					 | 
				
			||||||
    --write-info-json                write video metadata to a .info.json file
 | 
					    --write-info-json                write video metadata to a .info.json file
 | 
				
			||||||
    --write-annotations              write video annotations to a .annotation
 | 
					    --write-annotations              write video annotations to a .annotation file
 | 
				
			||||||
                                     file
 | 
					    --load-info FILE                 json file containing the video information (created with the "--write-json" option)
 | 
				
			||||||
    --write-thumbnail                write thumbnail image to disk
 | 
					    --cookies FILE                   file to read cookies from and dump cookie jar in
 | 
				
			||||||
    --load-info FILE                 json file containing the video information
 | 
					    --cache-dir DIR                  Location in the filesystem where youtube-dl can store some downloaded information permanently. By default $XDG_CACHE_HOME/youtube-dl
 | 
				
			||||||
                                     (created with the "--write-json" option)
 | 
					                                     or ~/.cache/youtube-dl . At the moment, only YouTube player files (for videos with obfuscated signatures) are cached, but that may
 | 
				
			||||||
    --cookies FILE                   file to read cookies from and dump cookie
 | 
					                                     change.
 | 
				
			||||||
                                     jar in
 | 
					 | 
				
			||||||
    --cache-dir DIR                  Location in the filesystem where youtube-dl
 | 
					 | 
				
			||||||
                                     can store some downloaded information
 | 
					 | 
				
			||||||
                                     permanently. By default $XDG_CACHE_HOME
 | 
					 | 
				
			||||||
                                     /youtube-dl or ~/.cache/youtube-dl . At the
 | 
					 | 
				
			||||||
                                     moment, only YouTube player files (for
 | 
					 | 
				
			||||||
                                     videos with obfuscated signatures) are
 | 
					 | 
				
			||||||
                                     cached, but that may change.
 | 
					 | 
				
			||||||
    --no-cache-dir                   Disable filesystem caching
 | 
					    --no-cache-dir                   Disable filesystem caching
 | 
				
			||||||
    --rm-cache-dir                   Delete all filesystem cache files
 | 
					    --rm-cache-dir                   Delete all filesystem cache files
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Thumbnail images:
 | 
				
			||||||
 | 
					    --write-thumbnail                write thumbnail image to disk
 | 
				
			||||||
 | 
					    --write-all-thumbnails           write all thumbnail image formats to disk
 | 
				
			||||||
 | 
					    --list-thumbnails                Simulate and list all available thumbnail formats
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Verbosity / Simulation Options:
 | 
					## Verbosity / Simulation Options:
 | 
				
			||||||
    -q, --quiet                      activates quiet mode
 | 
					    -q, --quiet                      activates quiet mode
 | 
				
			||||||
    --no-warnings                    Ignore warnings
 | 
					    --no-warnings                    Ignore warnings
 | 
				
			||||||
    -s, --simulate                   do not download the video and do not write
 | 
					    -s, --simulate                   do not download the video and do not write anything to disk
 | 
				
			||||||
                                     anything to disk
 | 
					 | 
				
			||||||
    --skip-download                  do not download the video
 | 
					    --skip-download                  do not download the video
 | 
				
			||||||
    -g, --get-url                    simulate, quiet but print URL
 | 
					    -g, --get-url                    simulate, quiet but print URL
 | 
				
			||||||
    -e, --get-title                  simulate, quiet but print title
 | 
					    -e, --get-title                  simulate, quiet but print title
 | 
				
			||||||
@@ -202,121 +157,88 @@ which means you can modify it, redistribute it or use it however you like.
 | 
				
			|||||||
    --get-duration                   simulate, quiet but print video length
 | 
					    --get-duration                   simulate, quiet but print video length
 | 
				
			||||||
    --get-filename                   simulate, quiet but print output filename
 | 
					    --get-filename                   simulate, quiet but print output filename
 | 
				
			||||||
    --get-format                     simulate, quiet but print output format
 | 
					    --get-format                     simulate, quiet but print output format
 | 
				
			||||||
    -j, --dump-json                  simulate, quiet but print JSON information.
 | 
					    -j, --dump-json                  simulate, quiet but print JSON information. See --output for a description of available keys.
 | 
				
			||||||
                                     See --output for a description of available
 | 
					    -J, --dump-single-json           simulate, quiet but print JSON information for each command-line argument. If the URL refers to a playlist, dump the whole playlist
 | 
				
			||||||
                                     keys.
 | 
					                                     information in a single line.
 | 
				
			||||||
    -J, --dump-single-json           simulate, quiet but print JSON information
 | 
					    --print-json                     Be quiet and print the video information as JSON (video is still being downloaded).
 | 
				
			||||||
                                     for each command-line argument. If the URL
 | 
					 | 
				
			||||||
                                     refers to a playlist, dump the whole
 | 
					 | 
				
			||||||
                                     playlist information in a single line.
 | 
					 | 
				
			||||||
    --newline                        output progress bar as new lines
 | 
					    --newline                        output progress bar as new lines
 | 
				
			||||||
    --no-progress                    do not print progress bar
 | 
					    --no-progress                    do not print progress bar
 | 
				
			||||||
    --console-title                  display progress in console titlebar
 | 
					    --console-title                  display progress in console titlebar
 | 
				
			||||||
    -v, --verbose                    print various debugging information
 | 
					    -v, --verbose                    print various debugging information
 | 
				
			||||||
    --dump-intermediate-pages        print downloaded pages to debug problems
 | 
					    --dump-intermediate-pages        print downloaded pages to debug problems (very verbose)
 | 
				
			||||||
                                     (very verbose)
 | 
					    --write-pages                    Write downloaded intermediary pages to files in the current directory to debug problems
 | 
				
			||||||
    --write-pages                    Write downloaded intermediary pages to
 | 
					 | 
				
			||||||
                                     files in the current directory to debug
 | 
					 | 
				
			||||||
                                     problems
 | 
					 | 
				
			||||||
    --print-traffic                  Display sent and read HTTP traffic
 | 
					    --print-traffic                  Display sent and read HTTP traffic
 | 
				
			||||||
 | 
					    -C, --call-home                  Contact the youtube-dl server for debugging.
 | 
				
			||||||
 | 
					    --no-call-home                   Do NOT contact the youtube-dl server for debugging.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Workarounds:
 | 
					## Workarounds:
 | 
				
			||||||
    --encoding ENCODING              Force the specified encoding (experimental)
 | 
					    --encoding ENCODING              Force the specified encoding (experimental)
 | 
				
			||||||
    --no-check-certificate           Suppress HTTPS certificate validation.
 | 
					    --no-check-certificate           Suppress HTTPS certificate validation.
 | 
				
			||||||
    --prefer-insecure                Use an unencrypted connection to retrieve
 | 
					    --prefer-insecure                Use an unencrypted connection to retrieve information about the video. (Currently supported only for YouTube)
 | 
				
			||||||
                                     information about the video. (Currently
 | 
					 | 
				
			||||||
                                     supported only for YouTube)
 | 
					 | 
				
			||||||
    --user-agent UA                  specify a custom user agent
 | 
					    --user-agent UA                  specify a custom user agent
 | 
				
			||||||
    --referer URL                    specify a custom referer, use if the video
 | 
					    --referer URL                    specify a custom referer, use if the video access is restricted to one domain
 | 
				
			||||||
                                     access is restricted to one domain
 | 
					    --add-header FIELD:VALUE         specify a custom HTTP header and its value, separated by a colon ':'. You can use this option multiple times
 | 
				
			||||||
    --add-header FIELD:VALUE         specify a custom HTTP header and its value,
 | 
					    --bidi-workaround                Work around terminals that lack bidirectional text support. Requires bidiv or fribidi executable in PATH
 | 
				
			||||||
                                     separated by a colon ':'. You can use this
 | 
					    --sleep-interval SECONDS         Number of seconds to sleep before each download.
 | 
				
			||||||
                                     option multiple times
 | 
					 | 
				
			||||||
    --bidi-workaround                Work around terminals that lack
 | 
					 | 
				
			||||||
                                     bidirectional text support. Requires bidiv
 | 
					 | 
				
			||||||
                                     or fribidi executable in PATH
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Video Format Options:
 | 
					## Video Format Options:
 | 
				
			||||||
    -f, --format FORMAT              video format code, specify the order of
 | 
					    -f, --format FORMAT              video format code, specify the order of preference using slashes, as in -f 22/17/18 .  Instead of format codes, you can select by
 | 
				
			||||||
                                     preference using slashes: -f 22/17/18 .  -f
 | 
					                                     extension for the extensions aac, m4a, mp3, mp4, ogg, wav, webm. You can also use the special names "best", "bestvideo", "bestaudio",
 | 
				
			||||||
                                     mp4 , -f m4a and  -f flv  are also
 | 
					                                     "worst".  You can filter the video results by putting a condition in brackets, as in -f "best[height=720]" (or -f "[filesize>10M]").
 | 
				
			||||||
                                     supported. You can also use the special
 | 
					                                     This works for filesize, height, width, tbr, abr, vbr, asr, and fps and the comparisons <, <=, >, >=, =, != and for ext, acodec,
 | 
				
			||||||
                                     names "best", "bestvideo", "bestaudio",
 | 
					                                     vcodec, container, and protocol and the comparisons =, != . Formats for which the value is not known are excluded unless you put a
 | 
				
			||||||
                                     "worst", "worstvideo" and "worstaudio". By
 | 
					                                     question mark (?) after the operator. You can combine format filters, so  -f "[height <=? 720][tbr>500]" selects up to 720p videos
 | 
				
			||||||
                                     default, youtube-dl will pick the best
 | 
					                                     (or videos where the height is not known) with a bitrate of at least 500 KBit/s. By default, youtube-dl will pick the best quality.
 | 
				
			||||||
                                     quality. Use commas to download multiple
 | 
					                                     Use commas to download multiple audio formats, such as -f  136/137/mp4/bestvideo,140/m4a/bestaudio. You can merge the video and audio
 | 
				
			||||||
                                     audio formats, such as -f
 | 
					                                     of two formats into a single file using -f <video-format>+<audio-format> (requires ffmpeg or avconv), for example -f
 | 
				
			||||||
                                     136/137/mp4/bestvideo,140/m4a/bestaudio.
 | 
					 | 
				
			||||||
                                     You can merge the video and audio of two
 | 
					 | 
				
			||||||
                                     formats into a single file using -f <video-
 | 
					 | 
				
			||||||
                                     format>+<audio-format> (requires ffmpeg or
 | 
					 | 
				
			||||||
                                     avconv), for example -f
 | 
					 | 
				
			||||||
                                     bestvideo+bestaudio.
 | 
					                                     bestvideo+bestaudio.
 | 
				
			||||||
    --all-formats                    download all available video formats
 | 
					    --all-formats                    download all available video formats
 | 
				
			||||||
    --prefer-free-formats            prefer free video formats unless a specific
 | 
					    --prefer-free-formats            prefer free video formats unless a specific one is requested
 | 
				
			||||||
                                     one is requested
 | 
					 | 
				
			||||||
    --max-quality FORMAT             highest quality format to download
 | 
					    --max-quality FORMAT             highest quality format to download
 | 
				
			||||||
    -F, --list-formats               list all available formats
 | 
					    -F, --list-formats               list all available formats
 | 
				
			||||||
    --youtube-skip-dash-manifest     Do not download the DASH manifest on
 | 
					    --youtube-skip-dash-manifest     Do not download the DASH manifest on YouTube videos
 | 
				
			||||||
                                     YouTube videos
 | 
					    --merge-output-format FORMAT     If a merge is required (e.g. bestvideo+bestaudio), output to given container format. One of mkv, mp4, ogg, webm, flv.Ignored if no
 | 
				
			||||||
 | 
					                                     merge is required
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Subtitle Options:
 | 
					## Subtitle Options:
 | 
				
			||||||
    --write-sub                      write subtitle file
 | 
					    --write-sub                      write subtitle file
 | 
				
			||||||
    --write-auto-sub                 write automatic subtitle file (youtube
 | 
					    --write-auto-sub                 write automatic subtitle file (youtube only)
 | 
				
			||||||
                                     only)
 | 
					    --all-subs                       downloads all the available subtitles of the video
 | 
				
			||||||
    --all-subs                       downloads all the available subtitles of
 | 
					 | 
				
			||||||
                                     the video
 | 
					 | 
				
			||||||
    --list-subs                      lists all available subtitles for the video
 | 
					    --list-subs                      lists all available subtitles for the video
 | 
				
			||||||
    --sub-format FORMAT              subtitle format (default=srt) ([sbv/vtt]
 | 
					    --sub-format FORMAT              subtitle format, accepts formats preference, for example: "ass/srt/best"
 | 
				
			||||||
                                     youtube only)
 | 
					    --sub-lang LANGS                 languages of the subtitles to download (optional) separated by commas, use IETF language tags like 'en,pt'
 | 
				
			||||||
    --sub-lang LANGS                 languages of the subtitles to download
 | 
					 | 
				
			||||||
                                     (optional) separated by commas, use IETF
 | 
					 | 
				
			||||||
                                     language tags like 'en,pt'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Authentication Options:
 | 
					## Authentication Options:
 | 
				
			||||||
    -u, --username USERNAME          login with this account ID
 | 
					    -u, --username USERNAME          login with this account ID
 | 
				
			||||||
    -p, --password PASSWORD          account password
 | 
					    -p, --password PASSWORD          account password. If this option is left out, youtube-dl will ask interactively.
 | 
				
			||||||
    -2, --twofactor TWOFACTOR        two-factor auth code
 | 
					    -2, --twofactor TWOFACTOR        two-factor auth code
 | 
				
			||||||
    -n, --netrc                      use .netrc authentication data
 | 
					    -n, --netrc                      use .netrc authentication data
 | 
				
			||||||
    --video-password PASSWORD        video password (vimeo, smotri)
 | 
					    --video-password PASSWORD        video password (vimeo, smotri)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Post-processing Options:
 | 
					## Post-processing Options:
 | 
				
			||||||
    -x, --extract-audio              convert video files to audio-only files
 | 
					    -x, --extract-audio              convert video files to audio-only files (requires ffmpeg or avconv and ffprobe or avprobe)
 | 
				
			||||||
                                     (requires ffmpeg or avconv and ffprobe or
 | 
					    --audio-format FORMAT            "best", "aac", "vorbis", "mp3", "m4a", "opus", or "wav"; "best" by default
 | 
				
			||||||
                                     avprobe)
 | 
					    --audio-quality QUALITY          ffmpeg/avconv audio quality specification, insert a value between 0 (better) and 9 (worse) for VBR or a specific bitrate like 128K
 | 
				
			||||||
    --audio-format FORMAT            "best", "aac", "vorbis", "mp3", "m4a",
 | 
					                                     (default 5)
 | 
				
			||||||
                                     "opus", or "wav"; "best" by default
 | 
					    --recode-video FORMAT            Encode the video to another format if necessary (currently supported: mp4|flv|ogg|webm|mkv)
 | 
				
			||||||
    --audio-quality QUALITY          ffmpeg/avconv audio quality specification,
 | 
					    -k, --keep-video                 keeps the video file on disk after the post-processing; the video is erased by default
 | 
				
			||||||
                                     insert a value between 0 (better) and 9
 | 
					    --no-post-overwrites             do not overwrite post-processed files; the post-processed files are overwritten by default
 | 
				
			||||||
                                     (worse) for VBR or a specific bitrate like
 | 
					    --embed-subs                     embed subtitles in the video (only for mp4 videos)
 | 
				
			||||||
                                     128K (default 5)
 | 
					 | 
				
			||||||
    --recode-video FORMAT            Encode the video to another format if
 | 
					 | 
				
			||||||
                                     necessary (currently supported:
 | 
					 | 
				
			||||||
                                     mp4|flv|ogg|webm|mkv)
 | 
					 | 
				
			||||||
    -k, --keep-video                 keeps the video file on disk after the
 | 
					 | 
				
			||||||
                                     post-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)
 | 
					 | 
				
			||||||
    --embed-thumbnail                embed thumbnail in the audio as cover art
 | 
					    --embed-thumbnail                embed thumbnail in the audio as cover art
 | 
				
			||||||
    --add-metadata                   write metadata to the video file
 | 
					    --add-metadata                   write metadata to the video file
 | 
				
			||||||
    --xattrs                         write metadata to the video file's xattrs
 | 
					    --xattrs                         write metadata to the video file's xattrs (using dublin core and xdg standards)
 | 
				
			||||||
                                     (using dublin core and xdg standards)
 | 
					    --fixup POLICY                   Automatically correct known faults of the file. One of never (do nothing), warn (only emit a warning), detect_or_warn(the default;
 | 
				
			||||||
    --prefer-avconv                  Prefer avconv over ffmpeg for running the
 | 
					                                     fix file if we can, warn otherwise)
 | 
				
			||||||
                                     postprocessors (default)
 | 
					    --prefer-avconv                  Prefer avconv over ffmpeg for running the postprocessors (default)
 | 
				
			||||||
    --prefer-ffmpeg                  Prefer ffmpeg over avconv for running the
 | 
					    --prefer-ffmpeg                  Prefer ffmpeg over avconv for running the postprocessors
 | 
				
			||||||
                                     postprocessors
 | 
					    --ffmpeg-location PATH           Location of the ffmpeg/avconv binary; either the path to the binary or its containing directory.
 | 
				
			||||||
    --exec CMD                       Execute a command on the file after
 | 
					    --exec CMD                       Execute a command on the file after downloading, similar to find's -exec syntax. Example: --exec 'adb push {} /sdcard/Music/ && rm
 | 
				
			||||||
                                     downloading, similar to find's -exec
 | 
					                                     {}'
 | 
				
			||||||
                                     syntax. Example: --exec 'adb push {}
 | 
					    --convert-subtitles FORMAT       Convert the subtitles to other format (currently supported: srt|ass|vtt)
 | 
				
			||||||
                                     /sdcard/Music/ && rm {}'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
# CONFIGURATION
 | 
					# CONFIGURATION
 | 
				
			||||||
 | 
					
 | 
				
			||||||
You can configure youtube-dl by placing default arguments (such as `--extract-audio --no-mtime` to always extract the audio and not copy the mtime) into `/etc/youtube-dl.conf` and/or `~/.config/youtube-dl/config`. On Windows, the configuration file locations are `%APPDATA%\youtube-dl\config.txt` and `C:\Users\<Yourname>\youtube-dl.conf`.
 | 
					You can configure youtube-dl by placing default arguments (such as `--extract-audio --no-mtime` to always extract the audio and not copy the mtime) into `/etc/youtube-dl.conf` and/or `~/.config/youtube-dl/config`. On Windows, the configuration file locations are `%APPDATA%\youtube-dl\config.txt` and `C:\Users\<user name>\youtube-dl.conf`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# OUTPUT TEMPLATE
 | 
					# OUTPUT TEMPLATE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -410,17 +332,31 @@ Apparently YouTube requires you to pass a CAPTCHA test if you download too much.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
Once the video is fully downloaded, use any video player, such as [vlc](http://www.videolan.org) or [mplayer](http://www.mplayerhq.hu/).
 | 
					Once the video is fully downloaded, use any video player, such as [vlc](http://www.videolan.org) or [mplayer](http://www.mplayerhq.hu/).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### The links provided by youtube-dl -g are not working anymore
 | 
					### I extracted a video URL with -g, but it does not play on another machine / in my webbrowser.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The URLs youtube-dl outputs require the downloader to have the correct cookies. Use the `--cookies` option to write the required cookies into a file, and advise your downloader to read cookies from that file. Some sites also require a common user agent to be used, use `--dump-user-agent` to see the one in use by youtube-dl.
 | 
					It depends a lot on the service. In many cases, requests for the video (to download/play it) must come from the same IP address and with the same cookies.  Use the `--cookies` option to write the required cookies into a file, and advise your downloader to read cookies from that file. Some sites also require a common user agent to be used, use `--dump-user-agent` to see the one in use by youtube-dl.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					It may be beneficial to use IPv6; in some cases, the restrictions are only applied to IPv4. Some services (sometimes only for a subset of videos) do not restrict the video URL by IP address, cookie, or user-agent, but these are the exception rather than the rule.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Please bear in mind that some URL protocols are **not** supported by browsers out of the box, including RTMP. If you are using -g, your own downloader must support these as well.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If you want to play the video on a machine that is not running youtube-dl, you can relay the video content from the machine that runs youtube-dl. You can use `-o -` to let youtube-dl stream a video to stdout, or simply allow the player to download the files written by youtube-dl in turn.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### ERROR: no fmt_url_map or conn information found in video info
 | 
					### ERROR: no fmt_url_map or conn information found in video info
 | 
				
			||||||
 | 
					
 | 
				
			||||||
youtube has switched to a new video info format in July 2011 which is not supported by old versions of youtube-dl. You can update youtube-dl with `sudo youtube-dl --update`.
 | 
					YouTube has switched to a new video info format in July 2011 which is not supported by old versions of youtube-dl. See [above](#how-do-i-update-youtube-dl) for how to update youtube-dl.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### ERROR: unable to download video ###
 | 
					### ERROR: unable to download video ###
 | 
				
			||||||
 | 
					
 | 
				
			||||||
youtube requires an additional signature since September 2012 which is not supported by old versions of youtube-dl. You can update youtube-dl with `sudo youtube-dl --update`.
 | 
					YouTube requires an additional signature since September 2012 which is not supported by old versions of youtube-dl. See [above](#how-do-i-update-youtube-dl) for how to update youtube-dl.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### ExtractorError: Could not find JS function u'OF'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					In February 2015, the new YouTube player contained a character sequence in a string that was misinterpreted by old versions of youtube-dl. See [above](#how-do-i-update-youtube-dl) for how to update youtube-dl.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### HTTP Error 429: Too Many Requests or 402: Payment Required
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					These two error codes indicate that the service is blocking your IP address because of overuse. Contact the service and ask them to unblock your IP address, or - if you have acquired a whitelisted IP address already - use the [`--proxy` or `--network-address` options](#network-options) to select another IP address.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### SyntaxError: Non-ASCII character ###
 | 
					### SyntaxError: Non-ASCII character ###
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -439,6 +375,41 @@ Since June 2012 (#342) youtube-dl is packed as an executable zipfile, simply unz
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
To run the exe you need to install first the [Microsoft Visual C++ 2008 Redistributable Package](http://www.microsoft.com/en-us/download/details.aspx?id=29).
 | 
					To run the exe you need to install first the [Microsoft Visual C++ 2008 Redistributable Package](http://www.microsoft.com/en-us/download/details.aspx?id=29).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### On Windows, how should I set up ffmpeg and youtube-dl? Where should I put the exe files?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If you put youtube-dl and ffmpeg in the same directory that you're running the command from, it will work, but that's rather cumbersome.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To make a different directory work - either for ffmpeg, or for youtube-dl, or for both - simply create the directory (say, `C:\bin`, or `C:\Users\<User name>\bin`), put all the executables directly in there, and then [set your PATH environment variable](https://www.java.com/en/download/help/path.xml) to include that directory.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					From then on, after restarting your shell, you will be able to access both youtube-dl and ffmpeg (and youtube-dl will be able to find ffmpeg) by simply typing `youtube-dl` or `ffmpeg`, no matter what directory you're in.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### How do I put downloads into a specific folder?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Use the `-o` to specify an [output template](#output-template), for example `-o "/home/user/videos/%(title)s-%(id)s.%(ext)s"`. If you want this for all of your downloads, put the option into your [configuration file](#configuration).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### How do I download a video starting with a `-` ?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Either prepend `http://www.youtube.com/watch?v=` or separate the ID from the options with `--`:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    youtube-dl -- -wNyEUrxzFU
 | 
				
			||||||
 | 
					    youtube-dl "http://www.youtube.com/watch?v=-wNyEUrxzFU"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Can you add support for this anime video site, or site which shows current movies for free?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					As a matter of policy (as well as legality), youtube-dl does not include support for services that specialize in infringing copyright. As a rule of thumb, if you cannot easily find a video that the service is quite obviously allowed to distribute (i.e. that has been uploaded by the creator, the creator's distributor, or is published under a free license), the service is probably unfit for inclusion to youtube-dl.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					A note on the service that they don't host the infringing content, but just link to those who do, is evidence that the service should **not** be included into youtube-dl. The same goes for any DMCA note when the whole front page of the service is filled with videos they are not allowed to distribute. A "fair use" note is equally unconvincing if the service shows copyright-protected videos in full without authorization.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Support requests for services that **do** purchase the rights to distribute their content are perfectly fine though. If in doubt, you can simply include a source that mentions the legitimate purchase of content.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### How can I detect whether a given URL is supported by youtube-dl?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					For one, have a look at the [list of supported sites](docs/supportedsites.md). Note that it can sometimes happen that the site changes its URL scheme (say, from http://example.com/video/1234567 to http://example.com/v/1234567 ) and youtube-dl reports an URL of a service in that list as unsupported. In that case, simply report a bug.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					It is *not* possible to detect whether a URL is supported or not. That's because youtube-dl contains a generic extractor which matches **all** URLs. You may be tempted to disable, exclude, or remove the generic extractor, but the generic extractor not only allows users to extract videos from lots of websites that embed a video from another service, but may also be used to extract video from a service that it's hosting itself. Therefore, we neither recommend nor support disabling, excluding, or removing the generic extractor.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If you want to find out whether a given URL is supported, simply call youtube-dl with it. If you get no videos back, chances are the URL is either not referring to a video or unsupported. You can find out which by examining the output (if you run youtube-dl on the console) or catching an `UnsupportedError` exception if you run it from a Python program.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# DEVELOPER INSTRUCTIONS
 | 
					# DEVELOPER INSTRUCTIONS
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Most users do not need to build youtube-dl and can [download the builds](http://rg3.github.io/youtube-dl/download.html) or get them from their distribution.
 | 
					Most users do not need to build youtube-dl and can [download the builds](http://rg3.github.io/youtube-dl/download.html) or get them from their distribution.
 | 
				
			||||||
@@ -511,7 +482,7 @@ If you want to add support for a new site, you can follow this quick list (assum
 | 
				
			|||||||
5. Add an import in [`youtube_dl/extractor/__init__.py`](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/__init__.py).
 | 
					5. Add an import in [`youtube_dl/extractor/__init__.py`](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/__init__.py).
 | 
				
			||||||
6. Run `python test/test_download.py TestDownload.test_YourExtractor`. This *should fail* at first, but you can continually re-run it until you're done. If you decide to add more than one test, then rename ``_TEST`` to ``_TESTS`` and make it into a list of dictionaries. The tests will be then be named `TestDownload.test_YourExtractor`, `TestDownload.test_YourExtractor_1`, `TestDownload.test_YourExtractor_2`, etc.
 | 
					6. Run `python test/test_download.py TestDownload.test_YourExtractor`. This *should fail* at first, but you can continually re-run it until you're done. If you decide to add more than one test, then rename ``_TEST`` to ``_TESTS`` and make it into a list of dictionaries. The tests will be then be named `TestDownload.test_YourExtractor`, `TestDownload.test_YourExtractor_1`, `TestDownload.test_YourExtractor_2`, etc.
 | 
				
			||||||
7. Have a look at [`youtube_dl/common/extractor/common.py`](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/common.py) for possible helper methods and a [detailed description of what your extractor should return](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/common.py#L38). Add tests and code for as many as you want.
 | 
					7. Have a look at [`youtube_dl/common/extractor/common.py`](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/common.py) for possible helper methods and a [detailed description of what your extractor should return](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/common.py#L38). Add tests and code for as many as you want.
 | 
				
			||||||
8. If you can, check the code with [pyflakes](https://pypi.python.org/pypi/pyflakes) (a good idea) and [pep8](https://pypi.python.org/pypi/pep8) (optional, ignore E501).
 | 
					8. If you can, check the code with [flake8](https://pypi.python.org/pypi/flake8).
 | 
				
			||||||
9. When the tests pass, [add](http://git-scm.com/docs/git-add) the new files and [commit](http://git-scm.com/docs/git-commit) them and [push](http://git-scm.com/docs/git-push) the result, like this:
 | 
					9. When the tests pass, [add](http://git-scm.com/docs/git-add) the new files and [commit](http://git-scm.com/docs/git-commit) them and [push](http://git-scm.com/docs/git-push) the result, like this:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $ git add youtube_dl/extractor/__init__.py
 | 
					        $ git add youtube_dl/extractor/__init__.py
 | 
				
			||||||
@@ -579,7 +550,9 @@ with youtube_dl.YoutubeDL(ydl_opts) as ydl:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
Bugs and suggestions should be reported at: <https://github.com/rg3/youtube-dl/issues> . Unless you were prompted so or there is another pertinent reason (e.g. GitHub fails to accept the bug report), please do not send bug reports via personal email. For discussions, join us in the irc channel #youtube-dl on freenode.
 | 
					Bugs and suggestions should be reported at: <https://github.com/rg3/youtube-dl/issues> . Unless you were prompted so or there is another pertinent reason (e.g. GitHub fails to accept the bug report), please do not send bug reports via personal email. For discussions, join us in the irc channel #youtube-dl on freenode.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Please include the full output of the command when run with `--verbose`. The output (including the first lines) contain important debugging information. Issues without the full output are often not reproducible and therefore do not get solved in short order, if ever.
 | 
					**Please include the full output of youtube-dl when run with `-v`**.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The output (including the first lines) contain important debugging information. Issues without the full output are often not reproducible and therefore do not get solved in short order, if ever.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Please re-read your issue once again to avoid a couple of common mistakes (you can and should use this as a checklist):
 | 
					Please re-read your issue once again to avoid a couple of common mistakes (you can and should use this as a checklist):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -625,7 +598,7 @@ In particular, every site support request issue should only pertain to services
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
###  Is anyone going to need the feature?
 | 
					###  Is anyone going to need the feature?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Only post features that you (or an incapicated friend you can personally talk to) require. Do not post features because they seem like a good idea. If they are really useful, they will be requested by someone who requires them.
 | 
					Only post features that you (or an incapacitated friend you can personally talk to) require. Do not post features because they seem like a good idea. If they are really useful, they will be requested by someone who requires them.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
###  Is your question about youtube-dl?
 | 
					###  Is your question about youtube-dl?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -45,12 +45,12 @@ for test in get_testcases():
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        RESULT = ('.' + domain + '\n' in LIST or '\n' + domain + '\n' in LIST)
 | 
					        RESULT = ('.' + domain + '\n' in LIST or '\n' + domain + '\n' in LIST)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if RESULT and ('info_dict' not in test or 'age_limit' not in test['info_dict']
 | 
					    if RESULT and ('info_dict' not in test or 'age_limit' not in test['info_dict'] or
 | 
				
			||||||
                   or test['info_dict']['age_limit'] != 18):
 | 
					                   test['info_dict']['age_limit'] != 18):
 | 
				
			||||||
        print('\nPotential missing age_limit check: {0}'.format(test['name']))
 | 
					        print('\nPotential missing age_limit check: {0}'.format(test['name']))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    elif not RESULT and ('info_dict' in test and 'age_limit' in test['info_dict']
 | 
					    elif not RESULT and ('info_dict' in test and 'age_limit' in test['info_dict'] and
 | 
				
			||||||
                         and test['info_dict']['age_limit'] == 18):
 | 
					                         test['info_dict']['age_limit'] == 18):
 | 
				
			||||||
        print('\nPotential false negative: {0}'.format(test['name']))
 | 
					        print('\nPotential false negative: {0}'.format(test['name']))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,7 +16,7 @@ def main():
 | 
				
			|||||||
        template = tmplf.read()
 | 
					        template = tmplf.read()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ie_htmls = []
 | 
					    ie_htmls = []
 | 
				
			||||||
    for ie in sorted(youtube_dl.gen_extractors(), key=lambda i: i.IE_NAME.lower()):
 | 
					    for ie in youtube_dl.list_extractors(age_limit=None):
 | 
				
			||||||
        ie_html = '<b>{}</b>'.format(ie.IE_NAME)
 | 
					        ie_html = '<b>{}</b>'.format(ie.IE_NAME)
 | 
				
			||||||
        ie_desc = getattr(ie, 'IE_DESC', None)
 | 
					        ie_desc = getattr(ie, 'IE_DESC', None)
 | 
				
			||||||
        if ie_desc is False:
 | 
					        if ie_desc is False:
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										45
									
								
								devscripts/make_supportedsites.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								devscripts/make_supportedsites.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import io
 | 
				
			||||||
 | 
					import optparse
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Import youtube_dl
 | 
				
			||||||
 | 
					ROOT_DIR = os.path.join(os.path.dirname(__file__), '..')
 | 
				
			||||||
 | 
					sys.path.append(ROOT_DIR)
 | 
				
			||||||
 | 
					import youtube_dl
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def main():
 | 
				
			||||||
 | 
					    parser = optparse.OptionParser(usage='%prog OUTFILE.md')
 | 
				
			||||||
 | 
					    options, args = parser.parse_args()
 | 
				
			||||||
 | 
					    if len(args) != 1:
 | 
				
			||||||
 | 
					        parser.error('Expected an output filename')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    outfile, = args
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def gen_ies_md(ies):
 | 
				
			||||||
 | 
					        for ie in ies:
 | 
				
			||||||
 | 
					            ie_md = '**{0}**'.format(ie.IE_NAME)
 | 
				
			||||||
 | 
					            ie_desc = getattr(ie, 'IE_DESC', None)
 | 
				
			||||||
 | 
					            if ie_desc is False:
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            if ie_desc is not None:
 | 
				
			||||||
 | 
					                ie_md += ': {0}'.format(ie.IE_DESC)
 | 
				
			||||||
 | 
					            if not ie.working():
 | 
				
			||||||
 | 
					                ie_md += ' (Currently broken)'
 | 
				
			||||||
 | 
					            yield ie_md
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ies = sorted(youtube_dl.gen_extractors(), key=lambda i: i.IE_NAME.lower())
 | 
				
			||||||
 | 
					    out = '# Supported sites\n' + ''.join(
 | 
				
			||||||
 | 
					        ' - ' + md + '\n'
 | 
				
			||||||
 | 
					        for md in gen_ies_md(ies))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with io.open(outfile, 'w', encoding='utf-8') as outf:
 | 
				
			||||||
 | 
					        outf.write(out)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == '__main__':
 | 
				
			||||||
 | 
					    main()
 | 
				
			||||||
@@ -11,8 +11,19 @@ README_FILE = os.path.join(ROOT_DIR, 'README.md')
 | 
				
			|||||||
with io.open(README_FILE, encoding='utf-8') as f:
 | 
					with io.open(README_FILE, encoding='utf-8') as f:
 | 
				
			||||||
    readme = f.read()
 | 
					    readme = f.read()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
PREFIX = '%YOUTUBE-DL(1)\n\n# NAME\n'
 | 
					PREFIX = '''%YOUTUBE-DL(1)
 | 
				
			||||||
readme = re.sub(r'(?s)# INSTALLATION.*?(?=# DESCRIPTION)', '', readme)
 | 
					
 | 
				
			||||||
 | 
					# NAME
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					youtube\-dl \- download videos from youtube.com or other video platforms
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# SYNOPSIS
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**youtube-dl** \[OPTIONS\] URL [URL...]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					'''
 | 
				
			||||||
 | 
					readme = re.sub(r'(?s)^.*?(?=# DESCRIPTION)', '', readme)
 | 
				
			||||||
 | 
					readme = re.sub(r'\s+youtube-dl \[OPTIONS\] URL \[URL\.\.\.\]', '', readme)
 | 
				
			||||||
readme = PREFIX + readme
 | 
					readme = PREFIX + readme
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if sys.version_info < (3, 0):
 | 
					if sys.version_info < (3, 0):
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,7 +35,7 @@ if [ ! -z "$useless_files" ]; then echo "ERROR: Non-.py files in youtube_dl: $us
 | 
				
			|||||||
if [ ! -f "updates_key.pem" ]; then echo 'ERROR: updates_key.pem missing'; exit 1; fi
 | 
					if [ ! -f "updates_key.pem" ]; then echo 'ERROR: updates_key.pem missing'; exit 1; fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/bin/echo -e "\n### First of all, testing..."
 | 
					/bin/echo -e "\n### First of all, testing..."
 | 
				
			||||||
make cleanall
 | 
					make clean
 | 
				
			||||||
if $skip_tests ; then
 | 
					if $skip_tests ; then
 | 
				
			||||||
    echo 'SKIPPING TESTS'
 | 
					    echo 'SKIPPING TESTS'
 | 
				
			||||||
else
 | 
					else
 | 
				
			||||||
@@ -45,9 +45,9 @@ fi
 | 
				
			|||||||
/bin/echo -e "\n### Changing version in version.py..."
 | 
					/bin/echo -e "\n### Changing version in version.py..."
 | 
				
			||||||
sed -i "s/__version__ = '.*'/__version__ = '$version'/" youtube_dl/version.py
 | 
					sed -i "s/__version__ = '.*'/__version__ = '$version'/" youtube_dl/version.py
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/bin/echo -e "\n### Committing README.md and youtube_dl/version.py..."
 | 
					/bin/echo -e "\n### Committing documentation and youtube_dl/version.py..."
 | 
				
			||||||
make README.md
 | 
					make README.md CONTRIBUTING.md supportedsites
 | 
				
			||||||
git add README.md youtube_dl/version.py
 | 
					git add README.md CONTRIBUTING.md docs/supportedsites.md youtube_dl/version.py
 | 
				
			||||||
git commit -m "release $version"
 | 
					git commit -m "release $version"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/bin/echo -e "\n### Now tagging, signing and pushing..."
 | 
					/bin/echo -e "\n### Now tagging, signing and pushing..."
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										577
									
								
								docs/supportedsites.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										577
									
								
								docs/supportedsites.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,577 @@
 | 
				
			|||||||
 | 
					# Supported sites
 | 
				
			||||||
 | 
					 - **1tv**: Первый канал
 | 
				
			||||||
 | 
					 - **1up.com**
 | 
				
			||||||
 | 
					 - **220.ro**
 | 
				
			||||||
 | 
					 - **24video**
 | 
				
			||||||
 | 
					 - **3sat**
 | 
				
			||||||
 | 
					 - **4tube**
 | 
				
			||||||
 | 
					 - **56.com**
 | 
				
			||||||
 | 
					 - **5min**
 | 
				
			||||||
 | 
					 - **8tracks**
 | 
				
			||||||
 | 
					 - **9gag**
 | 
				
			||||||
 | 
					 - **abc.net.au**
 | 
				
			||||||
 | 
					 - **Abc7News**
 | 
				
			||||||
 | 
					 - **AcademicEarth:Course**
 | 
				
			||||||
 | 
					 - **AddAnime**
 | 
				
			||||||
 | 
					 - **AdobeTV**
 | 
				
			||||||
 | 
					 - **AdultSwim**
 | 
				
			||||||
 | 
					 - **Aftenposten**
 | 
				
			||||||
 | 
					 - **Aftonbladet**
 | 
				
			||||||
 | 
					 - **AirMozilla**
 | 
				
			||||||
 | 
					 - **AlJazeera**
 | 
				
			||||||
 | 
					 - **Allocine**
 | 
				
			||||||
 | 
					 - **AlphaPorno**
 | 
				
			||||||
 | 
					 - **anitube.se**
 | 
				
			||||||
 | 
					 - **AnySex**
 | 
				
			||||||
 | 
					 - **Aparat**
 | 
				
			||||||
 | 
					 - **AppleDailyAnimationNews**
 | 
				
			||||||
 | 
					 - **AppleDailyRealtimeNews**
 | 
				
			||||||
 | 
					 - **AppleTrailers**
 | 
				
			||||||
 | 
					 - **archive.org**: archive.org videos
 | 
				
			||||||
 | 
					 - **ARD**
 | 
				
			||||||
 | 
					 - **ARD:mediathek**
 | 
				
			||||||
 | 
					 - **arte.tv**
 | 
				
			||||||
 | 
					 - **arte.tv:+7**
 | 
				
			||||||
 | 
					 - **arte.tv:concert**
 | 
				
			||||||
 | 
					 - **arte.tv:creative**
 | 
				
			||||||
 | 
					 - **arte.tv:ddc**
 | 
				
			||||||
 | 
					 - **arte.tv:embed**
 | 
				
			||||||
 | 
					 - **arte.tv:future**
 | 
				
			||||||
 | 
					 - **AtresPlayer**
 | 
				
			||||||
 | 
					 - **ATTTechChannel**
 | 
				
			||||||
 | 
					 - **audiomack**
 | 
				
			||||||
 | 
					 - **audiomack:album**
 | 
				
			||||||
 | 
					 - **Azubu**
 | 
				
			||||||
 | 
					 - **bambuser**
 | 
				
			||||||
 | 
					 - **bambuser:channel**
 | 
				
			||||||
 | 
					 - **Bandcamp**
 | 
				
			||||||
 | 
					 - **Bandcamp:album**
 | 
				
			||||||
 | 
					 - **bbc.co.uk**: BBC iPlayer
 | 
				
			||||||
 | 
					 - **Beeg**
 | 
				
			||||||
 | 
					 - **BehindKink**
 | 
				
			||||||
 | 
					 - **Bet**
 | 
				
			||||||
 | 
					 - **Bild**: Bild.de
 | 
				
			||||||
 | 
					 - **BiliBili**
 | 
				
			||||||
 | 
					 - **blinkx**
 | 
				
			||||||
 | 
					 - **blip.tv:user**
 | 
				
			||||||
 | 
					 - **BlipTV**
 | 
				
			||||||
 | 
					 - **Bloomberg**
 | 
				
			||||||
 | 
					 - **Bpb**: Bundeszentrale für politische Bildung
 | 
				
			||||||
 | 
					 - **BR**: Bayerischer Rundfunk Mediathek
 | 
				
			||||||
 | 
					 - **Break**
 | 
				
			||||||
 | 
					 - **Brightcove**
 | 
				
			||||||
 | 
					 - **BuzzFeed**
 | 
				
			||||||
 | 
					 - **BYUtv**
 | 
				
			||||||
 | 
					 - **Camdemy**
 | 
				
			||||||
 | 
					 - **CamdemyFolder**
 | 
				
			||||||
 | 
					 - **Canal13cl**
 | 
				
			||||||
 | 
					 - **canalc2.tv**
 | 
				
			||||||
 | 
					 - **Canalplus**: canalplus.fr, piwiplus.fr and d8.tv
 | 
				
			||||||
 | 
					 - **CBS**
 | 
				
			||||||
 | 
					 - **CBSNews**: CBS News
 | 
				
			||||||
 | 
					 - **CBSSports**
 | 
				
			||||||
 | 
					 - **CeskaTelevize**
 | 
				
			||||||
 | 
					 - **channel9**: Channel 9
 | 
				
			||||||
 | 
					 - **Chilloutzone**
 | 
				
			||||||
 | 
					 - **chirbit**
 | 
				
			||||||
 | 
					 - **chirbit:profile**
 | 
				
			||||||
 | 
					 - **Cinchcast**
 | 
				
			||||||
 | 
					 - **Cinemassacre**
 | 
				
			||||||
 | 
					 - **clipfish**
 | 
				
			||||||
 | 
					 - **cliphunter**
 | 
				
			||||||
 | 
					 - **Clipsyndicate**
 | 
				
			||||||
 | 
					 - **Cloudy**
 | 
				
			||||||
 | 
					 - **Clubic**
 | 
				
			||||||
 | 
					 - **cmt.com**
 | 
				
			||||||
 | 
					 - **CNET**
 | 
				
			||||||
 | 
					 - **CNN**
 | 
				
			||||||
 | 
					 - **CNNArticle**
 | 
				
			||||||
 | 
					 - **CNNBlogs**
 | 
				
			||||||
 | 
					 - **CollegeHumor**
 | 
				
			||||||
 | 
					 - **CollegeRama**
 | 
				
			||||||
 | 
					 - **ComCarCoff**
 | 
				
			||||||
 | 
					 - **ComedyCentral**
 | 
				
			||||||
 | 
					 - **ComedyCentralShows**: The Daily Show / The Colbert Report
 | 
				
			||||||
 | 
					 - **CondeNast**: Condé Nast media group: Condé Nast, GQ, Glamour, Vanity Fair, Vogue, W Magazine, WIRED
 | 
				
			||||||
 | 
					 - **Cracked**
 | 
				
			||||||
 | 
					 - **Criterion**
 | 
				
			||||||
 | 
					 - **Crunchyroll**
 | 
				
			||||||
 | 
					 - **crunchyroll:playlist**
 | 
				
			||||||
 | 
					 - **CSpan**: C-SPAN
 | 
				
			||||||
 | 
					 - **CtsNews**
 | 
				
			||||||
 | 
					 - **culturebox.francetvinfo.fr**
 | 
				
			||||||
 | 
					 - **dailymotion**
 | 
				
			||||||
 | 
					 - **dailymotion:playlist**
 | 
				
			||||||
 | 
					 - **dailymotion:user**
 | 
				
			||||||
 | 
					 - **daum.net**
 | 
				
			||||||
 | 
					 - **DBTV**
 | 
				
			||||||
 | 
					 - **DctpTv**
 | 
				
			||||||
 | 
					 - **DeezerPlaylist**
 | 
				
			||||||
 | 
					 - **defense.gouv.fr**
 | 
				
			||||||
 | 
					 - **Discovery**
 | 
				
			||||||
 | 
					 - **divxstage**: DivxStage
 | 
				
			||||||
 | 
					 - **Dotsub**
 | 
				
			||||||
 | 
					 - **DRBonanza**
 | 
				
			||||||
 | 
					 - **Dropbox**
 | 
				
			||||||
 | 
					 - **DrTuber**
 | 
				
			||||||
 | 
					 - **DRTV**
 | 
				
			||||||
 | 
					 - **Dump**
 | 
				
			||||||
 | 
					 - **dvtv**: http://video.aktualne.cz/
 | 
				
			||||||
 | 
					 - **EbaumsWorld**
 | 
				
			||||||
 | 
					 - **EchoMsk**
 | 
				
			||||||
 | 
					 - **eHow**
 | 
				
			||||||
 | 
					 - **Einthusan**
 | 
				
			||||||
 | 
					 - **eitb.tv**
 | 
				
			||||||
 | 
					 - **EllenTV**
 | 
				
			||||||
 | 
					 - **EllenTV:clips**
 | 
				
			||||||
 | 
					 - **ElPais**: El País
 | 
				
			||||||
 | 
					 - **Embedly**
 | 
				
			||||||
 | 
					 - **EMPFlix**
 | 
				
			||||||
 | 
					 - **Engadget**
 | 
				
			||||||
 | 
					 - **Eporner**
 | 
				
			||||||
 | 
					 - **EroProfile**
 | 
				
			||||||
 | 
					 - **Escapist**
 | 
				
			||||||
 | 
					 - **EveryonesMixtape**
 | 
				
			||||||
 | 
					 - **exfm**: ex.fm
 | 
				
			||||||
 | 
					 - **ExpoTV**
 | 
				
			||||||
 | 
					 - **ExtremeTube**
 | 
				
			||||||
 | 
					 - **facebook**
 | 
				
			||||||
 | 
					 - **faz.net**
 | 
				
			||||||
 | 
					 - **fc2**
 | 
				
			||||||
 | 
					 - **fernsehkritik.tv**
 | 
				
			||||||
 | 
					 - **fernsehkritik.tv:postecke**
 | 
				
			||||||
 | 
					 - **Firedrive**
 | 
				
			||||||
 | 
					 - **Firstpost**
 | 
				
			||||||
 | 
					 - **Flickr**
 | 
				
			||||||
 | 
					 - **Folketinget**: Folketinget (ft.dk; Danish parliament)
 | 
				
			||||||
 | 
					 - **Foxgay**
 | 
				
			||||||
 | 
					 - **FoxNews**
 | 
				
			||||||
 | 
					 - **france2.fr:generation-quoi**
 | 
				
			||||||
 | 
					 - **FranceCulture**
 | 
				
			||||||
 | 
					 - **FranceInter**
 | 
				
			||||||
 | 
					 - **francetv**: France 2, 3, 4, 5 and Ô
 | 
				
			||||||
 | 
					 - **francetvinfo.fr**
 | 
				
			||||||
 | 
					 - **Freesound**
 | 
				
			||||||
 | 
					 - **freespeech.org**
 | 
				
			||||||
 | 
					 - **FreeVideo**
 | 
				
			||||||
 | 
					 - **FunnyOrDie**
 | 
				
			||||||
 | 
					 - **Gamekings**
 | 
				
			||||||
 | 
					 - **GameOne**
 | 
				
			||||||
 | 
					 - **gameone:playlist**
 | 
				
			||||||
 | 
					 - **GameSpot**
 | 
				
			||||||
 | 
					 - **GameStar**
 | 
				
			||||||
 | 
					 - **Gametrailers**
 | 
				
			||||||
 | 
					 - **GDCVault**
 | 
				
			||||||
 | 
					 - **generic**: Generic downloader that works on some sites
 | 
				
			||||||
 | 
					 - **GiantBomb**
 | 
				
			||||||
 | 
					 - **Giga**
 | 
				
			||||||
 | 
					 - **Glide**: Glide mobile video messages (glide.me)
 | 
				
			||||||
 | 
					 - **Globo**
 | 
				
			||||||
 | 
					 - **GodTube**
 | 
				
			||||||
 | 
					 - **GoldenMoustache**
 | 
				
			||||||
 | 
					 - **Golem**
 | 
				
			||||||
 | 
					 - **GorillaVid**: GorillaVid.in, daclips.in, movpod.in and fastvideo.in
 | 
				
			||||||
 | 
					 - **Goshgay**
 | 
				
			||||||
 | 
					 - **Grooveshark**
 | 
				
			||||||
 | 
					 - **Groupon**
 | 
				
			||||||
 | 
					 - **Hark**
 | 
				
			||||||
 | 
					 - **HearThisAt**
 | 
				
			||||||
 | 
					 - **Heise**
 | 
				
			||||||
 | 
					 - **HellPorno**
 | 
				
			||||||
 | 
					 - **Helsinki**: helsinki.fi
 | 
				
			||||||
 | 
					 - **HentaiStigma**
 | 
				
			||||||
 | 
					 - **HistoricFilms**
 | 
				
			||||||
 | 
					 - **History**
 | 
				
			||||||
 | 
					 - **hitbox**
 | 
				
			||||||
 | 
					 - **hitbox:live**
 | 
				
			||||||
 | 
					 - **HornBunny**
 | 
				
			||||||
 | 
					 - **HostingBulk**
 | 
				
			||||||
 | 
					 - **HotNewHipHop**
 | 
				
			||||||
 | 
					 - **Howcast**
 | 
				
			||||||
 | 
					 - **HowStuffWorks**
 | 
				
			||||||
 | 
					 - **HuffPost**: Huffington Post
 | 
				
			||||||
 | 
					 - **Hypem**
 | 
				
			||||||
 | 
					 - **Iconosquare**
 | 
				
			||||||
 | 
					 - **ign.com**
 | 
				
			||||||
 | 
					 - **imdb**: Internet Movie Database trailers
 | 
				
			||||||
 | 
					 - **imdb:list**: Internet Movie Database lists
 | 
				
			||||||
 | 
					 - **Imgur**
 | 
				
			||||||
 | 
					 - **Ina**
 | 
				
			||||||
 | 
					 - **InfoQ**
 | 
				
			||||||
 | 
					 - **Instagram**
 | 
				
			||||||
 | 
					 - **instagram:user**: Instagram user profile
 | 
				
			||||||
 | 
					 - **InternetVideoArchive**
 | 
				
			||||||
 | 
					 - **IPrima**
 | 
				
			||||||
 | 
					 - **ivi**: ivi.ru
 | 
				
			||||||
 | 
					 - **ivi:compilation**: ivi.ru compilations
 | 
				
			||||||
 | 
					 - **Izlesene**
 | 
				
			||||||
 | 
					 - **JadoreCettePub**
 | 
				
			||||||
 | 
					 - **JeuxVideo**
 | 
				
			||||||
 | 
					 - **Jove**
 | 
				
			||||||
 | 
					 - **jpopsuki.tv**
 | 
				
			||||||
 | 
					 - **Jukebox**
 | 
				
			||||||
 | 
					 - **Kaltura**
 | 
				
			||||||
 | 
					 - **Kankan**
 | 
				
			||||||
 | 
					 - **Karaoketv**
 | 
				
			||||||
 | 
					 - **keek**
 | 
				
			||||||
 | 
					 - **KeezMovies**
 | 
				
			||||||
 | 
					 - **KhanAcademy**
 | 
				
			||||||
 | 
					 - **KickStarter**
 | 
				
			||||||
 | 
					 - **kontrtube**: KontrTube.ru - Труба зовёт
 | 
				
			||||||
 | 
					 - **KrasView**: Красвью
 | 
				
			||||||
 | 
					 - **Ku6**
 | 
				
			||||||
 | 
					 - **la7.tv**
 | 
				
			||||||
 | 
					 - **Laola1Tv**
 | 
				
			||||||
 | 
					 - **Letv**
 | 
				
			||||||
 | 
					 - **LetvPlaylist**
 | 
				
			||||||
 | 
					 - **LetvTv**
 | 
				
			||||||
 | 
					 - **lifenews**: LIFE | NEWS
 | 
				
			||||||
 | 
					 - **LiveLeak**
 | 
				
			||||||
 | 
					 - **livestream**
 | 
				
			||||||
 | 
					 - **livestream:original**
 | 
				
			||||||
 | 
					 - **LnkGo**
 | 
				
			||||||
 | 
					 - **lrt.lt**
 | 
				
			||||||
 | 
					 - **lynda**: lynda.com videos
 | 
				
			||||||
 | 
					 - **lynda:course**: lynda.com online courses
 | 
				
			||||||
 | 
					 - **m6**
 | 
				
			||||||
 | 
					 - **macgamestore**: MacGameStore trailers
 | 
				
			||||||
 | 
					 - **mailru**: Видео@Mail.Ru
 | 
				
			||||||
 | 
					 - **Malemotion**
 | 
				
			||||||
 | 
					 - **MDR**
 | 
				
			||||||
 | 
					 - **media.ccc.de**
 | 
				
			||||||
 | 
					 - **metacafe**
 | 
				
			||||||
 | 
					 - **Metacritic**
 | 
				
			||||||
 | 
					 - **Mgoon**
 | 
				
			||||||
 | 
					 - **Minhateca**
 | 
				
			||||||
 | 
					 - **MinistryGrid**
 | 
				
			||||||
 | 
					 - **mitele.es**
 | 
				
			||||||
 | 
					 - **mixcloud**
 | 
				
			||||||
 | 
					 - **MLB**
 | 
				
			||||||
 | 
					 - **MoeVideo**: LetitBit video services: moevideo.net, playreplay.net and videochart.net
 | 
				
			||||||
 | 
					 - **Mofosex**
 | 
				
			||||||
 | 
					 - **Mojvideo**
 | 
				
			||||||
 | 
					 - **Moniker**: allmyvideos.net and vidspot.net
 | 
				
			||||||
 | 
					 - **mooshare**: Mooshare.biz
 | 
				
			||||||
 | 
					 - **Morningstar**: morningstar.com
 | 
				
			||||||
 | 
					 - **Motherless**
 | 
				
			||||||
 | 
					 - **Motorsport**: motorsport.com
 | 
				
			||||||
 | 
					 - **MovieClips**
 | 
				
			||||||
 | 
					 - **Moviezine**
 | 
				
			||||||
 | 
					 - **movshare**: MovShare
 | 
				
			||||||
 | 
					 - **MPORA**
 | 
				
			||||||
 | 
					 - **MTV**
 | 
				
			||||||
 | 
					 - **mtviggy.com**
 | 
				
			||||||
 | 
					 - **mtvservices:embedded**
 | 
				
			||||||
 | 
					 - **MuenchenTV**: münchen.tv
 | 
				
			||||||
 | 
					 - **MusicPlayOn**
 | 
				
			||||||
 | 
					 - **MusicVault**
 | 
				
			||||||
 | 
					 - **muzu.tv**
 | 
				
			||||||
 | 
					 - **MySpace**
 | 
				
			||||||
 | 
					 - **MySpace:album**
 | 
				
			||||||
 | 
					 - **MySpass**
 | 
				
			||||||
 | 
					 - **myvideo**
 | 
				
			||||||
 | 
					 - **MyVidster**
 | 
				
			||||||
 | 
					 - **n-tv.de**
 | 
				
			||||||
 | 
					 - **NationalGeographic**
 | 
				
			||||||
 | 
					 - **Naver**
 | 
				
			||||||
 | 
					 - **NBA**
 | 
				
			||||||
 | 
					 - **NBC**
 | 
				
			||||||
 | 
					 - **NBCNews**
 | 
				
			||||||
 | 
					 - **ndr**: NDR.de - Mediathek
 | 
				
			||||||
 | 
					 - **NDTV**
 | 
				
			||||||
 | 
					 - **NerdCubedFeed**
 | 
				
			||||||
 | 
					 - **Nerdist**
 | 
				
			||||||
 | 
					 - **Netzkino**
 | 
				
			||||||
 | 
					 - **Newgrounds**
 | 
				
			||||||
 | 
					 - **Newstube**
 | 
				
			||||||
 | 
					 - **NextMedia**
 | 
				
			||||||
 | 
					 - **NextMediaActionNews**
 | 
				
			||||||
 | 
					 - **nfb**: National Film Board of Canada
 | 
				
			||||||
 | 
					 - **nfl.com**
 | 
				
			||||||
 | 
					 - **nhl.com**
 | 
				
			||||||
 | 
					 - **nhl.com:news**: NHL news
 | 
				
			||||||
 | 
					 - **nhl.com:videocenter**: NHL videocenter category
 | 
				
			||||||
 | 
					 - **niconico**: ニコニコ動画
 | 
				
			||||||
 | 
					 - **NiconicoPlaylist**
 | 
				
			||||||
 | 
					 - **Noco**
 | 
				
			||||||
 | 
					 - **Normalboots**
 | 
				
			||||||
 | 
					 - **NosVideo**
 | 
				
			||||||
 | 
					 - **novamov**: NovaMov
 | 
				
			||||||
 | 
					 - **Nowness**
 | 
				
			||||||
 | 
					 - **nowvideo**: NowVideo
 | 
				
			||||||
 | 
					 - **npo.nl**
 | 
				
			||||||
 | 
					 - **npo.nl:live**
 | 
				
			||||||
 | 
					 - **npo.nl:radio**
 | 
				
			||||||
 | 
					 - **npo.nl:radio:fragment**
 | 
				
			||||||
 | 
					 - **NRK**
 | 
				
			||||||
 | 
					 - **NRKTV**
 | 
				
			||||||
 | 
					 - **ntv.ru**
 | 
				
			||||||
 | 
					 - **Nuvid**
 | 
				
			||||||
 | 
					 - **NYTimes**
 | 
				
			||||||
 | 
					 - **ocw.mit.edu**
 | 
				
			||||||
 | 
					 - **Odnoklassniki**
 | 
				
			||||||
 | 
					 - **OktoberfestTV**
 | 
				
			||||||
 | 
					 - **on.aol.com**
 | 
				
			||||||
 | 
					 - **Ooyala**
 | 
				
			||||||
 | 
					 - **OpenFilm**
 | 
				
			||||||
 | 
					 - **orf:fm4**: radio FM4
 | 
				
			||||||
 | 
					 - **orf:oe1**: Radio Österreich 1
 | 
				
			||||||
 | 
					 - **orf:tvthek**: ORF TVthek
 | 
				
			||||||
 | 
					 - **parliamentlive.tv**: UK parliament videos
 | 
				
			||||||
 | 
					 - **Patreon**
 | 
				
			||||||
 | 
					 - **PBS**
 | 
				
			||||||
 | 
					 - **Phoenix**
 | 
				
			||||||
 | 
					 - **Photobucket**
 | 
				
			||||||
 | 
					 - **PlanetaPlay**
 | 
				
			||||||
 | 
					 - **play.fm**
 | 
				
			||||||
 | 
					 - **played.to**
 | 
				
			||||||
 | 
					 - **Playvid**
 | 
				
			||||||
 | 
					 - **plus.google**: Google Plus
 | 
				
			||||||
 | 
					 - **pluzz.francetv.fr**
 | 
				
			||||||
 | 
					 - **podomatic**
 | 
				
			||||||
 | 
					 - **PornHd**
 | 
				
			||||||
 | 
					 - **PornHub**
 | 
				
			||||||
 | 
					 - **PornHubPlaylist**
 | 
				
			||||||
 | 
					 - **Pornotube**
 | 
				
			||||||
 | 
					 - **PornoXO**
 | 
				
			||||||
 | 
					 - **PromptFile**
 | 
				
			||||||
 | 
					 - **prosiebensat1**: ProSiebenSat.1 Digital
 | 
				
			||||||
 | 
					 - **Puls4**
 | 
				
			||||||
 | 
					 - **Pyvideo**
 | 
				
			||||||
 | 
					 - **QuickVid**
 | 
				
			||||||
 | 
					 - **R7**
 | 
				
			||||||
 | 
					 - **radio.de**
 | 
				
			||||||
 | 
					 - **radiobremen**
 | 
				
			||||||
 | 
					 - **radiofrance**
 | 
				
			||||||
 | 
					 - **Rai**
 | 
				
			||||||
 | 
					 - **RBMARadio**
 | 
				
			||||||
 | 
					 - **RedTube**
 | 
				
			||||||
 | 
					 - **Restudy**
 | 
				
			||||||
 | 
					 - **ReverbNation**
 | 
				
			||||||
 | 
					 - **RingTV**
 | 
				
			||||||
 | 
					 - **RottenTomatoes**
 | 
				
			||||||
 | 
					 - **Roxwel**
 | 
				
			||||||
 | 
					 - **RTBF**
 | 
				
			||||||
 | 
					 - **Rte**
 | 
				
			||||||
 | 
					 - **rtl.nl**: rtl.nl and rtlxl.nl
 | 
				
			||||||
 | 
					 - **RTL2**
 | 
				
			||||||
 | 
					 - **RTLnow**
 | 
				
			||||||
 | 
					 - **RTP**
 | 
				
			||||||
 | 
					 - **RTS**: RTS.ch
 | 
				
			||||||
 | 
					 - **rtve.es:alacarta**: RTVE a la carta
 | 
				
			||||||
 | 
					 - **rtve.es:live**: RTVE.es live streams
 | 
				
			||||||
 | 
					 - **RUHD**
 | 
				
			||||||
 | 
					 - **rutube**: Rutube videos
 | 
				
			||||||
 | 
					 - **rutube:channel**: Rutube channels
 | 
				
			||||||
 | 
					 - **rutube:embed**: Rutube embedded videos
 | 
				
			||||||
 | 
					 - **rutube:movie**: Rutube movies
 | 
				
			||||||
 | 
					 - **rutube:person**: Rutube person videos
 | 
				
			||||||
 | 
					 - **RUTV**: RUTV.RU
 | 
				
			||||||
 | 
					 - **Sandia**: Sandia National Laboratories
 | 
				
			||||||
 | 
					 - **Sapo**: SAPO Vídeos
 | 
				
			||||||
 | 
					 - **savefrom.net**
 | 
				
			||||||
 | 
					 - **SBS**: sbs.com.au
 | 
				
			||||||
 | 
					 - **SciVee**
 | 
				
			||||||
 | 
					 - **screen.yahoo:search**: Yahoo screen search
 | 
				
			||||||
 | 
					 - **Screencast**
 | 
				
			||||||
 | 
					 - **ScreencastOMatic**
 | 
				
			||||||
 | 
					 - **ScreenwaveMedia**
 | 
				
			||||||
 | 
					 - **ServingSys**
 | 
				
			||||||
 | 
					 - **Sexu**
 | 
				
			||||||
 | 
					 - **SexyKarma**: Sexy Karma and Watch Indian Porn
 | 
				
			||||||
 | 
					 - **Shared**
 | 
				
			||||||
 | 
					 - **ShareSix**
 | 
				
			||||||
 | 
					 - **Sina**
 | 
				
			||||||
 | 
					 - **Slideshare**
 | 
				
			||||||
 | 
					 - **Slutload**
 | 
				
			||||||
 | 
					 - **smotri**: Smotri.com
 | 
				
			||||||
 | 
					 - **smotri:broadcast**: Smotri.com broadcasts
 | 
				
			||||||
 | 
					 - **smotri:community**: Smotri.com community videos
 | 
				
			||||||
 | 
					 - **smotri:user**: Smotri.com user videos
 | 
				
			||||||
 | 
					 - **Snotr**
 | 
				
			||||||
 | 
					 - **Sockshare**
 | 
				
			||||||
 | 
					 - **Sohu**
 | 
				
			||||||
 | 
					 - **soundcloud**
 | 
				
			||||||
 | 
					 - **soundcloud:playlist**
 | 
				
			||||||
 | 
					 - **soundcloud:set**
 | 
				
			||||||
 | 
					 - **soundcloud:user**
 | 
				
			||||||
 | 
					 - **soundgasm**
 | 
				
			||||||
 | 
					 - **soundgasm:profile**
 | 
				
			||||||
 | 
					 - **southpark.cc.com**
 | 
				
			||||||
 | 
					 - **southpark.de**
 | 
				
			||||||
 | 
					 - **Space**
 | 
				
			||||||
 | 
					 - **Spankwire**
 | 
				
			||||||
 | 
					 - **Spiegel**
 | 
				
			||||||
 | 
					 - **Spiegel:Article**: Articles on spiegel.de
 | 
				
			||||||
 | 
					 - **Spiegeltv**
 | 
				
			||||||
 | 
					 - **Spike**
 | 
				
			||||||
 | 
					 - **Sport5**
 | 
				
			||||||
 | 
					 - **SportBox**
 | 
				
			||||||
 | 
					 - **SportDeutschland**
 | 
				
			||||||
 | 
					 - **SRMediathek**: Saarländischer Rundfunk
 | 
				
			||||||
 | 
					 - **stanfordoc**: Stanford Open ClassRoom
 | 
				
			||||||
 | 
					 - **Steam**
 | 
				
			||||||
 | 
					 - **streamcloud.eu**
 | 
				
			||||||
 | 
					 - **StreamCZ**
 | 
				
			||||||
 | 
					 - **StreetVoice**
 | 
				
			||||||
 | 
					 - **SunPorno**
 | 
				
			||||||
 | 
					 - **SVTPlay**: SVT Play and Öppet arkiv
 | 
				
			||||||
 | 
					 - **SWRMediathek**
 | 
				
			||||||
 | 
					 - **Syfy**
 | 
				
			||||||
 | 
					 - **SztvHu**
 | 
				
			||||||
 | 
					 - **Tagesschau**
 | 
				
			||||||
 | 
					 - **Tapely**
 | 
				
			||||||
 | 
					 - **Tass**
 | 
				
			||||||
 | 
					 - **teachertube**: teachertube.com videos
 | 
				
			||||||
 | 
					 - **teachertube:user:collection**: teachertube.com user and collection videos
 | 
				
			||||||
 | 
					 - **TeachingChannel**
 | 
				
			||||||
 | 
					 - **Teamcoco**
 | 
				
			||||||
 | 
					 - **TeamFour**
 | 
				
			||||||
 | 
					 - **TechTalks**
 | 
				
			||||||
 | 
					 - **techtv.mit.edu**
 | 
				
			||||||
 | 
					 - **TED**
 | 
				
			||||||
 | 
					 - **tegenlicht.vpro.nl**
 | 
				
			||||||
 | 
					 - **TeleBruxelles**
 | 
				
			||||||
 | 
					 - **telecinco.es**
 | 
				
			||||||
 | 
					 - **TeleMB**
 | 
				
			||||||
 | 
					 - **TeleTask**
 | 
				
			||||||
 | 
					 - **TenPlay**
 | 
				
			||||||
 | 
					 - **TestTube**
 | 
				
			||||||
 | 
					 - **TF1**
 | 
				
			||||||
 | 
					 - **TheOnion**
 | 
				
			||||||
 | 
					 - **ThePlatform**
 | 
				
			||||||
 | 
					 - **TheSixtyOne**
 | 
				
			||||||
 | 
					 - **ThisAV**
 | 
				
			||||||
 | 
					 - **THVideo**
 | 
				
			||||||
 | 
					 - **THVideoPlaylist**
 | 
				
			||||||
 | 
					 - **tinypic**: tinypic.com videos
 | 
				
			||||||
 | 
					 - **tlc.com**
 | 
				
			||||||
 | 
					 - **tlc.de**
 | 
				
			||||||
 | 
					 - **TMZ**
 | 
				
			||||||
 | 
					 - **TNAFlix**
 | 
				
			||||||
 | 
					 - **tou.tv**
 | 
				
			||||||
 | 
					 - **Toypics**: Toypics user profile
 | 
				
			||||||
 | 
					 - **ToypicsUser**: Toypics user profile
 | 
				
			||||||
 | 
					 - **TrailerAddict** (Currently broken)
 | 
				
			||||||
 | 
					 - **Trilulilu**
 | 
				
			||||||
 | 
					 - **TruTube**
 | 
				
			||||||
 | 
					 - **Tube8**
 | 
				
			||||||
 | 
					 - **Tudou**
 | 
				
			||||||
 | 
					 - **Tumblr**
 | 
				
			||||||
 | 
					 - **TuneIn**
 | 
				
			||||||
 | 
					 - **Turbo**
 | 
				
			||||||
 | 
					 - **Tutv**
 | 
				
			||||||
 | 
					 - **tv.dfb.de**
 | 
				
			||||||
 | 
					 - **TV4**: tv4.se and tv4play.se
 | 
				
			||||||
 | 
					 - **tvigle**: Интернет-телевидение Tvigle.ru
 | 
				
			||||||
 | 
					 - **tvp.pl**
 | 
				
			||||||
 | 
					 - **tvp.pl:Series**
 | 
				
			||||||
 | 
					 - **TVPlay**: TV3Play and related services
 | 
				
			||||||
 | 
					 - **Tweakers**
 | 
				
			||||||
 | 
					 - **twitch:bookmarks**
 | 
				
			||||||
 | 
					 - **twitch:chapter**
 | 
				
			||||||
 | 
					 - **twitch:past_broadcasts**
 | 
				
			||||||
 | 
					 - **twitch:profile**
 | 
				
			||||||
 | 
					 - **twitch:stream**
 | 
				
			||||||
 | 
					 - **twitch:video**
 | 
				
			||||||
 | 
					 - **twitch:vod**
 | 
				
			||||||
 | 
					 - **Ubu**
 | 
				
			||||||
 | 
					 - **udemy**
 | 
				
			||||||
 | 
					 - **udemy:course**
 | 
				
			||||||
 | 
					 - **Unistra**
 | 
				
			||||||
 | 
					 - **Urort**: NRK P3 Urørt
 | 
				
			||||||
 | 
					 - **ustream**
 | 
				
			||||||
 | 
					 - **ustream:channel**
 | 
				
			||||||
 | 
					 - **Vbox7**
 | 
				
			||||||
 | 
					 - **VeeHD**
 | 
				
			||||||
 | 
					 - **Veoh**
 | 
				
			||||||
 | 
					 - **Vesti**: Вести.Ru
 | 
				
			||||||
 | 
					 - **Vevo**
 | 
				
			||||||
 | 
					 - **VGTV**
 | 
				
			||||||
 | 
					 - **vh1.com**
 | 
				
			||||||
 | 
					 - **Vice**
 | 
				
			||||||
 | 
					 - **Viddler**
 | 
				
			||||||
 | 
					 - **video.google:search**: Google Video search
 | 
				
			||||||
 | 
					 - **video.mit.edu**
 | 
				
			||||||
 | 
					 - **VideoBam**
 | 
				
			||||||
 | 
					 - **VideoDetective**
 | 
				
			||||||
 | 
					 - **videofy.me**
 | 
				
			||||||
 | 
					 - **videolectures.net**
 | 
				
			||||||
 | 
					 - **VideoMega**
 | 
				
			||||||
 | 
					 - **VideoPremium**
 | 
				
			||||||
 | 
					 - **VideoTt**: video.tt - Your True Tube
 | 
				
			||||||
 | 
					 - **videoweed**: VideoWeed
 | 
				
			||||||
 | 
					 - **Vidme**
 | 
				
			||||||
 | 
					 - **Vidzi**
 | 
				
			||||||
 | 
					 - **vier**
 | 
				
			||||||
 | 
					 - **vier:videos**
 | 
				
			||||||
 | 
					 - **viki**
 | 
				
			||||||
 | 
					 - **vimeo**
 | 
				
			||||||
 | 
					 - **vimeo:album**
 | 
				
			||||||
 | 
					 - **vimeo:channel**
 | 
				
			||||||
 | 
					 - **vimeo:group**
 | 
				
			||||||
 | 
					 - **vimeo:likes**: Vimeo user likes
 | 
				
			||||||
 | 
					 - **vimeo:review**: Review pages on vimeo
 | 
				
			||||||
 | 
					 - **vimeo:user**
 | 
				
			||||||
 | 
					 - **vimeo:watchlater**: Vimeo watch later list, "vimeowatchlater" keyword (requires authentication)
 | 
				
			||||||
 | 
					 - **Vimple**: Vimple.ru
 | 
				
			||||||
 | 
					 - **Vine**
 | 
				
			||||||
 | 
					 - **vine:user**
 | 
				
			||||||
 | 
					 - **vk.com**
 | 
				
			||||||
 | 
					 - **vk.com:user-videos**: vk.com:All of a user's videos
 | 
				
			||||||
 | 
					 - **Vodlocker**
 | 
				
			||||||
 | 
					 - **Vporn**
 | 
				
			||||||
 | 
					 - **VRT**
 | 
				
			||||||
 | 
					 - **vube**: Vube.com
 | 
				
			||||||
 | 
					 - **VuClip**
 | 
				
			||||||
 | 
					 - **vulture.com**
 | 
				
			||||||
 | 
					 - **Walla**
 | 
				
			||||||
 | 
					 - **WashingtonPost**
 | 
				
			||||||
 | 
					 - **wat.tv**
 | 
				
			||||||
 | 
					 - **WayOfTheMaster**
 | 
				
			||||||
 | 
					 - **WDR**
 | 
				
			||||||
 | 
					 - **wdr:mobile**
 | 
				
			||||||
 | 
					 - **WDRMaus**: Sendung mit der Maus
 | 
				
			||||||
 | 
					 - **WebOfStories**
 | 
				
			||||||
 | 
					 - **Weibo**
 | 
				
			||||||
 | 
					 - **Wimp**
 | 
				
			||||||
 | 
					 - **Wistia**
 | 
				
			||||||
 | 
					 - **WorldStarHipHop**
 | 
				
			||||||
 | 
					 - **wrzuta.pl**
 | 
				
			||||||
 | 
					 - **WSJ**: Wall Street Journal
 | 
				
			||||||
 | 
					 - **XBef**
 | 
				
			||||||
 | 
					 - **XboxClips**
 | 
				
			||||||
 | 
					 - **XHamster**
 | 
				
			||||||
 | 
					 - **XMinus**
 | 
				
			||||||
 | 
					 - **XNXX**
 | 
				
			||||||
 | 
					 - **XTube**
 | 
				
			||||||
 | 
					 - **XTubeUser**: XTube user profile
 | 
				
			||||||
 | 
					 - **Xuite**
 | 
				
			||||||
 | 
					 - **XVideos**
 | 
				
			||||||
 | 
					 - **XXXYMovies**
 | 
				
			||||||
 | 
					 - **Yahoo**: Yahoo screen and movies
 | 
				
			||||||
 | 
					 - **Yam**
 | 
				
			||||||
 | 
					 - **YesJapan**
 | 
				
			||||||
 | 
					 - **Ynet**
 | 
				
			||||||
 | 
					 - **YouJizz**
 | 
				
			||||||
 | 
					 - **Youku**
 | 
				
			||||||
 | 
					 - **YouPorn**
 | 
				
			||||||
 | 
					 - **YourUpload**
 | 
				
			||||||
 | 
					 - **youtube**: YouTube.com
 | 
				
			||||||
 | 
					 - **youtube:channel**: YouTube.com channels
 | 
				
			||||||
 | 
					 - **youtube:favorites**: YouTube.com favourite videos, ":ytfav" for short (requires authentication)
 | 
				
			||||||
 | 
					 - **youtube:history**: Youtube watch history, ":ythistory" for short (requires authentication)
 | 
				
			||||||
 | 
					 - **youtube:playlist**: YouTube.com playlists
 | 
				
			||||||
 | 
					 - **youtube:recommended**: YouTube.com recommended videos, ":ytrec" for short (requires authentication)
 | 
				
			||||||
 | 
					 - **youtube:search**: YouTube.com searches
 | 
				
			||||||
 | 
					 - **youtube:search:date**: YouTube.com searches, newest videos first
 | 
				
			||||||
 | 
					 - **youtube:search_url**: YouTube.com search URLs
 | 
				
			||||||
 | 
					 - **youtube:show**: YouTube.com (multi-season) shows
 | 
				
			||||||
 | 
					 - **youtube:subscriptions**: YouTube.com subscriptions feed, "ytsubs" keyword (requires authentication)
 | 
				
			||||||
 | 
					 - **youtube:user**: YouTube.com user videos (URL or "ytuser" keyword)
 | 
				
			||||||
 | 
					 - **youtube:watch_later**: Youtube watch later list, ":ytwatchlater" for short (requires authentication)
 | 
				
			||||||
 | 
					 - **Zapiks**
 | 
				
			||||||
 | 
					 - **ZDF**
 | 
				
			||||||
 | 
					 - **ZDFChannel**
 | 
				
			||||||
 | 
					 - **zingmp3:album**: mp3.zing.vn albums
 | 
				
			||||||
 | 
					 - **zingmp3:song**: mp3.zing.vn songs
 | 
				
			||||||
@@ -1,2 +1,6 @@
 | 
				
			|||||||
[wheel]
 | 
					[wheel]
 | 
				
			||||||
universal = True
 | 
					universal = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[flake8]
 | 
				
			||||||
 | 
					exclude = youtube_dl/extractor/__init__.py,devscripts/buildserver.py,setup.py,build,.git
 | 
				
			||||||
 | 
					ignore = E402,E501,E731
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -82,24 +82,14 @@ class FakeYDL(YoutubeDL):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
def gettestcases(include_onlymatching=False):
 | 
					def gettestcases(include_onlymatching=False):
 | 
				
			||||||
    for ie in youtube_dl.extractor.gen_extractors():
 | 
					    for ie in youtube_dl.extractor.gen_extractors():
 | 
				
			||||||
        t = getattr(ie, '_TEST', None)
 | 
					        for tc in ie.get_testcases(include_onlymatching):
 | 
				
			||||||
        if t:
 | 
					            yield tc
 | 
				
			||||||
            assert not hasattr(ie, '_TESTS'), \
 | 
					 | 
				
			||||||
                '%s has _TEST and _TESTS' % type(ie).__name__
 | 
					 | 
				
			||||||
            tests = [t]
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            tests = getattr(ie, '_TESTS', [])
 | 
					 | 
				
			||||||
        for t in tests:
 | 
					 | 
				
			||||||
            if not include_onlymatching and t.get('only_matching', False):
 | 
					 | 
				
			||||||
                continue
 | 
					 | 
				
			||||||
            t['name'] = type(ie).__name__[:-len('IE')]
 | 
					 | 
				
			||||||
            yield t
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
md5 = lambda s: hashlib.md5(s.encode('utf-8')).hexdigest()
 | 
					md5 = lambda s: hashlib.md5(s.encode('utf-8')).hexdigest()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def expect_info_dict(self, expected_dict, got_dict):
 | 
					def expect_info_dict(self, got_dict, expected_dict):
 | 
				
			||||||
    for info_field, expected in expected_dict.items():
 | 
					    for info_field, expected in expected_dict.items():
 | 
				
			||||||
        if isinstance(expected, compat_str) and expected.startswith('re:'):
 | 
					        if isinstance(expected, compat_str) and expected.startswith('re:'):
 | 
				
			||||||
            got = got_dict.get(info_field)
 | 
					            got = got_dict.get(info_field)
 | 
				
			||||||
@@ -113,6 +103,26 @@ def expect_info_dict(self, expected_dict, got_dict):
 | 
				
			|||||||
            self.assertTrue(
 | 
					            self.assertTrue(
 | 
				
			||||||
                match_rex.match(got),
 | 
					                match_rex.match(got),
 | 
				
			||||||
                'field %s (value: %r) should match %r' % (info_field, got, match_str))
 | 
					                'field %s (value: %r) should match %r' % (info_field, got, match_str))
 | 
				
			||||||
 | 
					        elif isinstance(expected, compat_str) and expected.startswith('startswith:'):
 | 
				
			||||||
 | 
					            got = got_dict.get(info_field)
 | 
				
			||||||
 | 
					            start_str = expected[len('startswith:'):]
 | 
				
			||||||
 | 
					            self.assertTrue(
 | 
				
			||||||
 | 
					                isinstance(got, compat_str),
 | 
				
			||||||
 | 
					                'Expected a %s object, but got %s for field %s' % (
 | 
				
			||||||
 | 
					                    compat_str.__name__, type(got).__name__, info_field))
 | 
				
			||||||
 | 
					            self.assertTrue(
 | 
				
			||||||
 | 
					                got.startswith(start_str),
 | 
				
			||||||
 | 
					                'field %s (value: %r) should start with %r' % (info_field, got, start_str))
 | 
				
			||||||
 | 
					        elif isinstance(expected, compat_str) and expected.startswith('contains:'):
 | 
				
			||||||
 | 
					            got = got_dict.get(info_field)
 | 
				
			||||||
 | 
					            contains_str = expected[len('contains:'):]
 | 
				
			||||||
 | 
					            self.assertTrue(
 | 
				
			||||||
 | 
					                isinstance(got, compat_str),
 | 
				
			||||||
 | 
					                'Expected a %s object, but got %s for field %s' % (
 | 
				
			||||||
 | 
					                    compat_str.__name__, type(got).__name__, info_field))
 | 
				
			||||||
 | 
					            self.assertTrue(
 | 
				
			||||||
 | 
					                contains_str in got,
 | 
				
			||||||
 | 
					                'field %s (value: %r) should contain %r' % (info_field, got, contains_str))
 | 
				
			||||||
        elif isinstance(expected, type):
 | 
					        elif isinstance(expected, type):
 | 
				
			||||||
            got = got_dict.get(info_field)
 | 
					            got = got_dict.get(info_field)
 | 
				
			||||||
            self.assertTrue(isinstance(got, expected),
 | 
					            self.assertTrue(isinstance(got, expected),
 | 
				
			||||||
@@ -120,6 +130,20 @@ def expect_info_dict(self, expected_dict, got_dict):
 | 
				
			|||||||
        else:
 | 
					        else:
 | 
				
			||||||
            if isinstance(expected, compat_str) and expected.startswith('md5:'):
 | 
					            if isinstance(expected, compat_str) and expected.startswith('md5:'):
 | 
				
			||||||
                got = 'md5:' + md5(got_dict.get(info_field))
 | 
					                got = 'md5:' + md5(got_dict.get(info_field))
 | 
				
			||||||
 | 
					            elif isinstance(expected, compat_str) and expected.startswith('mincount:'):
 | 
				
			||||||
 | 
					                got = got_dict.get(info_field)
 | 
				
			||||||
 | 
					                self.assertTrue(
 | 
				
			||||||
 | 
					                    isinstance(got, list),
 | 
				
			||||||
 | 
					                    'Expected field %s to be a list, but it is of type %s' % (
 | 
				
			||||||
 | 
					                        info_field, type(got).__name__))
 | 
				
			||||||
 | 
					                expected_num = int(expected.partition(':')[2])
 | 
				
			||||||
 | 
					                assertGreaterEqual(
 | 
				
			||||||
 | 
					                    self, len(got), expected_num,
 | 
				
			||||||
 | 
					                    'Expected %d items in field %s, but only got %d' % (
 | 
				
			||||||
 | 
					                        expected_num, info_field, len(got)
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                got = got_dict.get(info_field)
 | 
					                got = got_dict.get(info_field)
 | 
				
			||||||
            self.assertEqual(expected, got,
 | 
					            self.assertEqual(expected, got,
 | 
				
			||||||
@@ -136,7 +160,7 @@ def expect_info_dict(self, expected_dict, got_dict):
 | 
				
			|||||||
    # Are checkable fields missing from the test case definition?
 | 
					    # Are checkable fields missing from the test case definition?
 | 
				
			||||||
    test_info_dict = dict((key, value if not isinstance(value, compat_str) or len(value) < 250 else 'md5:' + md5(value))
 | 
					    test_info_dict = dict((key, value if not isinstance(value, compat_str) or len(value) < 250 else 'md5:' + md5(value))
 | 
				
			||||||
                          for key, value in got_dict.items()
 | 
					                          for key, value in got_dict.items()
 | 
				
			||||||
                          if value and key in ('title', 'description', 'uploader', 'upload_date', 'timestamp', 'uploader_id', 'location'))
 | 
					                          if value and key in ('id', 'title', 'description', 'uploader', 'upload_date', 'timestamp', 'uploader_id', 'location'))
 | 
				
			||||||
    missing_keys = set(test_info_dict.keys()) - set(expected_dict.keys())
 | 
					    missing_keys = set(test_info_dict.keys()) - set(expected_dict.keys())
 | 
				
			||||||
    if missing_keys:
 | 
					    if missing_keys:
 | 
				
			||||||
        def _repr(v):
 | 
					        def _repr(v):
 | 
				
			||||||
@@ -144,11 +168,19 @@ def expect_info_dict(self, expected_dict, got_dict):
 | 
				
			|||||||
                return "'%s'" % v.replace('\\', '\\\\').replace("'", "\\'").replace('\n', '\\n')
 | 
					                return "'%s'" % v.replace('\\', '\\\\').replace("'", "\\'").replace('\n', '\\n')
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                return repr(v)
 | 
					                return repr(v)
 | 
				
			||||||
        info_dict_str = ''.join(
 | 
					        info_dict_str = ''
 | 
				
			||||||
            '    %s: %s,\n' % (_repr(k), _repr(v))
 | 
					        if len(missing_keys) != len(expected_dict):
 | 
				
			||||||
            for k, v in test_info_dict.items())
 | 
					            info_dict_str += ''.join(
 | 
				
			||||||
 | 
					                '    %s: %s,\n' % (_repr(k), _repr(v))
 | 
				
			||||||
 | 
					                for k, v in test_info_dict.items() if k not in missing_keys)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if info_dict_str:
 | 
				
			||||||
 | 
					                info_dict_str += '\n'
 | 
				
			||||||
 | 
					        info_dict_str += ''.join(
 | 
				
			||||||
 | 
					            '    %s: %s,\n' % (_repr(k), _repr(test_info_dict[k]))
 | 
				
			||||||
 | 
					            for k in missing_keys)
 | 
				
			||||||
        write_string(
 | 
					        write_string(
 | 
				
			||||||
            '\n\'info_dict\': {\n' + info_dict_str + '}\n', out=sys.stderr)
 | 
					            '\n\'info_dict\': {\n' + info_dict_str + '},\n', out=sys.stderr)
 | 
				
			||||||
        self.assertFalse(
 | 
					        self.assertFalse(
 | 
				
			||||||
            missing_keys,
 | 
					            missing_keys,
 | 
				
			||||||
            'Missing keys in test definition: %s' % (
 | 
					            'Missing keys in test definition: %s' % (
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,7 +28,7 @@
 | 
				
			|||||||
    "retries": 10, 
 | 
					    "retries": 10, 
 | 
				
			||||||
    "simulate": false, 
 | 
					    "simulate": false, 
 | 
				
			||||||
    "subtitleslang": null, 
 | 
					    "subtitleslang": null, 
 | 
				
			||||||
    "subtitlesformat": "srt",
 | 
					    "subtitlesformat": "best",
 | 
				
			||||||
    "test": true, 
 | 
					    "test": true, 
 | 
				
			||||||
    "updatetime": true, 
 | 
					    "updatetime": true, 
 | 
				
			||||||
    "usenetrc": false, 
 | 
					    "usenetrc": false, 
 | 
				
			||||||
@@ -39,5 +39,6 @@
 | 
				
			|||||||
    "writesubtitles": false,
 | 
					    "writesubtitles": false,
 | 
				
			||||||
    "allsubtitles": false,
 | 
					    "allsubtitles": false,
 | 
				
			||||||
    "listssubtitles": false,
 | 
					    "listssubtitles": false,
 | 
				
			||||||
    "socket_timeout": 20
 | 
					    "socket_timeout": 20,
 | 
				
			||||||
 | 
					    "fixup": "never"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -40,5 +40,23 @@ class TestInfoExtractor(unittest.TestCase):
 | 
				
			|||||||
        self.assertEqual(ie._og_search_description(html), 'Some video\'s description ')
 | 
					        self.assertEqual(ie._og_search_description(html), 'Some video\'s description ')
 | 
				
			||||||
        self.assertEqual(ie._og_search_thumbnail(html), 'http://domain.com/pic.jpg?key1=val1&key2=val2')
 | 
					        self.assertEqual(ie._og_search_thumbnail(html), 'http://domain.com/pic.jpg?key1=val1&key2=val2')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_html_search_meta(self):
 | 
				
			||||||
 | 
					        ie = self.ie
 | 
				
			||||||
 | 
					        html = '''
 | 
				
			||||||
 | 
					            <meta name="a" content="1" />
 | 
				
			||||||
 | 
					            <meta name='b' content='2'>
 | 
				
			||||||
 | 
					            <meta name="c" content='3'>
 | 
				
			||||||
 | 
					            <meta name=d content='4'>
 | 
				
			||||||
 | 
					            <meta property="e" content='5' >
 | 
				
			||||||
 | 
					            <meta content="6" name="f">
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertEqual(ie._html_search_meta('a', html), '1')
 | 
				
			||||||
 | 
					        self.assertEqual(ie._html_search_meta('b', html), '2')
 | 
				
			||||||
 | 
					        self.assertEqual(ie._html_search_meta('c', html), '3')
 | 
				
			||||||
 | 
					        self.assertEqual(ie._html_search_meta('d', html), '4')
 | 
				
			||||||
 | 
					        self.assertEqual(ie._html_search_meta('e', html), '5')
 | 
				
			||||||
 | 
					        self.assertEqual(ie._html_search_meta('f', html), '6')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if __name__ == '__main__':
 | 
					if __name__ == '__main__':
 | 
				
			||||||
    unittest.main()
 | 
					    unittest.main()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,9 +8,12 @@ import sys
 | 
				
			|||||||
import unittest
 | 
					import unittest
 | 
				
			||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
 | 
					sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import copy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from test.helper import FakeYDL, assertRegexpMatches
 | 
					from test.helper import FakeYDL, assertRegexpMatches
 | 
				
			||||||
from youtube_dl import YoutubeDL
 | 
					from youtube_dl import YoutubeDL
 | 
				
			||||||
from youtube_dl.extractor import YoutubeIE
 | 
					from youtube_dl.extractor import YoutubeIE
 | 
				
			||||||
 | 
					from youtube_dl.postprocessor.common import PostProcessor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class YDL(FakeYDL):
 | 
					class YDL(FakeYDL):
 | 
				
			||||||
@@ -192,6 +195,37 @@ class TestFormatSelection(unittest.TestCase):
 | 
				
			|||||||
        downloaded = ydl.downloaded_info_dicts[0]
 | 
					        downloaded = ydl.downloaded_info_dicts[0]
 | 
				
			||||||
        self.assertEqual(downloaded['format_id'], 'vid-high')
 | 
					        self.assertEqual(downloaded['format_id'], 'vid-high')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_format_selection_audio_exts(self):
 | 
				
			||||||
 | 
					        formats = [
 | 
				
			||||||
 | 
					            {'format_id': 'mp3-64', 'ext': 'mp3', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'},
 | 
				
			||||||
 | 
					            {'format_id': 'ogg-64', 'ext': 'ogg', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'},
 | 
				
			||||||
 | 
					            {'format_id': 'aac-64', 'ext': 'aac', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'},
 | 
				
			||||||
 | 
					            {'format_id': 'mp3-32', 'ext': 'mp3', 'abr': 32, 'url': 'http://_', 'vcodec': 'none'},
 | 
				
			||||||
 | 
					            {'format_id': 'aac-32', 'ext': 'aac', 'abr': 32, 'url': 'http://_', 'vcodec': 'none'},
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        info_dict = _make_result(formats)
 | 
				
			||||||
 | 
					        ydl = YDL({'format': 'best'})
 | 
				
			||||||
 | 
					        ie = YoutubeIE(ydl)
 | 
				
			||||||
 | 
					        ie._sort_formats(info_dict['formats'])
 | 
				
			||||||
 | 
					        ydl.process_ie_result(copy.deepcopy(info_dict))
 | 
				
			||||||
 | 
					        downloaded = ydl.downloaded_info_dicts[0]
 | 
				
			||||||
 | 
					        self.assertEqual(downloaded['format_id'], 'aac-64')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ydl = YDL({'format': 'mp3'})
 | 
				
			||||||
 | 
					        ie = YoutubeIE(ydl)
 | 
				
			||||||
 | 
					        ie._sort_formats(info_dict['formats'])
 | 
				
			||||||
 | 
					        ydl.process_ie_result(copy.deepcopy(info_dict))
 | 
				
			||||||
 | 
					        downloaded = ydl.downloaded_info_dicts[0]
 | 
				
			||||||
 | 
					        self.assertEqual(downloaded['format_id'], 'mp3-64')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ydl = YDL({'prefer_free_formats': True})
 | 
				
			||||||
 | 
					        ie = YoutubeIE(ydl)
 | 
				
			||||||
 | 
					        ie._sort_formats(info_dict['formats'])
 | 
				
			||||||
 | 
					        ydl.process_ie_result(copy.deepcopy(info_dict))
 | 
				
			||||||
 | 
					        downloaded = ydl.downloaded_info_dicts[0]
 | 
				
			||||||
 | 
					        self.assertEqual(downloaded['format_id'], 'ogg-64')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_format_selection_video(self):
 | 
					    def test_format_selection_video(self):
 | 
				
			||||||
        formats = [
 | 
					        formats = [
 | 
				
			||||||
            {'format_id': 'dash-video-low', 'ext': 'mp4', 'preference': 1, 'acodec': 'none', 'url': '_'},
 | 
					            {'format_id': 'dash-video-low', 'ext': 'mp4', 'preference': 1, 'acodec': 'none', 'url': '_'},
 | 
				
			||||||
@@ -218,7 +252,7 @@ class TestFormatSelection(unittest.TestCase):
 | 
				
			|||||||
            # 3D
 | 
					            # 3D
 | 
				
			||||||
            '85', '84', '102', '83', '101', '82', '100',
 | 
					            '85', '84', '102', '83', '101', '82', '100',
 | 
				
			||||||
            # Dash video
 | 
					            # Dash video
 | 
				
			||||||
            '138', '137', '248', '136', '247', '135', '246',
 | 
					            '137', '248', '136', '247', '135', '246',
 | 
				
			||||||
            '245', '244', '134', '243', '133', '242', '160',
 | 
					            '245', '244', '134', '243', '133', '242', '160',
 | 
				
			||||||
            # Dash audio
 | 
					            # Dash audio
 | 
				
			||||||
            '141', '172', '140', '171', '139',
 | 
					            '141', '172', '140', '171', '139',
 | 
				
			||||||
@@ -248,6 +282,120 @@ class TestFormatSelection(unittest.TestCase):
 | 
				
			|||||||
            downloaded = ydl.downloaded_info_dicts[0]
 | 
					            downloaded = ydl.downloaded_info_dicts[0]
 | 
				
			||||||
            self.assertEqual(downloaded['format_id'], f1id)
 | 
					            self.assertEqual(downloaded['format_id'], f1id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_format_filtering(self):
 | 
				
			||||||
 | 
					        formats = [
 | 
				
			||||||
 | 
					            {'format_id': 'A', 'filesize': 500, 'width': 1000},
 | 
				
			||||||
 | 
					            {'format_id': 'B', 'filesize': 1000, 'width': 500},
 | 
				
			||||||
 | 
					            {'format_id': 'C', 'filesize': 1000, 'width': 400},
 | 
				
			||||||
 | 
					            {'format_id': 'D', 'filesize': 2000, 'width': 600},
 | 
				
			||||||
 | 
					            {'format_id': 'E', 'filesize': 3000},
 | 
				
			||||||
 | 
					            {'format_id': 'F'},
 | 
				
			||||||
 | 
					            {'format_id': 'G', 'filesize': 1000000},
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        for f in formats:
 | 
				
			||||||
 | 
					            f['url'] = 'http://_/'
 | 
				
			||||||
 | 
					            f['ext'] = 'unknown'
 | 
				
			||||||
 | 
					        info_dict = _make_result(formats)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ydl = YDL({'format': 'best[filesize<3000]'})
 | 
				
			||||||
 | 
					        ydl.process_ie_result(info_dict)
 | 
				
			||||||
 | 
					        downloaded = ydl.downloaded_info_dicts[0]
 | 
				
			||||||
 | 
					        self.assertEqual(downloaded['format_id'], 'D')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ydl = YDL({'format': 'best[filesize<=3000]'})
 | 
				
			||||||
 | 
					        ydl.process_ie_result(info_dict)
 | 
				
			||||||
 | 
					        downloaded = ydl.downloaded_info_dicts[0]
 | 
				
			||||||
 | 
					        self.assertEqual(downloaded['format_id'], 'E')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ydl = YDL({'format': 'best[filesize <= ? 3000]'})
 | 
				
			||||||
 | 
					        ydl.process_ie_result(info_dict)
 | 
				
			||||||
 | 
					        downloaded = ydl.downloaded_info_dicts[0]
 | 
				
			||||||
 | 
					        self.assertEqual(downloaded['format_id'], 'F')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ydl = YDL({'format': 'best [filesize = 1000] [width>450]'})
 | 
				
			||||||
 | 
					        ydl.process_ie_result(info_dict)
 | 
				
			||||||
 | 
					        downloaded = ydl.downloaded_info_dicts[0]
 | 
				
			||||||
 | 
					        self.assertEqual(downloaded['format_id'], 'B')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ydl = YDL({'format': 'best [filesize = 1000] [width!=450]'})
 | 
				
			||||||
 | 
					        ydl.process_ie_result(info_dict)
 | 
				
			||||||
 | 
					        downloaded = ydl.downloaded_info_dicts[0]
 | 
				
			||||||
 | 
					        self.assertEqual(downloaded['format_id'], 'C')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ydl = YDL({'format': '[filesize>?1]'})
 | 
				
			||||||
 | 
					        ydl.process_ie_result(info_dict)
 | 
				
			||||||
 | 
					        downloaded = ydl.downloaded_info_dicts[0]
 | 
				
			||||||
 | 
					        self.assertEqual(downloaded['format_id'], 'G')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ydl = YDL({'format': '[filesize<1M]'})
 | 
				
			||||||
 | 
					        ydl.process_ie_result(info_dict)
 | 
				
			||||||
 | 
					        downloaded = ydl.downloaded_info_dicts[0]
 | 
				
			||||||
 | 
					        self.assertEqual(downloaded['format_id'], 'E')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ydl = YDL({'format': '[filesize<1MiB]'})
 | 
				
			||||||
 | 
					        ydl.process_ie_result(info_dict)
 | 
				
			||||||
 | 
					        downloaded = ydl.downloaded_info_dicts[0]
 | 
				
			||||||
 | 
					        self.assertEqual(downloaded['format_id'], 'G')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_subtitles(self):
 | 
				
			||||||
 | 
					        def s_formats(lang, autocaption=False):
 | 
				
			||||||
 | 
					            return [{
 | 
				
			||||||
 | 
					                'ext': ext,
 | 
				
			||||||
 | 
					                'url': 'http://localhost/video.%s.%s' % (lang, ext),
 | 
				
			||||||
 | 
					                '_auto': autocaption,
 | 
				
			||||||
 | 
					            } for ext in ['vtt', 'srt', 'ass']]
 | 
				
			||||||
 | 
					        subtitles = dict((l, s_formats(l)) for l in ['en', 'fr', 'es'])
 | 
				
			||||||
 | 
					        auto_captions = dict((l, s_formats(l, True)) for l in ['it', 'pt', 'es'])
 | 
				
			||||||
 | 
					        info_dict = {
 | 
				
			||||||
 | 
					            'id': 'test',
 | 
				
			||||||
 | 
					            'title': 'Test',
 | 
				
			||||||
 | 
					            'url': 'http://localhost/video.mp4',
 | 
				
			||||||
 | 
					            'subtitles': subtitles,
 | 
				
			||||||
 | 
					            'automatic_captions': auto_captions,
 | 
				
			||||||
 | 
					            'extractor': 'TEST',
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def get_info(params={}):
 | 
				
			||||||
 | 
					            params.setdefault('simulate', True)
 | 
				
			||||||
 | 
					            ydl = YDL(params)
 | 
				
			||||||
 | 
					            ydl.report_warning = lambda *args, **kargs: None
 | 
				
			||||||
 | 
					            return ydl.process_video_result(info_dict, download=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        result = get_info()
 | 
				
			||||||
 | 
					        self.assertFalse(result.get('requested_subtitles'))
 | 
				
			||||||
 | 
					        self.assertEqual(result['subtitles'], subtitles)
 | 
				
			||||||
 | 
					        self.assertEqual(result['automatic_captions'], auto_captions)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        result = get_info({'writesubtitles': True})
 | 
				
			||||||
 | 
					        subs = result['requested_subtitles']
 | 
				
			||||||
 | 
					        self.assertTrue(subs)
 | 
				
			||||||
 | 
					        self.assertEqual(set(subs.keys()), set(['en']))
 | 
				
			||||||
 | 
					        self.assertTrue(subs['en'].get('data') is None)
 | 
				
			||||||
 | 
					        self.assertEqual(subs['en']['ext'], 'ass')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        result = get_info({'writesubtitles': True, 'subtitlesformat': 'foo/srt'})
 | 
				
			||||||
 | 
					        subs = result['requested_subtitles']
 | 
				
			||||||
 | 
					        self.assertEqual(subs['en']['ext'], 'srt')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        result = get_info({'writesubtitles': True, 'subtitleslangs': ['es', 'fr', 'it']})
 | 
				
			||||||
 | 
					        subs = result['requested_subtitles']
 | 
				
			||||||
 | 
					        self.assertTrue(subs)
 | 
				
			||||||
 | 
					        self.assertEqual(set(subs.keys()), set(['es', 'fr']))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        result = get_info({'writesubtitles': True, 'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']})
 | 
				
			||||||
 | 
					        subs = result['requested_subtitles']
 | 
				
			||||||
 | 
					        self.assertTrue(subs)
 | 
				
			||||||
 | 
					        self.assertEqual(set(subs.keys()), set(['es', 'pt']))
 | 
				
			||||||
 | 
					        self.assertFalse(subs['es']['_auto'])
 | 
				
			||||||
 | 
					        self.assertTrue(subs['pt']['_auto'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        result = get_info({'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']})
 | 
				
			||||||
 | 
					        subs = result['requested_subtitles']
 | 
				
			||||||
 | 
					        self.assertTrue(subs)
 | 
				
			||||||
 | 
					        self.assertEqual(set(subs.keys()), set(['es', 'pt']))
 | 
				
			||||||
 | 
					        self.assertTrue(subs['es']['_auto'])
 | 
				
			||||||
 | 
					        self.assertTrue(subs['pt']['_auto'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_add_extra_info(self):
 | 
					    def test_add_extra_info(self):
 | 
				
			||||||
        test_dict = {
 | 
					        test_dict = {
 | 
				
			||||||
            'extractor': 'Foo',
 | 
					            'extractor': 'Foo',
 | 
				
			||||||
@@ -282,5 +430,35 @@ class TestFormatSelection(unittest.TestCase):
 | 
				
			|||||||
            'vbr': 10,
 | 
					            'vbr': 10,
 | 
				
			||||||
        }), '^\s*10k$')
 | 
					        }), '^\s*10k$')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_postprocessors(self):
 | 
				
			||||||
 | 
					        filename = 'post-processor-testfile.mp4'
 | 
				
			||||||
 | 
					        audiofile = filename + '.mp3'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        class SimplePP(PostProcessor):
 | 
				
			||||||
 | 
					            def run(self, info):
 | 
				
			||||||
 | 
					                with open(audiofile, 'wt') as f:
 | 
				
			||||||
 | 
					                    f.write('EXAMPLE')
 | 
				
			||||||
 | 
					                info['filepath']
 | 
				
			||||||
 | 
					                return False, info
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def run_pp(params):
 | 
				
			||||||
 | 
					            with open(filename, 'wt') as f:
 | 
				
			||||||
 | 
					                f.write('EXAMPLE')
 | 
				
			||||||
 | 
					            ydl = YoutubeDL(params)
 | 
				
			||||||
 | 
					            ydl.add_post_processor(SimplePP())
 | 
				
			||||||
 | 
					            ydl.post_process(filename, {'filepath': filename})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        run_pp({'keepvideo': True})
 | 
				
			||||||
 | 
					        self.assertTrue(os.path.exists(filename), '%s doesn\'t exist' % filename)
 | 
				
			||||||
 | 
					        self.assertTrue(os.path.exists(audiofile), '%s doesn\'t exist' % audiofile)
 | 
				
			||||||
 | 
					        os.unlink(filename)
 | 
				
			||||||
 | 
					        os.unlink(audiofile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        run_pp({'keepvideo': False})
 | 
				
			||||||
 | 
					        self.assertFalse(os.path.exists(filename), '%s exists' % filename)
 | 
				
			||||||
 | 
					        self.assertTrue(os.path.exists(audiofile), '%s doesn\'t exist' % audiofile)
 | 
				
			||||||
 | 
					        os.unlink(audiofile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if __name__ == '__main__':
 | 
					if __name__ == '__main__':
 | 
				
			||||||
    unittest.main()
 | 
					    unittest.main()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -45,11 +45,6 @@ class TestAgeRestriction(unittest.TestCase):
 | 
				
			|||||||
            'http://www.youporn.com/watch/505835/sex-ed-is-it-safe-to-masturbate-daily/',
 | 
					            'http://www.youporn.com/watch/505835/sex-ed-is-it-safe-to-masturbate-daily/',
 | 
				
			||||||
            '505835.mp4', 2, old_age=25)
 | 
					            '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__':
 | 
					if __name__ == '__main__':
 | 
				
			||||||
    unittest.main()
 | 
					    unittest.main()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,7 +14,6 @@ from test.helper import gettestcases
 | 
				
			|||||||
from youtube_dl.extractor import (
 | 
					from youtube_dl.extractor import (
 | 
				
			||||||
    FacebookIE,
 | 
					    FacebookIE,
 | 
				
			||||||
    gen_extractors,
 | 
					    gen_extractors,
 | 
				
			||||||
    TwitchIE,
 | 
					 | 
				
			||||||
    YoutubeIE,
 | 
					    YoutubeIE,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -72,18 +71,6 @@ class TestAllURLsMatching(unittest.TestCase):
 | 
				
			|||||||
        self.assertMatch('http://www.youtube.com/results?search_query=making+mustard', ['youtube:search_url'])
 | 
					        self.assertMatch('http://www.youtube.com/results?search_query=making+mustard', ['youtube:search_url'])
 | 
				
			||||||
        self.assertMatch('https://www.youtube.com/results?baz=bar&search_query=youtube-dl+test+video&filters=video&lclk=video', ['youtube:search_url'])
 | 
					        self.assertMatch('https://www.youtube.com/results?baz=bar&search_query=youtube-dl+test+video&filters=video&lclk=video', ['youtube:search_url'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_twitch_channelid_matching(self):
 | 
					 | 
				
			||||||
        self.assertTrue(TwitchIE.suitable('twitch.tv/vanillatv'))
 | 
					 | 
				
			||||||
        self.assertTrue(TwitchIE.suitable('www.twitch.tv/vanillatv'))
 | 
					 | 
				
			||||||
        self.assertTrue(TwitchIE.suitable('http://www.twitch.tv/vanillatv'))
 | 
					 | 
				
			||||||
        self.assertTrue(TwitchIE.suitable('http://www.twitch.tv/vanillatv/'))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_twitch_videoid_matching(self):
 | 
					 | 
				
			||||||
        self.assertTrue(TwitchIE.suitable('http://www.twitch.tv/vanillatv/b/328087483'))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_twitch_chapterid_matching(self):
 | 
					 | 
				
			||||||
        self.assertTrue(TwitchIE.suitable('http://www.twitch.tv/tsm_theoddone/c/2349361'))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_youtube_extract(self):
 | 
					    def test_youtube_extract(self):
 | 
				
			||||||
        assertExtractId = lambda url, id: self.assertEqual(YoutubeIE.extract_id(url), id)
 | 
					        assertExtractId = lambda url, id: self.assertEqual(YoutubeIE.extract_id(url), id)
 | 
				
			||||||
        assertExtractId('http://www.youtube.com/watch?&v=BaW_jenozKc', 'BaW_jenozKc')
 | 
					        assertExtractId('http://www.youtube.com/watch?&v=BaW_jenozKc', 'BaW_jenozKc')
 | 
				
			||||||
@@ -115,8 +102,6 @@ class TestAllURLsMatching(unittest.TestCase):
 | 
				
			|||||||
        self.assertMatch(':ythistory', ['youtube:history'])
 | 
					        self.assertMatch(':ythistory', ['youtube:history'])
 | 
				
			||||||
        self.assertMatch(':thedailyshow', ['ComedyCentralShows'])
 | 
					        self.assertMatch(':thedailyshow', ['ComedyCentralShows'])
 | 
				
			||||||
        self.assertMatch(':tds', ['ComedyCentralShows'])
 | 
					        self.assertMatch(':tds', ['ComedyCentralShows'])
 | 
				
			||||||
        self.assertMatch(':colbertreport', ['ComedyCentralShows'])
 | 
					 | 
				
			||||||
        self.assertMatch(':cr', ['ComedyCentralShows'])
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_vimeo_matching(self):
 | 
					    def test_vimeo_matching(self):
 | 
				
			||||||
        self.assertMatch('http://vimeo.com/channels/tributes', ['vimeo:channel'])
 | 
					        self.assertMatch('http://vimeo.com/channels/tributes', ['vimeo:channel'])
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -89,7 +89,7 @@ def generator(test_case):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        for tc in test_cases:
 | 
					        for tc in test_cases:
 | 
				
			||||||
            info_dict = tc.get('info_dict', {})
 | 
					            info_dict = tc.get('info_dict', {})
 | 
				
			||||||
            if not tc.get('file') and not (info_dict.get('id') and info_dict.get('ext')):
 | 
					            if not (info_dict.get('id') and info_dict.get('ext')):
 | 
				
			||||||
                raise Exception('Test definition incorrect. The output file cannot be known. Are both \'id\' and \'ext\' keys present?')
 | 
					                raise Exception('Test definition incorrect. The output file cannot be known. Are both \'id\' and \'ext\' keys present?')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if 'skip' in test_case:
 | 
					        if 'skip' in test_case:
 | 
				
			||||||
@@ -116,7 +116,7 @@ def generator(test_case):
 | 
				
			|||||||
        expect_warnings(ydl, test_case.get('expected_warnings', []))
 | 
					        expect_warnings(ydl, test_case.get('expected_warnings', []))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def get_tc_filename(tc):
 | 
					        def get_tc_filename(tc):
 | 
				
			||||||
            return tc.get('file') or ydl.prepare_filename(tc.get('info_dict', {}))
 | 
					            return ydl.prepare_filename(tc.get('info_dict', {}))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        res_dict = None
 | 
					        res_dict = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -155,7 +155,7 @@ def generator(test_case):
 | 
				
			|||||||
            if is_playlist:
 | 
					            if is_playlist:
 | 
				
			||||||
                self.assertEqual(res_dict['_type'], 'playlist')
 | 
					                self.assertEqual(res_dict['_type'], 'playlist')
 | 
				
			||||||
                self.assertTrue('entries' in res_dict)
 | 
					                self.assertTrue('entries' in res_dict)
 | 
				
			||||||
                expect_info_dict(self, test_case.get('info_dict', {}), res_dict)
 | 
					                expect_info_dict(self, res_dict, test_case.get('info_dict', {}))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if 'playlist_mincount' in test_case:
 | 
					            if 'playlist_mincount' in test_case:
 | 
				
			||||||
                assertGreaterEqual(
 | 
					                assertGreaterEqual(
 | 
				
			||||||
@@ -204,7 +204,7 @@ def generator(test_case):
 | 
				
			|||||||
                with io.open(info_json_fn, encoding='utf-8') as infof:
 | 
					                with io.open(info_json_fn, encoding='utf-8') as infof:
 | 
				
			||||||
                    info_dict = json.load(infof)
 | 
					                    info_dict = json.load(infof)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                expect_info_dict(self, tc.get('info_dict', {}), info_dict)
 | 
					                expect_info_dict(self, info_dict, tc.get('info_dict', {}))
 | 
				
			||||||
        finally:
 | 
					        finally:
 | 
				
			||||||
            try_rm_tcs_files()
 | 
					            try_rm_tcs_files()
 | 
				
			||||||
            if is_playlist and res_dict is not None and res_dict.get('entries'):
 | 
					            if is_playlist and res_dict is not None and res_dict.get('entries'):
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										72
									
								
								test/test_http.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								test/test_http.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,72 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Allow direct execution
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					import unittest
 | 
				
			||||||
 | 
					sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from youtube_dl import YoutubeDL
 | 
				
			||||||
 | 
					from youtube_dl.compat import compat_http_server
 | 
				
			||||||
 | 
					import ssl
 | 
				
			||||||
 | 
					import threading
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TEST_DIR = os.path.dirname(os.path.abspath(__file__))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class HTTPTestRequestHandler(compat_http_server.BaseHTTPRequestHandler):
 | 
				
			||||||
 | 
					    def log_message(self, format, *args):
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def do_GET(self):
 | 
				
			||||||
 | 
					        if self.path == '/video.html':
 | 
				
			||||||
 | 
					            self.send_response(200)
 | 
				
			||||||
 | 
					            self.send_header('Content-Type', 'text/html; charset=utf-8')
 | 
				
			||||||
 | 
					            self.end_headers()
 | 
				
			||||||
 | 
					            self.wfile.write(b'<html><video src="/vid.mp4" /></html>')
 | 
				
			||||||
 | 
					        elif self.path == '/vid.mp4':
 | 
				
			||||||
 | 
					            self.send_response(200)
 | 
				
			||||||
 | 
					            self.send_header('Content-Type', 'video/mp4')
 | 
				
			||||||
 | 
					            self.end_headers()
 | 
				
			||||||
 | 
					            self.wfile.write(b'\x00\x00\x00\x00\x20\x66\x74[video]')
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            assert False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FakeLogger(object):
 | 
				
			||||||
 | 
					    def debug(self, msg):
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def warning(self, msg):
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def error(self, msg):
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestHTTP(unittest.TestCase):
 | 
				
			||||||
 | 
					    def setUp(self):
 | 
				
			||||||
 | 
					        certfn = os.path.join(TEST_DIR, 'testcert.pem')
 | 
				
			||||||
 | 
					        self.httpd = compat_http_server.HTTPServer(
 | 
				
			||||||
 | 
					            ('localhost', 0), HTTPTestRequestHandler)
 | 
				
			||||||
 | 
					        self.httpd.socket = ssl.wrap_socket(
 | 
				
			||||||
 | 
					            self.httpd.socket, certfile=certfn, server_side=True)
 | 
				
			||||||
 | 
					        self.port = self.httpd.socket.getsockname()[1]
 | 
				
			||||||
 | 
					        self.server_thread = threading.Thread(target=self.httpd.serve_forever)
 | 
				
			||||||
 | 
					        self.server_thread.daemon = True
 | 
				
			||||||
 | 
					        self.server_thread.start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_nocheckcertificate(self):
 | 
				
			||||||
 | 
					        if sys.version_info >= (2, 7, 9):  # No certificate checking anyways
 | 
				
			||||||
 | 
					            ydl = YoutubeDL({'logger': FakeLogger()})
 | 
				
			||||||
 | 
					            self.assertRaises(
 | 
				
			||||||
 | 
					                Exception,
 | 
				
			||||||
 | 
					                ydl.extract_info, 'https://localhost:%d/video.html' % self.port)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ydl = YoutubeDL({'logger': FakeLogger(), 'nocheckcertificate': True})
 | 
				
			||||||
 | 
					        r = ydl.extract_info('https://localhost:%d/video.html' % self.port)
 | 
				
			||||||
 | 
					        self.assertEqual(r['url'], 'https://localhost:%d/vid.mp4' % self.port)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == '__main__':
 | 
				
			||||||
 | 
					    unittest.main()
 | 
				
			||||||
							
								
								
									
										106
									
								
								test/test_jsinterp.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								test/test_jsinterp.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,106 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Allow direct execution
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					import unittest
 | 
				
			||||||
 | 
					sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from youtube_dl.jsinterp import JSInterpreter
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestJSInterpreter(unittest.TestCase):
 | 
				
			||||||
 | 
					    def test_basic(self):
 | 
				
			||||||
 | 
					        jsi = JSInterpreter('function x(){;}')
 | 
				
			||||||
 | 
					        self.assertEqual(jsi.call_function('x'), None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        jsi = JSInterpreter('function x3(){return 42;}')
 | 
				
			||||||
 | 
					        self.assertEqual(jsi.call_function('x3'), 42)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_calc(self):
 | 
				
			||||||
 | 
					        jsi = JSInterpreter('function x4(a){return 2*a+1;}')
 | 
				
			||||||
 | 
					        self.assertEqual(jsi.call_function('x4', 3), 7)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_empty_return(self):
 | 
				
			||||||
 | 
					        jsi = JSInterpreter('function f(){return; y()}')
 | 
				
			||||||
 | 
					        self.assertEqual(jsi.call_function('f'), None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_morespace(self):
 | 
				
			||||||
 | 
					        jsi = JSInterpreter('function x (a) { return 2 * a + 1 ; }')
 | 
				
			||||||
 | 
					        self.assertEqual(jsi.call_function('x', 3), 7)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        jsi = JSInterpreter('function f () { x =  2  ; return x; }')
 | 
				
			||||||
 | 
					        self.assertEqual(jsi.call_function('f'), 2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_strange_chars(self):
 | 
				
			||||||
 | 
					        jsi = JSInterpreter('function $_xY1 ($_axY1) { var $_axY2 = $_axY1 + 1; return $_axY2; }')
 | 
				
			||||||
 | 
					        self.assertEqual(jsi.call_function('$_xY1', 20), 21)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_operators(self):
 | 
				
			||||||
 | 
					        jsi = JSInterpreter('function f(){return 1 << 5;}')
 | 
				
			||||||
 | 
					        self.assertEqual(jsi.call_function('f'), 32)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        jsi = JSInterpreter('function f(){return 19 & 21;}')
 | 
				
			||||||
 | 
					        self.assertEqual(jsi.call_function('f'), 17)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        jsi = JSInterpreter('function f(){return 11 >> 2;}')
 | 
				
			||||||
 | 
					        self.assertEqual(jsi.call_function('f'), 2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_array_access(self):
 | 
				
			||||||
 | 
					        jsi = JSInterpreter('function f(){var x = [1,2,3]; x[0] = 4; x[0] = 5; x[2] = 7; return x;}')
 | 
				
			||||||
 | 
					        self.assertEqual(jsi.call_function('f'), [5, 2, 7])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_parens(self):
 | 
				
			||||||
 | 
					        jsi = JSInterpreter('function f(){return (1) + (2) * ((( (( (((((3)))))) )) ));}')
 | 
				
			||||||
 | 
					        self.assertEqual(jsi.call_function('f'), 7)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        jsi = JSInterpreter('function f(){return (1 + 2) * 3;}')
 | 
				
			||||||
 | 
					        self.assertEqual(jsi.call_function('f'), 9)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_assignments(self):
 | 
				
			||||||
 | 
					        jsi = JSInterpreter('function f(){var x = 20; x = 30 + 1; return x;}')
 | 
				
			||||||
 | 
					        self.assertEqual(jsi.call_function('f'), 31)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        jsi = JSInterpreter('function f(){var x = 20; x += 30 + 1; return x;}')
 | 
				
			||||||
 | 
					        self.assertEqual(jsi.call_function('f'), 51)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        jsi = JSInterpreter('function f(){var x = 20; x -= 30 + 1; return x;}')
 | 
				
			||||||
 | 
					        self.assertEqual(jsi.call_function('f'), -11)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_comments(self):
 | 
				
			||||||
 | 
					        'Skipping: Not yet fully implemented'
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					        jsi = JSInterpreter('''
 | 
				
			||||||
 | 
					        function x() {
 | 
				
			||||||
 | 
					            var x = /* 1 + */ 2;
 | 
				
			||||||
 | 
					            var y = /* 30
 | 
				
			||||||
 | 
					            * 40 */ 50;
 | 
				
			||||||
 | 
					            return x + y;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        ''')
 | 
				
			||||||
 | 
					        self.assertEqual(jsi.call_function('x'), 52)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        jsi = JSInterpreter('''
 | 
				
			||||||
 | 
					        function f() {
 | 
				
			||||||
 | 
					            var x = "/*";
 | 
				
			||||||
 | 
					            var y = 1 /* comment */ + 2;
 | 
				
			||||||
 | 
					            return y;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        ''')
 | 
				
			||||||
 | 
					        self.assertEqual(jsi.call_function('f'), 3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_precedence(self):
 | 
				
			||||||
 | 
					        jsi = JSInterpreter('''
 | 
				
			||||||
 | 
					        function x() {
 | 
				
			||||||
 | 
					            var a = [10, 20, 30, 40, 50];
 | 
				
			||||||
 | 
					            var b = 6;
 | 
				
			||||||
 | 
					            a[0]=a[b%a.length];
 | 
				
			||||||
 | 
					            return a;
 | 
				
			||||||
 | 
					        }''')
 | 
				
			||||||
 | 
					        self.assertEqual(jsi.call_function('x'), [20, 20, 30, 40, 50])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == '__main__':
 | 
				
			||||||
 | 
					    unittest.main()
 | 
				
			||||||
@@ -17,6 +17,15 @@ from youtube_dl.extractor import (
 | 
				
			|||||||
    TEDIE,
 | 
					    TEDIE,
 | 
				
			||||||
    VimeoIE,
 | 
					    VimeoIE,
 | 
				
			||||||
    WallaIE,
 | 
					    WallaIE,
 | 
				
			||||||
 | 
					    CeskaTelevizeIE,
 | 
				
			||||||
 | 
					    LyndaIE,
 | 
				
			||||||
 | 
					    NPOIE,
 | 
				
			||||||
 | 
					    ComedyCentralIE,
 | 
				
			||||||
 | 
					    NRKTVIE,
 | 
				
			||||||
 | 
					    RaiIE,
 | 
				
			||||||
 | 
					    VikiIE,
 | 
				
			||||||
 | 
					    ThePlatformIE,
 | 
				
			||||||
 | 
					    RTVEALaCartaIE,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -26,42 +35,38 @@ class BaseTestSubtitles(unittest.TestCase):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def setUp(self):
 | 
					    def setUp(self):
 | 
				
			||||||
        self.DL = FakeYDL()
 | 
					        self.DL = FakeYDL()
 | 
				
			||||||
        self.ie = self.IE(self.DL)
 | 
					        self.ie = self.IE()
 | 
				
			||||||
 | 
					        self.DL.add_info_extractor(self.ie)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def getInfoDict(self):
 | 
					    def getInfoDict(self):
 | 
				
			||||||
        info_dict = self.ie.extract(self.url)
 | 
					        info_dict = self.DL.extract_info(self.url, download=False)
 | 
				
			||||||
        return info_dict
 | 
					        return info_dict
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def getSubtitles(self):
 | 
					    def getSubtitles(self):
 | 
				
			||||||
        info_dict = self.getInfoDict()
 | 
					        info_dict = self.getInfoDict()
 | 
				
			||||||
        return info_dict['subtitles']
 | 
					        subtitles = info_dict['requested_subtitles']
 | 
				
			||||||
 | 
					        if not subtitles:
 | 
				
			||||||
 | 
					            return subtitles
 | 
				
			||||||
 | 
					        for sub_info in subtitles.values():
 | 
				
			||||||
 | 
					            if sub_info.get('data') is None:
 | 
				
			||||||
 | 
					                uf = self.DL.urlopen(sub_info['url'])
 | 
				
			||||||
 | 
					                sub_info['data'] = uf.read().decode('utf-8')
 | 
				
			||||||
 | 
					        return dict((l, sub_info['data']) for l, sub_info in subtitles.items())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TestYoutubeSubtitles(BaseTestSubtitles):
 | 
					class TestYoutubeSubtitles(BaseTestSubtitles):
 | 
				
			||||||
    url = 'QRS8MkLhQmM'
 | 
					    url = 'QRS8MkLhQmM'
 | 
				
			||||||
    IE = YoutubeIE
 | 
					    IE = YoutubeIE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    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):
 | 
					    def test_youtube_allsubtitles(self):
 | 
				
			||||||
        self.DL.params['writesubtitles'] = True
 | 
					        self.DL.params['writesubtitles'] = True
 | 
				
			||||||
        self.DL.params['allsubtitles'] = True
 | 
					        self.DL.params['allsubtitles'] = True
 | 
				
			||||||
        subtitles = self.getSubtitles()
 | 
					        subtitles = self.getSubtitles()
 | 
				
			||||||
        self.assertEqual(len(subtitles.keys()), 13)
 | 
					        self.assertEqual(len(subtitles.keys()), 13)
 | 
				
			||||||
 | 
					        self.assertEqual(md5(subtitles['en']), '4cd9278a35ba2305f47354ee13472260')
 | 
				
			||||||
 | 
					        self.assertEqual(md5(subtitles['it']), '164a51f16f260476a05b50fe4c2f161d')
 | 
				
			||||||
 | 
					        for lang in ['it', 'fr', 'de']:
 | 
				
			||||||
 | 
					            self.assertTrue(subtitles.get(lang) is not None, 'Subtitles for \'%s\' not extracted' % lang)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_youtube_subtitles_sbv_format(self):
 | 
					    def test_youtube_subtitles_sbv_format(self):
 | 
				
			||||||
        self.DL.params['writesubtitles'] = True
 | 
					        self.DL.params['writesubtitles'] = True
 | 
				
			||||||
@@ -75,12 +80,6 @@ class TestYoutubeSubtitles(BaseTestSubtitles):
 | 
				
			|||||||
        subtitles = self.getSubtitles()
 | 
					        subtitles = self.getSubtitles()
 | 
				
			||||||
        self.assertEqual(md5(subtitles['en']), '3cb210999d3e021bd6c7f0ea751eab06')
 | 
					        self.assertEqual(md5(subtitles['en']), '3cb210999d3e021bd6c7f0ea751eab06')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_youtube_list_subtitles(self):
 | 
					 | 
				
			||||||
        self.DL.expect_warning('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):
 | 
					    def test_youtube_automatic_captions(self):
 | 
				
			||||||
        self.url = '8YoUxe5ncPo'
 | 
					        self.url = '8YoUxe5ncPo'
 | 
				
			||||||
        self.DL.params['writeautomaticsub'] = True
 | 
					        self.DL.params['writeautomaticsub'] = True
 | 
				
			||||||
@@ -88,61 +87,36 @@ class TestYoutubeSubtitles(BaseTestSubtitles):
 | 
				
			|||||||
        subtitles = self.getSubtitles()
 | 
					        subtitles = self.getSubtitles()
 | 
				
			||||||
        self.assertTrue(subtitles['it'] is not None)
 | 
					        self.assertTrue(subtitles['it'] is not None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_youtube_translated_subtitles(self):
 | 
				
			||||||
 | 
					        # This video has a subtitles track, which can be translated
 | 
				
			||||||
 | 
					        self.url = 'Ky9eprVWzlI'
 | 
				
			||||||
 | 
					        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):
 | 
					    def test_youtube_nosubtitles(self):
 | 
				
			||||||
        self.DL.expect_warning('video doesn\'t have subtitles')
 | 
					        self.DL.expect_warning('video doesn\'t have subtitles')
 | 
				
			||||||
        self.url = 'n5BB19UTcdA'
 | 
					        self.url = 'n5BB19UTcdA'
 | 
				
			||||||
        self.DL.params['writesubtitles'] = True
 | 
					        self.DL.params['writesubtitles'] = True
 | 
				
			||||||
        self.DL.params['allsubtitles'] = True
 | 
					        self.DL.params['allsubtitles'] = True
 | 
				
			||||||
        subtitles = self.getSubtitles()
 | 
					        subtitles = self.getSubtitles()
 | 
				
			||||||
        self.assertEqual(len(subtitles), 0)
 | 
					        self.assertFalse(subtitles)
 | 
				
			||||||
 | 
					 | 
				
			||||||
    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, 'Subtitles for \'%s\' not extracted' % lang)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TestDailymotionSubtitles(BaseTestSubtitles):
 | 
					class TestDailymotionSubtitles(BaseTestSubtitles):
 | 
				
			||||||
    url = 'http://www.dailymotion.com/video/xczg00'
 | 
					    url = 'http://www.dailymotion.com/video/xczg00'
 | 
				
			||||||
    IE = DailymotionIE
 | 
					    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):
 | 
					    def test_allsubtitles(self):
 | 
				
			||||||
        self.DL.params['writesubtitles'] = True
 | 
					        self.DL.params['writesubtitles'] = True
 | 
				
			||||||
        self.DL.params['allsubtitles'] = True
 | 
					        self.DL.params['allsubtitles'] = True
 | 
				
			||||||
        subtitles = self.getSubtitles()
 | 
					        subtitles = self.getSubtitles()
 | 
				
			||||||
        self.assertEqual(len(subtitles.keys()), 5)
 | 
					        self.assertTrue(len(subtitles.keys()) >= 6)
 | 
				
			||||||
 | 
					        self.assertEqual(md5(subtitles['en']), '976553874490cba125086bbfea3ff76f')
 | 
				
			||||||
    def test_list_subtitles(self):
 | 
					        self.assertEqual(md5(subtitles['fr']), '594564ec7d588942e384e920e5341792')
 | 
				
			||||||
        self.DL.expect_warning('Automatic Captions not supported by this server')
 | 
					        for lang in ['es', 'fr', 'de']:
 | 
				
			||||||
        self.DL.params['listsubtitles'] = True
 | 
					            self.assertTrue(subtitles.get(lang) is not None, 'Subtitles for \'%s\' not extracted' % lang)
 | 
				
			||||||
        info_dict = self.getInfoDict()
 | 
					 | 
				
			||||||
        self.assertEqual(info_dict, None)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_automatic_captions(self):
 | 
					 | 
				
			||||||
        self.DL.expect_warning('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):
 | 
					    def test_nosubtitles(self):
 | 
				
			||||||
        self.DL.expect_warning('video doesn\'t have subtitles')
 | 
					        self.DL.expect_warning('video doesn\'t have subtitles')
 | 
				
			||||||
@@ -150,61 +124,21 @@ class TestDailymotionSubtitles(BaseTestSubtitles):
 | 
				
			|||||||
        self.DL.params['writesubtitles'] = True
 | 
					        self.DL.params['writesubtitles'] = True
 | 
				
			||||||
        self.DL.params['allsubtitles'] = True
 | 
					        self.DL.params['allsubtitles'] = True
 | 
				
			||||||
        subtitles = self.getSubtitles()
 | 
					        subtitles = self.getSubtitles()
 | 
				
			||||||
        self.assertEqual(len(subtitles), 0)
 | 
					        self.assertFalse(subtitles)
 | 
				
			||||||
 | 
					 | 
				
			||||||
    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, 'Subtitles for \'%s\' not extracted' % lang)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TestTedSubtitles(BaseTestSubtitles):
 | 
					class TestTedSubtitles(BaseTestSubtitles):
 | 
				
			||||||
    url = 'http://www.ted.com/talks/dan_dennett_on_our_consciousness.html'
 | 
					    url = 'http://www.ted.com/talks/dan_dennett_on_our_consciousness.html'
 | 
				
			||||||
    IE = TEDIE
 | 
					    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']), '4262c1665ff928a2dada178f62cb8d14')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_subtitles_lang(self):
 | 
					 | 
				
			||||||
        self.DL.params['writesubtitles'] = True
 | 
					 | 
				
			||||||
        self.DL.params['subtitleslangs'] = ['fr']
 | 
					 | 
				
			||||||
        subtitles = self.getSubtitles()
 | 
					 | 
				
			||||||
        self.assertEqual(md5(subtitles['fr']), '66a63f7f42c97a50f8c0e90bc7797bb5')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_allsubtitles(self):
 | 
					    def test_allsubtitles(self):
 | 
				
			||||||
        self.DL.params['writesubtitles'] = True
 | 
					        self.DL.params['writesubtitles'] = True
 | 
				
			||||||
        self.DL.params['allsubtitles'] = True
 | 
					        self.DL.params['allsubtitles'] = True
 | 
				
			||||||
        subtitles = self.getSubtitles()
 | 
					        subtitles = self.getSubtitles()
 | 
				
			||||||
        self.assertTrue(len(subtitles.keys()) >= 28)
 | 
					        self.assertTrue(len(subtitles.keys()) >= 28)
 | 
				
			||||||
 | 
					        self.assertEqual(md5(subtitles['en']), '4262c1665ff928a2dada178f62cb8d14')
 | 
				
			||||||
    def test_list_subtitles(self):
 | 
					        self.assertEqual(md5(subtitles['fr']), '66a63f7f42c97a50f8c0e90bc7797bb5')
 | 
				
			||||||
        self.DL.expect_warning('Automatic Captions not supported by this server')
 | 
					        for lang in ['es', 'fr', 'de']:
 | 
				
			||||||
        self.DL.params['listsubtitles'] = True
 | 
					 | 
				
			||||||
        info_dict = self.getInfoDict()
 | 
					 | 
				
			||||||
        self.assertEqual(info_dict, None)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_automatic_captions(self):
 | 
					 | 
				
			||||||
        self.DL.expect_warning('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, 'Subtitles for \'%s\' not extracted' % lang)
 | 
					            self.assertTrue(subtitles.get(lang) is not None, 'Subtitles for \'%s\' not extracted' % lang)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -212,14 +146,7 @@ class TestBlipTVSubtitles(BaseTestSubtitles):
 | 
				
			|||||||
    url = 'http://blip.tv/a/a-6603250'
 | 
					    url = 'http://blip.tv/a/a-6603250'
 | 
				
			||||||
    IE = BlipTVIE
 | 
					    IE = BlipTVIE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_list_subtitles(self):
 | 
					 | 
				
			||||||
        self.DL.expect_warning('Automatic Captions not supported by this server')
 | 
					 | 
				
			||||||
        self.DL.params['listsubtitles'] = True
 | 
					 | 
				
			||||||
        info_dict = self.getInfoDict()
 | 
					 | 
				
			||||||
        self.assertEqual(info_dict, None)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_allsubtitles(self):
 | 
					    def test_allsubtitles(self):
 | 
				
			||||||
        self.DL.expect_warning('Automatic Captions not supported by this server')
 | 
					 | 
				
			||||||
        self.DL.params['writesubtitles'] = True
 | 
					        self.DL.params['writesubtitles'] = True
 | 
				
			||||||
        self.DL.params['allsubtitles'] = True
 | 
					        self.DL.params['allsubtitles'] = True
 | 
				
			||||||
        subtitles = self.getSubtitles()
 | 
					        subtitles = self.getSubtitles()
 | 
				
			||||||
@@ -231,39 +158,13 @@ class TestVimeoSubtitles(BaseTestSubtitles):
 | 
				
			|||||||
    url = 'http://vimeo.com/76979871'
 | 
					    url = 'http://vimeo.com/76979871'
 | 
				
			||||||
    IE = VimeoIE
 | 
					    IE = VimeoIE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    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']), '26399116d23ae3cf2c087cea94bc43b4')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_subtitles_lang(self):
 | 
					 | 
				
			||||||
        self.DL.params['writesubtitles'] = True
 | 
					 | 
				
			||||||
        self.DL.params['subtitleslangs'] = ['fr']
 | 
					 | 
				
			||||||
        subtitles = self.getSubtitles()
 | 
					 | 
				
			||||||
        self.assertEqual(md5(subtitles['fr']), 'b6191146a6c5d3a452244d853fde6dc8')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_allsubtitles(self):
 | 
					    def test_allsubtitles(self):
 | 
				
			||||||
        self.DL.params['writesubtitles'] = True
 | 
					        self.DL.params['writesubtitles'] = True
 | 
				
			||||||
        self.DL.params['allsubtitles'] = True
 | 
					        self.DL.params['allsubtitles'] = True
 | 
				
			||||||
        subtitles = self.getSubtitles()
 | 
					        subtitles = self.getSubtitles()
 | 
				
			||||||
        self.assertEqual(set(subtitles.keys()), set(['de', 'en', 'es', 'fr']))
 | 
					        self.assertEqual(set(subtitles.keys()), set(['de', 'en', 'es', 'fr']))
 | 
				
			||||||
 | 
					        self.assertEqual(md5(subtitles['en']), '8062383cf4dec168fc40a088aa6d5888')
 | 
				
			||||||
    def test_list_subtitles(self):
 | 
					        self.assertEqual(md5(subtitles['fr']), 'b6191146a6c5d3a452244d853fde6dc8')
 | 
				
			||||||
        self.DL.expect_warning('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('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):
 | 
					    def test_nosubtitles(self):
 | 
				
			||||||
        self.DL.expect_warning('video doesn\'t have subtitles')
 | 
					        self.DL.expect_warning('video doesn\'t have subtitles')
 | 
				
			||||||
@@ -271,27 +172,13 @@ class TestVimeoSubtitles(BaseTestSubtitles):
 | 
				
			|||||||
        self.DL.params['writesubtitles'] = True
 | 
					        self.DL.params['writesubtitles'] = True
 | 
				
			||||||
        self.DL.params['allsubtitles'] = True
 | 
					        self.DL.params['allsubtitles'] = True
 | 
				
			||||||
        subtitles = self.getSubtitles()
 | 
					        subtitles = self.getSubtitles()
 | 
				
			||||||
        self.assertEqual(len(subtitles), 0)
 | 
					        self.assertFalse(subtitles)
 | 
				
			||||||
 | 
					 | 
				
			||||||
    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, 'Subtitles for \'%s\' not extracted' % lang)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TestWallaSubtitles(BaseTestSubtitles):
 | 
					class TestWallaSubtitles(BaseTestSubtitles):
 | 
				
			||||||
    url = 'http://vod.walla.co.il/movie/2705958/the-yes-men'
 | 
					    url = 'http://vod.walla.co.il/movie/2705958/the-yes-men'
 | 
				
			||||||
    IE = WallaIE
 | 
					    IE = WallaIE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_list_subtitles(self):
 | 
					 | 
				
			||||||
        self.DL.expect_warning('Automatic Captions not supported by this server')
 | 
					 | 
				
			||||||
        self.DL.params['listsubtitles'] = True
 | 
					 | 
				
			||||||
        info_dict = self.getInfoDict()
 | 
					 | 
				
			||||||
        self.assertEqual(info_dict, None)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_allsubtitles(self):
 | 
					    def test_allsubtitles(self):
 | 
				
			||||||
        self.DL.expect_warning('Automatic Captions not supported by this server')
 | 
					        self.DL.expect_warning('Automatic Captions not supported by this server')
 | 
				
			||||||
        self.DL.params['writesubtitles'] = True
 | 
					        self.DL.params['writesubtitles'] = True
 | 
				
			||||||
@@ -306,7 +193,131 @@ class TestWallaSubtitles(BaseTestSubtitles):
 | 
				
			|||||||
        self.DL.params['writesubtitles'] = True
 | 
					        self.DL.params['writesubtitles'] = True
 | 
				
			||||||
        self.DL.params['allsubtitles'] = True
 | 
					        self.DL.params['allsubtitles'] = True
 | 
				
			||||||
        subtitles = self.getSubtitles()
 | 
					        subtitles = self.getSubtitles()
 | 
				
			||||||
        self.assertEqual(len(subtitles), 0)
 | 
					        self.assertFalse(subtitles)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestCeskaTelevizeSubtitles(BaseTestSubtitles):
 | 
				
			||||||
 | 
					    url = 'http://www.ceskatelevize.cz/ivysilani/10600540290-u6-uzasny-svet-techniky'
 | 
				
			||||||
 | 
					    IE = CeskaTelevizeIE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_allsubtitles(self):
 | 
				
			||||||
 | 
					        self.DL.expect_warning('Automatic Captions not supported by this server')
 | 
				
			||||||
 | 
					        self.DL.params['writesubtitles'] = True
 | 
				
			||||||
 | 
					        self.DL.params['allsubtitles'] = True
 | 
				
			||||||
 | 
					        subtitles = self.getSubtitles()
 | 
				
			||||||
 | 
					        self.assertEqual(set(subtitles.keys()), set(['cs']))
 | 
				
			||||||
 | 
					        self.assertTrue(len(subtitles['cs']) > 20000)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_nosubtitles(self):
 | 
				
			||||||
 | 
					        self.DL.expect_warning('video doesn\'t have subtitles')
 | 
				
			||||||
 | 
					        self.url = 'http://www.ceskatelevize.cz/ivysilani/ivysilani/10441294653-hyde-park-civilizace/214411058091220'
 | 
				
			||||||
 | 
					        self.DL.params['writesubtitles'] = True
 | 
				
			||||||
 | 
					        self.DL.params['allsubtitles'] = True
 | 
				
			||||||
 | 
					        subtitles = self.getSubtitles()
 | 
				
			||||||
 | 
					        self.assertFalse(subtitles)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestLyndaSubtitles(BaseTestSubtitles):
 | 
				
			||||||
 | 
					    url = 'http://www.lynda.com/Bootstrap-tutorials/Using-exercise-files/110885/114408-4.html'
 | 
				
			||||||
 | 
					    IE = LyndaIE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_allsubtitles(self):
 | 
				
			||||||
 | 
					        self.DL.params['writesubtitles'] = True
 | 
				
			||||||
 | 
					        self.DL.params['allsubtitles'] = True
 | 
				
			||||||
 | 
					        subtitles = self.getSubtitles()
 | 
				
			||||||
 | 
					        self.assertEqual(set(subtitles.keys()), set(['en']))
 | 
				
			||||||
 | 
					        self.assertEqual(md5(subtitles['en']), '09bbe67222259bed60deaa26997d73a7')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestNPOSubtitles(BaseTestSubtitles):
 | 
				
			||||||
 | 
					    url = 'http://www.npo.nl/nos-journaal/28-08-2014/POW_00722860'
 | 
				
			||||||
 | 
					    IE = NPOIE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_allsubtitles(self):
 | 
				
			||||||
 | 
					        self.DL.params['writesubtitles'] = True
 | 
				
			||||||
 | 
					        self.DL.params['allsubtitles'] = True
 | 
				
			||||||
 | 
					        subtitles = self.getSubtitles()
 | 
				
			||||||
 | 
					        self.assertEqual(set(subtitles.keys()), set(['nl']))
 | 
				
			||||||
 | 
					        self.assertEqual(md5(subtitles['nl']), 'fc6435027572b63fb4ab143abd5ad3f4')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestMTVSubtitles(BaseTestSubtitles):
 | 
				
			||||||
 | 
					    url = 'http://www.cc.com/video-clips/kllhuv/stand-up-greg-fitzsimmons--uncensored---too-good-of-a-mother'
 | 
				
			||||||
 | 
					    IE = ComedyCentralIE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def getInfoDict(self):
 | 
				
			||||||
 | 
					        return super(TestMTVSubtitles, self).getInfoDict()['entries'][0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_allsubtitles(self):
 | 
				
			||||||
 | 
					        self.DL.params['writesubtitles'] = True
 | 
				
			||||||
 | 
					        self.DL.params['allsubtitles'] = True
 | 
				
			||||||
 | 
					        subtitles = self.getSubtitles()
 | 
				
			||||||
 | 
					        self.assertEqual(set(subtitles.keys()), set(['en']))
 | 
				
			||||||
 | 
					        self.assertEqual(md5(subtitles['en']), 'b9f6ca22a6acf597ec76f61749765e65')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestNRKSubtitles(BaseTestSubtitles):
 | 
				
			||||||
 | 
					    url = 'http://tv.nrk.no/serie/ikke-gjoer-dette-hjemme/DMPV73000411/sesong-2/episode-1'
 | 
				
			||||||
 | 
					    IE = NRKTVIE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_allsubtitles(self):
 | 
				
			||||||
 | 
					        self.DL.params['writesubtitles'] = True
 | 
				
			||||||
 | 
					        self.DL.params['allsubtitles'] = True
 | 
				
			||||||
 | 
					        subtitles = self.getSubtitles()
 | 
				
			||||||
 | 
					        self.assertEqual(set(subtitles.keys()), set(['no']))
 | 
				
			||||||
 | 
					        self.assertEqual(md5(subtitles['no']), '1d221e6458c95c5494dcd38e6a1f129a')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestRaiSubtitles(BaseTestSubtitles):
 | 
				
			||||||
 | 
					    url = 'http://www.rai.tv/dl/RaiTV/programmi/media/ContentItem-cb27157f-9dd0-4aee-b788-b1f67643a391.html'
 | 
				
			||||||
 | 
					    IE = RaiIE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_allsubtitles(self):
 | 
				
			||||||
 | 
					        self.DL.params['writesubtitles'] = True
 | 
				
			||||||
 | 
					        self.DL.params['allsubtitles'] = True
 | 
				
			||||||
 | 
					        subtitles = self.getSubtitles()
 | 
				
			||||||
 | 
					        self.assertEqual(set(subtitles.keys()), set(['it']))
 | 
				
			||||||
 | 
					        self.assertEqual(md5(subtitles['it']), 'b1d90a98755126b61e667567a1f6680a')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestVikiSubtitles(BaseTestSubtitles):
 | 
				
			||||||
 | 
					    url = 'http://www.viki.com/videos/1060846v-punch-episode-18'
 | 
				
			||||||
 | 
					    IE = VikiIE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_allsubtitles(self):
 | 
				
			||||||
 | 
					        self.DL.params['writesubtitles'] = True
 | 
				
			||||||
 | 
					        self.DL.params['allsubtitles'] = True
 | 
				
			||||||
 | 
					        subtitles = self.getSubtitles()
 | 
				
			||||||
 | 
					        self.assertEqual(set(subtitles.keys()), set(['en']))
 | 
				
			||||||
 | 
					        self.assertEqual(md5(subtitles['en']), '53cb083a5914b2d84ef1ab67b880d18a')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestThePlatformSubtitles(BaseTestSubtitles):
 | 
				
			||||||
 | 
					    # from http://www.3playmedia.com/services-features/tools/integrations/theplatform/
 | 
				
			||||||
 | 
					    # (see http://theplatform.com/about/partners/type/subtitles-closed-captioning/)
 | 
				
			||||||
 | 
					    url = 'theplatform:JFUjUE1_ehvq'
 | 
				
			||||||
 | 
					    IE = ThePlatformIE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_allsubtitles(self):
 | 
				
			||||||
 | 
					        self.DL.params['writesubtitles'] = True
 | 
				
			||||||
 | 
					        self.DL.params['allsubtitles'] = True
 | 
				
			||||||
 | 
					        subtitles = self.getSubtitles()
 | 
				
			||||||
 | 
					        self.assertEqual(set(subtitles.keys()), set(['en']))
 | 
				
			||||||
 | 
					        self.assertEqual(md5(subtitles['en']), '97e7670cbae3c4d26ae8bcc7fdd78d4b')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestRtveSubtitles(BaseTestSubtitles):
 | 
				
			||||||
 | 
					    url = 'http://www.rtve.es/alacarta/videos/los-misterios-de-laura/misterios-laura-capitulo-32-misterio-del-numero-17-2-parte/2428621/'
 | 
				
			||||||
 | 
					    IE = RTVEALaCartaIE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_allsubtitles(self):
 | 
				
			||||||
 | 
					        print('Skipping, only available from Spain')
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					        self.DL.params['writesubtitles'] = True
 | 
				
			||||||
 | 
					        self.DL.params['allsubtitles'] = True
 | 
				
			||||||
 | 
					        subtitles = self.getSubtitles()
 | 
				
			||||||
 | 
					        self.assertEqual(set(subtitles.keys()), set(['es']))
 | 
				
			||||||
 | 
					        self.assertEqual(md5(subtitles['es']), '69e70cae2d40574fb7316f31d6eb7fca')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if __name__ == '__main__':
 | 
					if __name__ == '__main__':
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -34,8 +34,8 @@ def _make_testfunc(testfile):
 | 
				
			|||||||
    def test_func(self):
 | 
					    def test_func(self):
 | 
				
			||||||
        as_file = os.path.join(TEST_DIR, testfile)
 | 
					        as_file = os.path.join(TEST_DIR, testfile)
 | 
				
			||||||
        swf_file = os.path.join(TEST_DIR, test_id + '.swf')
 | 
					        swf_file = os.path.join(TEST_DIR, test_id + '.swf')
 | 
				
			||||||
        if ((not os.path.exists(swf_file))
 | 
					        if ((not os.path.exists(swf_file)) or
 | 
				
			||||||
                or os.path.getmtime(swf_file) < os.path.getmtime(as_file)):
 | 
					                os.path.getmtime(swf_file) < os.path.getmtime(as_file)):
 | 
				
			||||||
            # Recompile
 | 
					            # Recompile
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                subprocess.check_call([
 | 
					                subprocess.check_call([
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,9 +7,7 @@ import unittest
 | 
				
			|||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
 | 
					sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import io
 | 
					import io
 | 
				
			||||||
import os
 | 
					 | 
				
			||||||
import re
 | 
					import re
 | 
				
			||||||
import unittest
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
rootDir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 | 
					rootDir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,6 +16,7 @@ import json
 | 
				
			|||||||
import xml.etree.ElementTree
 | 
					import xml.etree.ElementTree
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from youtube_dl.utils import (
 | 
					from youtube_dl.utils import (
 | 
				
			||||||
 | 
					    age_restricted,
 | 
				
			||||||
    args_to_str,
 | 
					    args_to_str,
 | 
				
			||||||
    clean_html,
 | 
					    clean_html,
 | 
				
			||||||
    DateRange,
 | 
					    DateRange,
 | 
				
			||||||
@@ -27,6 +28,7 @@ from youtube_dl.utils import (
 | 
				
			|||||||
    fix_xml_ampersands,
 | 
					    fix_xml_ampersands,
 | 
				
			||||||
    InAdvancePagedList,
 | 
					    InAdvancePagedList,
 | 
				
			||||||
    intlist_to_bytes,
 | 
					    intlist_to_bytes,
 | 
				
			||||||
 | 
					    is_html,
 | 
				
			||||||
    js_to_json,
 | 
					    js_to_json,
 | 
				
			||||||
    limit_length,
 | 
					    limit_length,
 | 
				
			||||||
    OnDemandPagedList,
 | 
					    OnDemandPagedList,
 | 
				
			||||||
@@ -50,6 +52,8 @@ from youtube_dl.utils import (
 | 
				
			|||||||
    urlencode_postdata,
 | 
					    urlencode_postdata,
 | 
				
			||||||
    version_tuple,
 | 
					    version_tuple,
 | 
				
			||||||
    xpath_with_ns,
 | 
					    xpath_with_ns,
 | 
				
			||||||
 | 
					    render_table,
 | 
				
			||||||
 | 
					    match_str,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -78,6 +82,15 @@ class TestUtil(unittest.TestCase):
 | 
				
			|||||||
        tests = '\u043a\u0438\u0440\u0438\u043b\u043b\u0438\u0446\u0430'
 | 
					        tests = '\u043a\u0438\u0440\u0438\u043b\u043b\u0438\u0446\u0430'
 | 
				
			||||||
        self.assertEqual(sanitize_filename(tests), tests)
 | 
					        self.assertEqual(sanitize_filename(tests), tests)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertEqual(
 | 
				
			||||||
 | 
					            sanitize_filename('New World record at 0:12:34'),
 | 
				
			||||||
 | 
					            'New World record at 0_12_34')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertEqual(sanitize_filename('--gasdgf'), '_-gasdgf')
 | 
				
			||||||
 | 
					        self.assertEqual(sanitize_filename('--gasdgf', is_id=True), '--gasdgf')
 | 
				
			||||||
 | 
					        self.assertEqual(sanitize_filename('.gasdgf'), 'gasdgf')
 | 
				
			||||||
 | 
					        self.assertEqual(sanitize_filename('.gasdgf', is_id=True), '.gasdgf')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        forbidden = '"\0\\/'
 | 
					        forbidden = '"\0\\/'
 | 
				
			||||||
        for fc in forbidden:
 | 
					        for fc in forbidden:
 | 
				
			||||||
            for fbc in forbidden:
 | 
					            for fbc in forbidden:
 | 
				
			||||||
@@ -143,11 +156,15 @@ class TestUtil(unittest.TestCase):
 | 
				
			|||||||
        self.assertEqual(unified_strdate('8/7/2009'), '20090708')
 | 
					        self.assertEqual(unified_strdate('8/7/2009'), '20090708')
 | 
				
			||||||
        self.assertEqual(unified_strdate('Dec 14, 2012'), '20121214')
 | 
					        self.assertEqual(unified_strdate('Dec 14, 2012'), '20121214')
 | 
				
			||||||
        self.assertEqual(unified_strdate('2012/10/11 01:56:38 +0000'), '20121011')
 | 
					        self.assertEqual(unified_strdate('2012/10/11 01:56:38 +0000'), '20121011')
 | 
				
			||||||
 | 
					        self.assertEqual(unified_strdate('1968 12 10'), '19681210')
 | 
				
			||||||
        self.assertEqual(unified_strdate('1968-12-10'), '19681210')
 | 
					        self.assertEqual(unified_strdate('1968-12-10'), '19681210')
 | 
				
			||||||
        self.assertEqual(unified_strdate('28/01/2014 21:00:00 +0100'), '20140128')
 | 
					        self.assertEqual(unified_strdate('28/01/2014 21:00:00 +0100'), '20140128')
 | 
				
			||||||
        self.assertEqual(
 | 
					        self.assertEqual(
 | 
				
			||||||
            unified_strdate('11/26/2014 11:30:00 AM PST', day_first=False),
 | 
					            unified_strdate('11/26/2014 11:30:00 AM PST', day_first=False),
 | 
				
			||||||
            '20141126')
 | 
					            '20141126')
 | 
				
			||||||
 | 
					        self.assertEqual(
 | 
				
			||||||
 | 
					            unified_strdate('2/2/2015 6:47:40 PM', day_first=False),
 | 
				
			||||||
 | 
					            '20150202')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_find_xpath_attr(self):
 | 
					    def test_find_xpath_attr(self):
 | 
				
			||||||
        testxml = '''<root>
 | 
					        testxml = '''<root>
 | 
				
			||||||
@@ -207,6 +224,8 @@ class TestUtil(unittest.TestCase):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def test_parse_duration(self):
 | 
					    def test_parse_duration(self):
 | 
				
			||||||
        self.assertEqual(parse_duration(None), None)
 | 
					        self.assertEqual(parse_duration(None), None)
 | 
				
			||||||
 | 
					        self.assertEqual(parse_duration(False), None)
 | 
				
			||||||
 | 
					        self.assertEqual(parse_duration('invalid'), None)
 | 
				
			||||||
        self.assertEqual(parse_duration('1'), 1)
 | 
					        self.assertEqual(parse_duration('1'), 1)
 | 
				
			||||||
        self.assertEqual(parse_duration('1337:12'), 80232)
 | 
					        self.assertEqual(parse_duration('1337:12'), 80232)
 | 
				
			||||||
        self.assertEqual(parse_duration('9:12:43'), 33163)
 | 
					        self.assertEqual(parse_duration('9:12:43'), 33163)
 | 
				
			||||||
@@ -228,6 +247,9 @@ class TestUtil(unittest.TestCase):
 | 
				
			|||||||
        self.assertEqual(parse_duration('5 s'), 5)
 | 
					        self.assertEqual(parse_duration('5 s'), 5)
 | 
				
			||||||
        self.assertEqual(parse_duration('3 min'), 180)
 | 
					        self.assertEqual(parse_duration('3 min'), 180)
 | 
				
			||||||
        self.assertEqual(parse_duration('2.5 hours'), 9000)
 | 
					        self.assertEqual(parse_duration('2.5 hours'), 9000)
 | 
				
			||||||
 | 
					        self.assertEqual(parse_duration('02:03:04'), 7384)
 | 
				
			||||||
 | 
					        self.assertEqual(parse_duration('01:02:03:04'), 93784)
 | 
				
			||||||
 | 
					        self.assertEqual(parse_duration('1 hour 3 minutes'), 3780)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_fix_xml_ampersands(self):
 | 
					    def test_fix_xml_ampersands(self):
 | 
				
			||||||
        self.assertEqual(
 | 
					        self.assertEqual(
 | 
				
			||||||
@@ -354,6 +376,10 @@ class TestUtil(unittest.TestCase):
 | 
				
			|||||||
            "playlist":[{"controls":{"all":null}}]
 | 
					            "playlist":[{"controls":{"all":null}}]
 | 
				
			||||||
        }''')
 | 
					        }''')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        inp = '"SAND Number: SAND 2013-7800P\\nPresenter: Tom Russo\\nHabanero Software Training - Xyce Software\\nXyce, Sandia\\u0027s"'
 | 
				
			||||||
 | 
					        json_code = js_to_json(inp)
 | 
				
			||||||
 | 
					        self.assertEqual(json.loads(json_code), json.loads(inp))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_js_to_json_edgecases(self):
 | 
					    def test_js_to_json_edgecases(self):
 | 
				
			||||||
        on = js_to_json("{abc_def:'1\\'\\\\2\\\\\\'3\"4'}")
 | 
					        on = js_to_json("{abc_def:'1\\'\\\\2\\\\\\'3\"4'}")
 | 
				
			||||||
        self.assertEqual(json.loads(on), {"abc_def": "1'\\2\\'3\"4"})
 | 
					        self.assertEqual(json.loads(on), {"abc_def": "1'\\2\\'3\"4"})
 | 
				
			||||||
@@ -361,6 +387,16 @@ class TestUtil(unittest.TestCase):
 | 
				
			|||||||
        on = js_to_json('{"abc": true}')
 | 
					        on = js_to_json('{"abc": true}')
 | 
				
			||||||
        self.assertEqual(json.loads(on), {'abc': True})
 | 
					        self.assertEqual(json.loads(on), {'abc': True})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Ignore JavaScript code as well
 | 
				
			||||||
 | 
					        on = js_to_json('''{
 | 
				
			||||||
 | 
					            "x": 1,
 | 
				
			||||||
 | 
					            y: "a",
 | 
				
			||||||
 | 
					            z: some.code
 | 
				
			||||||
 | 
					        }''')
 | 
				
			||||||
 | 
					        d = json.loads(on)
 | 
				
			||||||
 | 
					        self.assertEqual(d['x'], 1)
 | 
				
			||||||
 | 
					        self.assertEqual(d['y'], 'a')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_clean_html(self):
 | 
					    def test_clean_html(self):
 | 
				
			||||||
        self.assertEqual(clean_html('a:\nb'), 'a: b')
 | 
					        self.assertEqual(clean_html('a:\nb'), 'a: b')
 | 
				
			||||||
        self.assertEqual(clean_html('a:\n   "b"'), 'a:    "b"')
 | 
					        self.assertEqual(clean_html('a:\n   "b"'), 'a:    "b"')
 | 
				
			||||||
@@ -402,5 +438,69 @@ Trying to open render node...
 | 
				
			|||||||
Success at /dev/dri/renderD128.
 | 
					Success at /dev/dri/renderD128.
 | 
				
			||||||
ffmpeg version 2.4.4 Copyright (c) 2000-2014 the FFmpeg ...'''), '2.4.4')
 | 
					ffmpeg version 2.4.4 Copyright (c) 2000-2014 the FFmpeg ...'''), '2.4.4')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_age_restricted(self):
 | 
				
			||||||
 | 
					        self.assertFalse(age_restricted(None, 10))  # unrestricted content
 | 
				
			||||||
 | 
					        self.assertFalse(age_restricted(1, None))  # unrestricted policy
 | 
				
			||||||
 | 
					        self.assertFalse(age_restricted(8, 10))
 | 
				
			||||||
 | 
					        self.assertTrue(age_restricted(18, 14))
 | 
				
			||||||
 | 
					        self.assertFalse(age_restricted(18, 18))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_is_html(self):
 | 
				
			||||||
 | 
					        self.assertFalse(is_html(b'\x49\x44\x43<html'))
 | 
				
			||||||
 | 
					        self.assertTrue(is_html(b'<!DOCTYPE foo>\xaaa'))
 | 
				
			||||||
 | 
					        self.assertTrue(is_html(  # UTF-8 with BOM
 | 
				
			||||||
 | 
					            b'\xef\xbb\xbf<!DOCTYPE foo>\xaaa'))
 | 
				
			||||||
 | 
					        self.assertTrue(is_html(  # UTF-16-LE
 | 
				
			||||||
 | 
					            b'\xff\xfe<\x00h\x00t\x00m\x00l\x00>\x00\xe4\x00'
 | 
				
			||||||
 | 
					        ))
 | 
				
			||||||
 | 
					        self.assertTrue(is_html(  # UTF-16-BE
 | 
				
			||||||
 | 
					            b'\xfe\xff\x00<\x00h\x00t\x00m\x00l\x00>\x00\xe4'
 | 
				
			||||||
 | 
					        ))
 | 
				
			||||||
 | 
					        self.assertTrue(is_html(  # UTF-32-BE
 | 
				
			||||||
 | 
					            b'\x00\x00\xFE\xFF\x00\x00\x00<\x00\x00\x00h\x00\x00\x00t\x00\x00\x00m\x00\x00\x00l\x00\x00\x00>\x00\x00\x00\xe4'))
 | 
				
			||||||
 | 
					        self.assertTrue(is_html(  # UTF-32-LE
 | 
				
			||||||
 | 
					            b'\xFF\xFE\x00\x00<\x00\x00\x00h\x00\x00\x00t\x00\x00\x00m\x00\x00\x00l\x00\x00\x00>\x00\x00\x00\xe4\x00\x00\x00'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_render_table(self):
 | 
				
			||||||
 | 
					        self.assertEqual(
 | 
				
			||||||
 | 
					            render_table(
 | 
				
			||||||
 | 
					                ['a', 'bcd'],
 | 
				
			||||||
 | 
					                [[123, 4], [9999, 51]]),
 | 
				
			||||||
 | 
					            'a    bcd\n'
 | 
				
			||||||
 | 
					            '123  4\n'
 | 
				
			||||||
 | 
					            '9999 51')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_match_str(self):
 | 
				
			||||||
 | 
					        self.assertRaises(ValueError, match_str, 'xy>foobar', {})
 | 
				
			||||||
 | 
					        self.assertFalse(match_str('xy', {'x': 1200}))
 | 
				
			||||||
 | 
					        self.assertTrue(match_str('!xy', {'x': 1200}))
 | 
				
			||||||
 | 
					        self.assertTrue(match_str('x', {'x': 1200}))
 | 
				
			||||||
 | 
					        self.assertFalse(match_str('!x', {'x': 1200}))
 | 
				
			||||||
 | 
					        self.assertTrue(match_str('x', {'x': 0}))
 | 
				
			||||||
 | 
					        self.assertFalse(match_str('x>0', {'x': 0}))
 | 
				
			||||||
 | 
					        self.assertFalse(match_str('x>0', {}))
 | 
				
			||||||
 | 
					        self.assertTrue(match_str('x>?0', {}))
 | 
				
			||||||
 | 
					        self.assertTrue(match_str('x>1K', {'x': 1200}))
 | 
				
			||||||
 | 
					        self.assertFalse(match_str('x>2K', {'x': 1200}))
 | 
				
			||||||
 | 
					        self.assertTrue(match_str('x>=1200 & x < 1300', {'x': 1200}))
 | 
				
			||||||
 | 
					        self.assertFalse(match_str('x>=1100 & x < 1200', {'x': 1200}))
 | 
				
			||||||
 | 
					        self.assertFalse(match_str('y=a212', {'y': 'foobar42'}))
 | 
				
			||||||
 | 
					        self.assertTrue(match_str('y=foobar42', {'y': 'foobar42'}))
 | 
				
			||||||
 | 
					        self.assertFalse(match_str('y!=foobar42', {'y': 'foobar42'}))
 | 
				
			||||||
 | 
					        self.assertTrue(match_str('y!=foobar2', {'y': 'foobar42'}))
 | 
				
			||||||
 | 
					        self.assertFalse(match_str(
 | 
				
			||||||
 | 
					            'like_count > 100 & dislike_count <? 50 & description',
 | 
				
			||||||
 | 
					            {'like_count': 90, 'description': 'foo'}))
 | 
				
			||||||
 | 
					        self.assertTrue(match_str(
 | 
				
			||||||
 | 
					            'like_count > 100 & dislike_count <? 50 & description',
 | 
				
			||||||
 | 
					            {'like_count': 190, 'description': 'foo'}))
 | 
				
			||||||
 | 
					        self.assertFalse(match_str(
 | 
				
			||||||
 | 
					            'like_count > 100 & dislike_count <? 50 & description',
 | 
				
			||||||
 | 
					            {'like_count': 190, 'dislike_count': 60, 'description': 'foo'}))
 | 
				
			||||||
 | 
					        self.assertFalse(match_str(
 | 
				
			||||||
 | 
					            'like_count > 100 & dislike_count <? 50 & description',
 | 
				
			||||||
 | 
					            {'like_count': 190, 'dislike_count': 10}))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if __name__ == '__main__':
 | 
					if __name__ == '__main__':
 | 
				
			||||||
    unittest.main()
 | 
					    unittest.main()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,11 +8,11 @@ import sys
 | 
				
			|||||||
import unittest
 | 
					import unittest
 | 
				
			||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
 | 
					sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
import io
 | 
					import io
 | 
				
			||||||
import re
 | 
					import re
 | 
				
			||||||
import string
 | 
					import string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from test.helper import FakeYDL
 | 
				
			||||||
from youtube_dl.extractor import YoutubeIE
 | 
					from youtube_dl.extractor import YoutubeIE
 | 
				
			||||||
from youtube_dl.compat import compat_str, compat_urlretrieve
 | 
					from youtube_dl.compat import compat_str, compat_urlretrieve
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -64,6 +64,12 @@ _TESTS = [
 | 
				
			|||||||
        'js',
 | 
					        'js',
 | 
				
			||||||
        '4646B5181C6C3020DF1D9C7FCFEA.AD80ABF70C39BD369CCCAE780AFBB98FA6B6CB42766249D9488C288',
 | 
					        '4646B5181C6C3020DF1D9C7FCFEA.AD80ABF70C39BD369CCCAE780AFBB98FA6B6CB42766249D9488C288',
 | 
				
			||||||
        '82C8849D94266724DC6B6AF89BBFA087EACCD963.B93C07FBA084ACAEFCF7C9D1FD0203C6C1815B6B'
 | 
					        '82C8849D94266724DC6B6AF89BBFA087EACCD963.B93C07FBA084ACAEFCF7C9D1FD0203C6C1815B6B'
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    (
 | 
				
			||||||
 | 
					        'https://s.ytimg.com/yts/jsbin/html5player-en_US-vflKjOTVq/html5player.js',
 | 
				
			||||||
 | 
					        'js',
 | 
				
			||||||
 | 
					        '312AA52209E3623129A412D56A40F11CB0AF14AE.3EE09501CB14E3BCDC3B2AE808BF3F1D14E7FBF12',
 | 
				
			||||||
 | 
					        '112AA5220913623229A412D56A40F11CB0AF14AE.3EE0950FCB14EEBCDC3B2AE808BF331D14E7FBF3',
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -88,7 +94,8 @@ def make_tfunc(url, stype, sig_input, expected_sig):
 | 
				
			|||||||
        if not os.path.exists(fn):
 | 
					        if not os.path.exists(fn):
 | 
				
			||||||
            compat_urlretrieve(url, fn)
 | 
					            compat_urlretrieve(url, fn)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ie = YoutubeIE()
 | 
					        ydl = FakeYDL()
 | 
				
			||||||
 | 
					        ie = YoutubeIE(ydl)
 | 
				
			||||||
        if stype == 'js':
 | 
					        if stype == 'js':
 | 
				
			||||||
            with io.open(fn, encoding='utf-8') as testf:
 | 
					            with io.open(fn, encoding='utf-8') as testf:
 | 
				
			||||||
                jscode = testf.read()
 | 
					                jscode = testf.read()
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										52
									
								
								test/testcert.pem
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								test/testcert.pem
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
				
			|||||||
 | 
					-----BEGIN PRIVATE KEY-----
 | 
				
			||||||
 | 
					MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDMF0bAzaHAdIyB
 | 
				
			||||||
 | 
					HRmnIp4vv40lGqEePmWqicCl0QZ0wsb5dNysSxSa7330M2QeQopGfdaUYF1uTcNp
 | 
				
			||||||
 | 
					Qx6ECgBSfg+RrOBI7r/u4F+sKX8MUXVaf/5QoBUrGNGSn/pp7HMGOuQqO6BVg4+h
 | 
				
			||||||
 | 
					A1ySSwUG8mZItLRry1ISyErmW8b9xlqfd97uLME/5tX+sMelRFjUbAx8A4CK58Ev
 | 
				
			||||||
 | 
					mMguHVTlXzx5RMdYcf1VScYcjlV/qA45uzP8zwI5aigfcmUD+tbGuQRhKxUhmw0J
 | 
				
			||||||
 | 
					aobtOR6+JSOAULW5gYa/egE4dWLwbyM6b6eFbdnjlQzEA1EW7ChMPAW/Mo83KyiP
 | 
				
			||||||
 | 
					tKMCSQulAgMBAAECggEALCfBDAexPjU5DNoh6bIorUXxIJzxTNzNHCdvgbCGiA54
 | 
				
			||||||
 | 
					BBKPh8s6qwazpnjT6WQWDIg/O5zZufqjE4wM9x4+0Zoqfib742ucJO9wY4way6x4
 | 
				
			||||||
 | 
					Clt0xzbLPabB+MoZ4H7ip+9n2+dImhe7pGdYyOHoNYeOL57BBi1YFW42Hj6u/8pd
 | 
				
			||||||
 | 
					63YCXisto3Rz1YvRQVjwsrS+cRKZlzAFQRviL30jav7Wh1aWEfcXxjj4zhm8pJdk
 | 
				
			||||||
 | 
					ITGtq6howz57M0NtX6hZnfe8ywzTnDFIGKIMA2cYHuYJcBh9bc4tCGubTvTKK9UE
 | 
				
			||||||
 | 
					8fM+f6UbfGqfpKCq1mcgs0XMoFDSzKS9+mSJn0+5JQKBgQD+OCKaeH3Yzw5zGnlw
 | 
				
			||||||
 | 
					XuQfMJGNcgNr+ImjmvzUAC2fAZUJLAcQueE5kzMv5Fmd+EFE2CEX1Vit3tg0SXvA
 | 
				
			||||||
 | 
					G+bq609doILHMA03JHnV1npO/YNIhG3AAtJlKYGxQNfWH9mflYj9mEui8ZFxG52o
 | 
				
			||||||
 | 
					zWhHYuifOjjZszUR+/eio6NPzwKBgQDNhUBTrT8LIX4SE/EFUiTlYmWIvOMgXYvN
 | 
				
			||||||
 | 
					8Cm3IRNQ/yyphZaXEU0eJzfX5uCDfSVOgd6YM/2pRah+t+1Hvey4H8e0GVTu5wMP
 | 
				
			||||||
 | 
					gkkqwKPGIR1YOmlw6ippqwvoJD7LuYrm6Q4D6e1PvkjwCq6lEndrOPmPrrXNd0JJ
 | 
				
			||||||
 | 
					XO60y3U2SwKBgQDLkyZarryQXxcCI6Q10Tc6pskYDMIit095PUbTeiUOXNT9GE28
 | 
				
			||||||
 | 
					Hi32ziLCakk9kCysNasii81MxtQ54tJ/f5iGbNMMddnkKl2a19Hc5LjjAm4cJzg/
 | 
				
			||||||
 | 
					98KGEhvyVqvAo5bBDZ06/rcrD+lZOzUglQS5jcIcqCIYa0LHWQ/wJLxFzwKBgFcZ
 | 
				
			||||||
 | 
					1SRhdSmDfUmuF+S4ZpistflYjC3IV5rk4NkS9HvMWaJS0nqdw4A3AMzItXgkjq4S
 | 
				
			||||||
 | 
					DkOVLTkTI5Do5HAWRv/VwC5M2hkR4NMu1VGAKSisGiKtRsirBWSZMEenLNHshbjN
 | 
				
			||||||
 | 
					Jrpz5rZ4H7NT46ZkCCZyFBpX4gb9NyOedjA7Via3AoGARF8RxbYjnEGGFuhnbrJB
 | 
				
			||||||
 | 
					FTPR0vaL4faY3lOgRZ8jOG9V2c9Hzi/y8a8TU4C11jnJSDqYCXBTd5XN28npYxtD
 | 
				
			||||||
 | 
					pjRsCwy6ze+yvYXPO7C978eMG3YRyj366NXUxnXN59ibwe/lxi2OD9z8J1LEdF6z
 | 
				
			||||||
 | 
					VJua1Wn8HKxnXMI61DhTCSo=
 | 
				
			||||||
 | 
					-----END PRIVATE KEY-----
 | 
				
			||||||
 | 
					-----BEGIN CERTIFICATE-----
 | 
				
			||||||
 | 
					MIIEEzCCAvugAwIBAgIJAK1haYi6gmSKMA0GCSqGSIb3DQEBCwUAMIGeMQswCQYD
 | 
				
			||||||
 | 
					VQQGEwJERTEMMAoGA1UECAwDTlJXMRQwEgYDVQQHDAtEdWVzc2VsZG9yZjEbMBkG
 | 
				
			||||||
 | 
					A1UECgwSeW91dHViZS1kbCBwcm9qZWN0MRkwFwYDVQQLDBB5b3V0dWJlLWRsIHRl
 | 
				
			||||||
 | 
					c3RzMRIwEAYDVQQDDAlsb2NhbGhvc3QxHzAdBgkqhkiG9w0BCQEWEHBoaWhhZ0Bw
 | 
				
			||||||
 | 
					aGloYWcuZGUwIBcNMTUwMTMwMDExNTA4WhgPMjExNTAxMDYwMTE1MDhaMIGeMQsw
 | 
				
			||||||
 | 
					CQYDVQQGEwJERTEMMAoGA1UECAwDTlJXMRQwEgYDVQQHDAtEdWVzc2VsZG9yZjEb
 | 
				
			||||||
 | 
					MBkGA1UECgwSeW91dHViZS1kbCBwcm9qZWN0MRkwFwYDVQQLDBB5b3V0dWJlLWRs
 | 
				
			||||||
 | 
					IHRlc3RzMRIwEAYDVQQDDAlsb2NhbGhvc3QxHzAdBgkqhkiG9w0BCQEWEHBoaWhh
 | 
				
			||||||
 | 
					Z0BwaGloYWcuZGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDMF0bA
 | 
				
			||||||
 | 
					zaHAdIyBHRmnIp4vv40lGqEePmWqicCl0QZ0wsb5dNysSxSa7330M2QeQopGfdaU
 | 
				
			||||||
 | 
					YF1uTcNpQx6ECgBSfg+RrOBI7r/u4F+sKX8MUXVaf/5QoBUrGNGSn/pp7HMGOuQq
 | 
				
			||||||
 | 
					O6BVg4+hA1ySSwUG8mZItLRry1ISyErmW8b9xlqfd97uLME/5tX+sMelRFjUbAx8
 | 
				
			||||||
 | 
					A4CK58EvmMguHVTlXzx5RMdYcf1VScYcjlV/qA45uzP8zwI5aigfcmUD+tbGuQRh
 | 
				
			||||||
 | 
					KxUhmw0JaobtOR6+JSOAULW5gYa/egE4dWLwbyM6b6eFbdnjlQzEA1EW7ChMPAW/
 | 
				
			||||||
 | 
					Mo83KyiPtKMCSQulAgMBAAGjUDBOMB0GA1UdDgQWBBTBUZoqhQkzHQ6xNgZfFxOd
 | 
				
			||||||
 | 
					ZEVt8TAfBgNVHSMEGDAWgBTBUZoqhQkzHQ6xNgZfFxOdZEVt8TAMBgNVHRMEBTAD
 | 
				
			||||||
 | 
					AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCUOCl3T/J9B08Z+ijfOJAtkbUaEHuVZb4x
 | 
				
			||||||
 | 
					5EpZSy2ZbkLvtsftMFieHVNXn9dDswQc5qjYStCC4o60LKw4M6Y63FRsAZ/DNaqb
 | 
				
			||||||
 | 
					PY3jyCyuugZ8/sNf50vHYkAcF7SQYqOQFQX4TQsNUk2xMJIt7H0ErQFmkf/u3dg6
 | 
				
			||||||
 | 
					cy89zkT462IwxzSG7NNhIlRkL9o5qg+Y1mF9eZA1B0rcL6hO24PPTHOd90HDChBu
 | 
				
			||||||
 | 
					SZ6XMi/LzYQSTf0Vg2R+uMIVlzSlkdcZ6sqVnnqeLL8dFyIa4e9sj/D4ZCYP8Mqe
 | 
				
			||||||
 | 
					Z73H5/NNhmwCHRqVUTgm307xblQaWGhwAiDkaRvRW2aJQ0qGEdZK
 | 
				
			||||||
 | 
					-----END CERTIFICATE-----
 | 
				
			||||||
@@ -4,12 +4,15 @@
 | 
				
			|||||||
from __future__ import absolute_import, unicode_literals
 | 
					from __future__ import absolute_import, unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import collections
 | 
					import collections
 | 
				
			||||||
 | 
					import contextlib
 | 
				
			||||||
import datetime
 | 
					import datetime
 | 
				
			||||||
import errno
 | 
					import errno
 | 
				
			||||||
 | 
					import fileinput
 | 
				
			||||||
import io
 | 
					import io
 | 
				
			||||||
import itertools
 | 
					import itertools
 | 
				
			||||||
import json
 | 
					import json
 | 
				
			||||||
import locale
 | 
					import locale
 | 
				
			||||||
 | 
					import operator
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
import platform
 | 
					import platform
 | 
				
			||||||
import re
 | 
					import re
 | 
				
			||||||
@@ -24,8 +27,10 @@ if os.name == 'nt':
 | 
				
			|||||||
    import ctypes
 | 
					    import ctypes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .compat import (
 | 
					from .compat import (
 | 
				
			||||||
 | 
					    compat_basestring,
 | 
				
			||||||
    compat_cookiejar,
 | 
					    compat_cookiejar,
 | 
				
			||||||
    compat_expanduser,
 | 
					    compat_expanduser,
 | 
				
			||||||
 | 
					    compat_get_terminal_size,
 | 
				
			||||||
    compat_http_client,
 | 
					    compat_http_client,
 | 
				
			||||||
    compat_kwargs,
 | 
					    compat_kwargs,
 | 
				
			||||||
    compat_str,
 | 
					    compat_str,
 | 
				
			||||||
@@ -44,31 +49,37 @@ from .utils import (
 | 
				
			|||||||
    ExtractorError,
 | 
					    ExtractorError,
 | 
				
			||||||
    format_bytes,
 | 
					    format_bytes,
 | 
				
			||||||
    formatSeconds,
 | 
					    formatSeconds,
 | 
				
			||||||
    get_term_width,
 | 
					 | 
				
			||||||
    locked_file,
 | 
					    locked_file,
 | 
				
			||||||
    make_HTTPS_handler,
 | 
					    make_HTTPS_handler,
 | 
				
			||||||
    MaxDownloadsReached,
 | 
					    MaxDownloadsReached,
 | 
				
			||||||
    PagedList,
 | 
					    PagedList,
 | 
				
			||||||
 | 
					    parse_filesize,
 | 
				
			||||||
    PostProcessingError,
 | 
					    PostProcessingError,
 | 
				
			||||||
    platform_name,
 | 
					    platform_name,
 | 
				
			||||||
    preferredencoding,
 | 
					    preferredencoding,
 | 
				
			||||||
 | 
					    render_table,
 | 
				
			||||||
    SameFileError,
 | 
					    SameFileError,
 | 
				
			||||||
    sanitize_filename,
 | 
					    sanitize_filename,
 | 
				
			||||||
 | 
					    std_headers,
 | 
				
			||||||
    subtitles_filename,
 | 
					    subtitles_filename,
 | 
				
			||||||
    takewhile_inclusive,
 | 
					    takewhile_inclusive,
 | 
				
			||||||
    UnavailableVideoError,
 | 
					    UnavailableVideoError,
 | 
				
			||||||
    url_basename,
 | 
					    url_basename,
 | 
				
			||||||
 | 
					    version_tuple,
 | 
				
			||||||
    write_json_file,
 | 
					    write_json_file,
 | 
				
			||||||
    write_string,
 | 
					    write_string,
 | 
				
			||||||
    YoutubeDLHandler,
 | 
					    YoutubeDLHandler,
 | 
				
			||||||
    prepend_extension,
 | 
					    prepend_extension,
 | 
				
			||||||
    args_to_str,
 | 
					    args_to_str,
 | 
				
			||||||
 | 
					    age_restricted,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from .cache import Cache
 | 
					from .cache import Cache
 | 
				
			||||||
from .extractor import get_info_extractor, gen_extractors
 | 
					from .extractor import get_info_extractor, gen_extractors
 | 
				
			||||||
from .downloader import get_suitable_downloader
 | 
					from .downloader import get_suitable_downloader
 | 
				
			||||||
from .downloader.rtmp import rtmpdump_version
 | 
					from .downloader.rtmp import rtmpdump_version
 | 
				
			||||||
from .postprocessor import (
 | 
					from .postprocessor import (
 | 
				
			||||||
 | 
					    FFmpegFixupM4aPP,
 | 
				
			||||||
 | 
					    FFmpegFixupStretchedPP,
 | 
				
			||||||
    FFmpegMergerPP,
 | 
					    FFmpegMergerPP,
 | 
				
			||||||
    FFmpegPostProcessor,
 | 
					    FFmpegPostProcessor,
 | 
				
			||||||
    get_postprocessor,
 | 
					    get_postprocessor,
 | 
				
			||||||
@@ -129,6 +140,7 @@ class YoutubeDL(object):
 | 
				
			|||||||
    nooverwrites:      Prevent overwriting files.
 | 
					    nooverwrites:      Prevent overwriting files.
 | 
				
			||||||
    playliststart:     Playlist item to start at.
 | 
					    playliststart:     Playlist item to start at.
 | 
				
			||||||
    playlistend:       Playlist item to end at.
 | 
					    playlistend:       Playlist item to end at.
 | 
				
			||||||
 | 
					    playlist_items:    Specific indices of playlist to download.
 | 
				
			||||||
    playlistreverse:   Download playlist items in reverse order.
 | 
					    playlistreverse:   Download playlist items in reverse order.
 | 
				
			||||||
    matchtitle:        Download only matching titles.
 | 
					    matchtitle:        Download only matching titles.
 | 
				
			||||||
    rejecttitle:       Reject downloads for matching titles.
 | 
					    rejecttitle:       Reject downloads for matching titles.
 | 
				
			||||||
@@ -138,12 +150,13 @@ class YoutubeDL(object):
 | 
				
			|||||||
    writeinfojson:     Write the video description to a .info.json file
 | 
					    writeinfojson:     Write the video description to a .info.json file
 | 
				
			||||||
    writeannotations:  Write the video annotations to a .annotations.xml file
 | 
					    writeannotations:  Write the video annotations to a .annotations.xml file
 | 
				
			||||||
    writethumbnail:    Write the thumbnail image to a file
 | 
					    writethumbnail:    Write the thumbnail image to a file
 | 
				
			||||||
 | 
					    write_all_thumbnails:  Write all thumbnail formats to files
 | 
				
			||||||
    writesubtitles:    Write the video subtitles to a file
 | 
					    writesubtitles:    Write the video subtitles to a file
 | 
				
			||||||
    writeautomaticsub: Write the automatic subtitles to a file
 | 
					    writeautomaticsub: Write the automatic subtitles to a file
 | 
				
			||||||
    allsubtitles:      Downloads all the subtitles of the video
 | 
					    allsubtitles:      Downloads all the subtitles of the video
 | 
				
			||||||
                       (requires writesubtitles or writeautomaticsub)
 | 
					                       (requires writesubtitles or writeautomaticsub)
 | 
				
			||||||
    listsubtitles:     Lists all available subtitles for the video
 | 
					    listsubtitles:     Lists all available subtitles for the video
 | 
				
			||||||
    subtitlesformat:   Subtitle format [srt/sbv/vtt] (default=srt)
 | 
					    subtitlesformat:   The format code for subtitles
 | 
				
			||||||
    subtitleslangs:    List of languages of the subtitles to download
 | 
					    subtitleslangs:    List of languages of the subtitles to download
 | 
				
			||||||
    keepvideo:         Keep the video file after post-processing
 | 
					    keepvideo:         Keep the video file after post-processing
 | 
				
			||||||
    daterange:         A DateRange object, download only if the upload_date is in the range.
 | 
					    daterange:         A DateRange object, download only if the upload_date is in the range.
 | 
				
			||||||
@@ -188,26 +201,58 @@ class YoutubeDL(object):
 | 
				
			|||||||
                       postprocessor.
 | 
					                       postprocessor.
 | 
				
			||||||
    progress_hooks:    A list of functions that get called on download
 | 
					    progress_hooks:    A list of functions that get called on download
 | 
				
			||||||
                       progress, with a dictionary with the entries
 | 
					                       progress, with a dictionary with the entries
 | 
				
			||||||
                       * filename: The final filename
 | 
					                       * status: One of "downloading", "error", or "finished".
 | 
				
			||||||
                       * status: One of "downloading" and "finished"
 | 
					                                 Check this first and ignore unknown values.
 | 
				
			||||||
 | 
					 | 
				
			||||||
                       The dict may also have some of the following entries:
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                       If status is one of "downloading", or "finished", the
 | 
				
			||||||
 | 
					                       following properties may also be present:
 | 
				
			||||||
 | 
					                       * filename: The final filename (always present)
 | 
				
			||||||
 | 
					                       * tmpfilename: The filename we're currently writing to
 | 
				
			||||||
                       * downloaded_bytes: Bytes on disk
 | 
					                       * downloaded_bytes: Bytes on disk
 | 
				
			||||||
                       * total_bytes: Size of the whole file, None if unknown
 | 
					                       * total_bytes: Size of the whole file, None if unknown
 | 
				
			||||||
                       * tmpfilename: The filename we're currently writing to
 | 
					                       * total_bytes_estimate: Guess of the eventual file size,
 | 
				
			||||||
 | 
					                                               None if unavailable.
 | 
				
			||||||
 | 
					                       * elapsed: The number of seconds since download started.
 | 
				
			||||||
                       * eta: The estimated time in seconds, None if unknown
 | 
					                       * eta: The estimated time in seconds, None if unknown
 | 
				
			||||||
                       * speed: The download speed in bytes/second, None if
 | 
					                       * speed: The download speed in bytes/second, None if
 | 
				
			||||||
                                unknown
 | 
					                                unknown
 | 
				
			||||||
 | 
					                       * fragment_index: The counter of the currently
 | 
				
			||||||
 | 
					                                         downloaded video fragment.
 | 
				
			||||||
 | 
					                       * fragment_count: The number of fragments (= individual
 | 
				
			||||||
 | 
					                                         files that will be merged)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                       Progress hooks are guaranteed to be called at least once
 | 
					                       Progress hooks are guaranteed to be called at least once
 | 
				
			||||||
                       (with status "finished") if the download is successful.
 | 
					                       (with status "finished") if the download is successful.
 | 
				
			||||||
 | 
					    merge_output_format: Extension to use when merging formats.
 | 
				
			||||||
 | 
					    fixup:             Automatically correct known faults of the file.
 | 
				
			||||||
 | 
					                       One of:
 | 
				
			||||||
 | 
					                       - "never": do nothing
 | 
				
			||||||
 | 
					                       - "warn": only emit a warning
 | 
				
			||||||
 | 
					                       - "detect_or_warn": check whether we can do anything
 | 
				
			||||||
 | 
					                                           about it, warn otherwise (default)
 | 
				
			||||||
 | 
					    source_address:    (Experimental) Client-side IP address to bind to.
 | 
				
			||||||
 | 
					    call_home:         Boolean, true iff we are allowed to contact the
 | 
				
			||||||
 | 
					                       youtube-dl servers for debugging.
 | 
				
			||||||
 | 
					    sleep_interval:    Number of seconds to sleep before each download.
 | 
				
			||||||
 | 
					    listformats:       Print an overview of available video formats and exit.
 | 
				
			||||||
 | 
					    list_thumbnails:   Print a table of all thumbnails and exit.
 | 
				
			||||||
 | 
					    match_filter:      A function that gets called with the info_dict of
 | 
				
			||||||
 | 
					                       every video.
 | 
				
			||||||
 | 
					                       If it returns a message, the video is ignored.
 | 
				
			||||||
 | 
					                       If it returns None, the video is downloaded.
 | 
				
			||||||
 | 
					                       match_filter_func in utils.py is one example for this.
 | 
				
			||||||
 | 
					    no_color:          Do not emit color codes in output.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    The following options determine which downloader is picked:
 | 
				
			||||||
 | 
					    external_downloader: Executable of the external downloader to call.
 | 
				
			||||||
 | 
					                       None or unset for standard (built-in) downloader.
 | 
				
			||||||
 | 
					    hls_prefer_native: Use the native HLS downloader instead of ffmpeg/avconv.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    The following parameters are not used by YoutubeDL itself, they are used by
 | 
					    The following parameters are not used by YoutubeDL itself, they are used by
 | 
				
			||||||
    the FileDownloader:
 | 
					    the downloader (see youtube_dl/downloader/common.py):
 | 
				
			||||||
    nopart, updatetime, buffersize, ratelimit, min_filesize, max_filesize, test,
 | 
					    nopart, updatetime, buffersize, ratelimit, min_filesize, max_filesize, test,
 | 
				
			||||||
    noresizebuffer, retries, continuedl, noprogress, consoletitle
 | 
					    noresizebuffer, retries, continuedl, noprogress, consoletitle,
 | 
				
			||||||
 | 
					    xattr_set_filesize, external_downloader_args.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    The following options are used by the post processors:
 | 
					    The following options are used by the post processors:
 | 
				
			||||||
    prefer_ffmpeg:     If True, use ffmpeg instead of avconv if both are available,
 | 
					    prefer_ffmpeg:     If True, use ffmpeg instead of avconv if both are available,
 | 
				
			||||||
@@ -241,7 +286,7 @@ class YoutubeDL(object):
 | 
				
			|||||||
            try:
 | 
					            try:
 | 
				
			||||||
                import pty
 | 
					                import pty
 | 
				
			||||||
                master, slave = pty.openpty()
 | 
					                master, slave = pty.openpty()
 | 
				
			||||||
                width = get_term_width()
 | 
					                width = compat_get_terminal_size().columns
 | 
				
			||||||
                if width is None:
 | 
					                if width is None:
 | 
				
			||||||
                    width_args = []
 | 
					                    width_args = []
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
@@ -265,8 +310,8 @@ class YoutubeDL(object):
 | 
				
			|||||||
                    raise
 | 
					                    raise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (sys.version_info >= (3,) and sys.platform != 'win32' and
 | 
					        if (sys.version_info >= (3,) and sys.platform != 'win32' and
 | 
				
			||||||
                sys.getfilesystemencoding() in ['ascii', 'ANSI_X3.4-1968']
 | 
					                sys.getfilesystemencoding() in ['ascii', 'ANSI_X3.4-1968'] and
 | 
				
			||||||
                and not params.get('restrictfilenames', False)):
 | 
					                not params.get('restrictfilenames', False)):
 | 
				
			||||||
            # On Python 3, the Unicode filesystem API will throw errors (#1474)
 | 
					            # On Python 3, the Unicode filesystem API will throw errors (#1474)
 | 
				
			||||||
            self.report_warning(
 | 
					            self.report_warning(
 | 
				
			||||||
                'Assuming --restrict-filenames since file system encoding '
 | 
					                'Assuming --restrict-filenames since file system encoding '
 | 
				
			||||||
@@ -458,7 +503,7 @@ class YoutubeDL(object):
 | 
				
			|||||||
        else:
 | 
					        else:
 | 
				
			||||||
            if self.params.get('no_warnings'):
 | 
					            if self.params.get('no_warnings'):
 | 
				
			||||||
                return
 | 
					                return
 | 
				
			||||||
            if self._err_file.isatty() and os.name != 'nt':
 | 
					            if not self.params.get('no_color') and self._err_file.isatty() and os.name != 'nt':
 | 
				
			||||||
                _msg_header = '\033[0;33mWARNING:\033[0m'
 | 
					                _msg_header = '\033[0;33mWARNING:\033[0m'
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                _msg_header = 'WARNING:'
 | 
					                _msg_header = 'WARNING:'
 | 
				
			||||||
@@ -470,7 +515,7 @@ class YoutubeDL(object):
 | 
				
			|||||||
        Do the same as trouble, but prefixes the message with 'ERROR:', colored
 | 
					        Do the same as trouble, but prefixes the message with 'ERROR:', colored
 | 
				
			||||||
        in red if stderr is a tty file.
 | 
					        in red if stderr is a tty file.
 | 
				
			||||||
        '''
 | 
					        '''
 | 
				
			||||||
        if self._err_file.isatty() and os.name != 'nt':
 | 
					        if not self.params.get('no_color') and self._err_file.isatty() and os.name != 'nt':
 | 
				
			||||||
            _msg_header = '\033[0;31mERROR:\033[0m'
 | 
					            _msg_header = '\033[0;31mERROR:\033[0m'
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            _msg_header = 'ERROR:'
 | 
					            _msg_header = 'ERROR:'
 | 
				
			||||||
@@ -517,12 +562,17 @@ class YoutubeDL(object):
 | 
				
			|||||||
            outtmpl = self.params.get('outtmpl', DEFAULT_OUTTMPL)
 | 
					            outtmpl = self.params.get('outtmpl', DEFAULT_OUTTMPL)
 | 
				
			||||||
            tmpl = compat_expanduser(outtmpl)
 | 
					            tmpl = compat_expanduser(outtmpl)
 | 
				
			||||||
            filename = tmpl % template_dict
 | 
					            filename = tmpl % template_dict
 | 
				
			||||||
 | 
					            # Temporary fix for #4787
 | 
				
			||||||
 | 
					            # 'Treat' all problem characters by passing filename through preferredencoding
 | 
				
			||||||
 | 
					            # to workaround encoding issues with subprocess on python2 @ Windows
 | 
				
			||||||
 | 
					            if sys.version_info < (3, 0) and sys.platform == 'win32':
 | 
				
			||||||
 | 
					                filename = encodeFilename(filename, True).decode(preferredencoding())
 | 
				
			||||||
            return filename
 | 
					            return filename
 | 
				
			||||||
        except ValueError as err:
 | 
					        except ValueError as err:
 | 
				
			||||||
            self.report_error('Error in output template: ' + str(err) + ' (encoding: ' + repr(preferredencoding()) + ')')
 | 
					            self.report_error('Error in output template: ' + str(err) + ' (encoding: ' + repr(preferredencoding()) + ')')
 | 
				
			||||||
            return None
 | 
					            return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _match_entry(self, info_dict):
 | 
					    def _match_entry(self, info_dict, incomplete):
 | 
				
			||||||
        """ Returns None iff the file should be downloaded """
 | 
					        """ Returns None iff the file should be downloaded """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        video_title = info_dict.get('title', info_dict.get('id', 'video'))
 | 
					        video_title = info_dict.get('title', info_dict.get('id', 'video'))
 | 
				
			||||||
@@ -550,15 +600,18 @@ class YoutubeDL(object):
 | 
				
			|||||||
            max_views = self.params.get('max_views')
 | 
					            max_views = self.params.get('max_views')
 | 
				
			||||||
            if max_views is not None and view_count > max_views:
 | 
					            if max_views is not None and view_count > max_views:
 | 
				
			||||||
                return 'Skipping %s, because it has exceeded the maximum view count (%d/%d)' % (video_title, view_count, max_views)
 | 
					                return 'Skipping %s, because it has exceeded the maximum view count (%d/%d)' % (video_title, view_count, max_views)
 | 
				
			||||||
        age_limit = self.params.get('age_limit')
 | 
					        if age_restricted(info_dict.get('age_limit'), self.params.get('age_limit')):
 | 
				
			||||||
        if age_limit is not None:
 | 
					            return 'Skipping "%s" because it is age restricted' % video_title
 | 
				
			||||||
            actual_age_limit = info_dict.get('age_limit')
 | 
					 | 
				
			||||||
            if actual_age_limit is None:
 | 
					 | 
				
			||||||
                actual_age_limit = 0
 | 
					 | 
				
			||||||
            if age_limit < actual_age_limit:
 | 
					 | 
				
			||||||
                return 'Skipping "' + title + '" because it is age restricted'
 | 
					 | 
				
			||||||
        if self.in_download_archive(info_dict):
 | 
					        if self.in_download_archive(info_dict):
 | 
				
			||||||
            return '%s has already been recorded in archive' % video_title
 | 
					            return '%s has already been recorded in archive' % video_title
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not incomplete:
 | 
				
			||||||
 | 
					            match_filter = self.params.get('match_filter')
 | 
				
			||||||
 | 
					            if match_filter is not None:
 | 
				
			||||||
 | 
					                ret = match_filter(info_dict)
 | 
				
			||||||
 | 
					                if ret is not None:
 | 
				
			||||||
 | 
					                    return ret
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return None
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @staticmethod
 | 
					    @staticmethod
 | 
				
			||||||
@@ -685,24 +738,51 @@ class YoutubeDL(object):
 | 
				
			|||||||
            if playlistend == -1:
 | 
					            if playlistend == -1:
 | 
				
			||||||
                playlistend = None
 | 
					                playlistend = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            playlistitems_str = self.params.get('playlist_items', None)
 | 
				
			||||||
 | 
					            playlistitems = None
 | 
				
			||||||
 | 
					            if playlistitems_str is not None:
 | 
				
			||||||
 | 
					                def iter_playlistitems(format):
 | 
				
			||||||
 | 
					                    for string_segment in format.split(','):
 | 
				
			||||||
 | 
					                        if '-' in string_segment:
 | 
				
			||||||
 | 
					                            start, end = string_segment.split('-')
 | 
				
			||||||
 | 
					                            for item in range(int(start), int(end) + 1):
 | 
				
			||||||
 | 
					                                yield int(item)
 | 
				
			||||||
 | 
					                        else:
 | 
				
			||||||
 | 
					                            yield int(string_segment)
 | 
				
			||||||
 | 
					                playlistitems = iter_playlistitems(playlistitems_str)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            ie_entries = ie_result['entries']
 | 
					            ie_entries = ie_result['entries']
 | 
				
			||||||
            if isinstance(ie_entries, list):
 | 
					            if isinstance(ie_entries, list):
 | 
				
			||||||
                n_all_entries = len(ie_entries)
 | 
					                n_all_entries = len(ie_entries)
 | 
				
			||||||
                entries = ie_entries[playliststart:playlistend]
 | 
					                if playlistitems:
 | 
				
			||||||
 | 
					                    entries = [ie_entries[i - 1] for i in playlistitems]
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    entries = ie_entries[playliststart:playlistend]
 | 
				
			||||||
                n_entries = len(entries)
 | 
					                n_entries = len(entries)
 | 
				
			||||||
                self.to_screen(
 | 
					                self.to_screen(
 | 
				
			||||||
                    "[%s] playlist %s: Collected %d video ids (downloading %d of them)" %
 | 
					                    "[%s] playlist %s: Collected %d video ids (downloading %d of them)" %
 | 
				
			||||||
                    (ie_result['extractor'], playlist, n_all_entries, n_entries))
 | 
					                    (ie_result['extractor'], playlist, n_all_entries, n_entries))
 | 
				
			||||||
            elif isinstance(ie_entries, PagedList):
 | 
					            elif isinstance(ie_entries, PagedList):
 | 
				
			||||||
                entries = ie_entries.getslice(
 | 
					                if playlistitems:
 | 
				
			||||||
                    playliststart, playlistend)
 | 
					                    entries = []
 | 
				
			||||||
 | 
					                    for item in playlistitems:
 | 
				
			||||||
 | 
					                        entries.extend(ie_entries.getslice(
 | 
				
			||||||
 | 
					                            item - 1, item
 | 
				
			||||||
 | 
					                        ))
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    entries = ie_entries.getslice(
 | 
				
			||||||
 | 
					                        playliststart, playlistend)
 | 
				
			||||||
                n_entries = len(entries)
 | 
					                n_entries = len(entries)
 | 
				
			||||||
                self.to_screen(
 | 
					                self.to_screen(
 | 
				
			||||||
                    "[%s] playlist %s: Downloading %d videos" %
 | 
					                    "[%s] playlist %s: Downloading %d videos" %
 | 
				
			||||||
                    (ie_result['extractor'], playlist, n_entries))
 | 
					                    (ie_result['extractor'], playlist, n_entries))
 | 
				
			||||||
            else:  # iterable
 | 
					            else:  # iterable
 | 
				
			||||||
                entries = list(itertools.islice(
 | 
					                if playlistitems:
 | 
				
			||||||
                    ie_entries, playliststart, playlistend))
 | 
					                    entry_list = list(ie_entries)
 | 
				
			||||||
 | 
					                    entries = [entry_list[i - 1] for i in playlistitems]
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    entries = list(itertools.islice(
 | 
				
			||||||
 | 
					                        ie_entries, playliststart, playlistend))
 | 
				
			||||||
                n_entries = len(entries)
 | 
					                n_entries = len(entries)
 | 
				
			||||||
                self.to_screen(
 | 
					                self.to_screen(
 | 
				
			||||||
                    "[%s] playlist %s: Downloading %d videos" %
 | 
					                    "[%s] playlist %s: Downloading %d videos" %
 | 
				
			||||||
@@ -725,7 +805,7 @@ class YoutubeDL(object):
 | 
				
			|||||||
                    'extractor_key': ie_result['extractor_key'],
 | 
					                    'extractor_key': ie_result['extractor_key'],
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                reason = self._match_entry(entry)
 | 
					                reason = self._match_entry(entry, incomplete=True)
 | 
				
			||||||
                if reason is not None:
 | 
					                if reason is not None:
 | 
				
			||||||
                    self.to_screen('[download] ' + reason)
 | 
					                    self.to_screen('[download] ' + reason)
 | 
				
			||||||
                    continue
 | 
					                    continue
 | 
				
			||||||
@@ -760,7 +840,76 @@ class YoutubeDL(object):
 | 
				
			|||||||
        else:
 | 
					        else:
 | 
				
			||||||
            raise Exception('Invalid result type: %s' % result_type)
 | 
					            raise Exception('Invalid result type: %s' % result_type)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _apply_format_filter(self, format_spec, available_formats):
 | 
				
			||||||
 | 
					        " Returns a tuple of the remaining format_spec and filtered formats "
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        OPERATORS = {
 | 
				
			||||||
 | 
					            '<': operator.lt,
 | 
				
			||||||
 | 
					            '<=': operator.le,
 | 
				
			||||||
 | 
					            '>': operator.gt,
 | 
				
			||||||
 | 
					            '>=': operator.ge,
 | 
				
			||||||
 | 
					            '=': operator.eq,
 | 
				
			||||||
 | 
					            '!=': operator.ne,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        operator_rex = re.compile(r'''(?x)\s*\[
 | 
				
			||||||
 | 
					            (?P<key>width|height|tbr|abr|vbr|asr|filesize|fps)
 | 
				
			||||||
 | 
					            \s*(?P<op>%s)(?P<none_inclusive>\s*\?)?\s*
 | 
				
			||||||
 | 
					            (?P<value>[0-9.]+(?:[kKmMgGtTpPeEzZyY]i?[Bb]?)?)
 | 
				
			||||||
 | 
					            \]$
 | 
				
			||||||
 | 
					            ''' % '|'.join(map(re.escape, OPERATORS.keys())))
 | 
				
			||||||
 | 
					        m = operator_rex.search(format_spec)
 | 
				
			||||||
 | 
					        if m:
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                comparison_value = int(m.group('value'))
 | 
				
			||||||
 | 
					            except ValueError:
 | 
				
			||||||
 | 
					                comparison_value = parse_filesize(m.group('value'))
 | 
				
			||||||
 | 
					                if comparison_value is None:
 | 
				
			||||||
 | 
					                    comparison_value = parse_filesize(m.group('value') + 'B')
 | 
				
			||||||
 | 
					                if comparison_value is None:
 | 
				
			||||||
 | 
					                    raise ValueError(
 | 
				
			||||||
 | 
					                        'Invalid value %r in format specification %r' % (
 | 
				
			||||||
 | 
					                            m.group('value'), format_spec))
 | 
				
			||||||
 | 
					            op = OPERATORS[m.group('op')]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not m:
 | 
				
			||||||
 | 
					            STR_OPERATORS = {
 | 
				
			||||||
 | 
					                '=': operator.eq,
 | 
				
			||||||
 | 
					                '!=': operator.ne,
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            str_operator_rex = re.compile(r'''(?x)\s*\[
 | 
				
			||||||
 | 
					                \s*(?P<key>ext|acodec|vcodec|container|protocol)
 | 
				
			||||||
 | 
					                \s*(?P<op>%s)(?P<none_inclusive>\s*\?)?
 | 
				
			||||||
 | 
					                \s*(?P<value>[a-zA-Z0-9_-]+)
 | 
				
			||||||
 | 
					                \s*\]$
 | 
				
			||||||
 | 
					                ''' % '|'.join(map(re.escape, STR_OPERATORS.keys())))
 | 
				
			||||||
 | 
					            m = str_operator_rex.search(format_spec)
 | 
				
			||||||
 | 
					            if m:
 | 
				
			||||||
 | 
					                comparison_value = m.group('value')
 | 
				
			||||||
 | 
					                op = STR_OPERATORS[m.group('op')]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not m:
 | 
				
			||||||
 | 
					            raise ValueError('Invalid format specification %r' % format_spec)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def _filter(f):
 | 
				
			||||||
 | 
					            actual_value = f.get(m.group('key'))
 | 
				
			||||||
 | 
					            if actual_value is None:
 | 
				
			||||||
 | 
					                return m.group('none_inclusive')
 | 
				
			||||||
 | 
					            return op(actual_value, comparison_value)
 | 
				
			||||||
 | 
					        new_formats = [f for f in available_formats if _filter(f)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        new_format_spec = format_spec[:-len(m.group(0))]
 | 
				
			||||||
 | 
					        if not new_format_spec:
 | 
				
			||||||
 | 
					            new_format_spec = 'best'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return (new_format_spec, new_formats)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def select_format(self, format_spec, available_formats):
 | 
					    def select_format(self, format_spec, available_formats):
 | 
				
			||||||
 | 
					        while format_spec.endswith(']'):
 | 
				
			||||||
 | 
					            format_spec, available_formats = self._apply_format_filter(
 | 
				
			||||||
 | 
					                format_spec, available_formats)
 | 
				
			||||||
 | 
					        if not available_formats:
 | 
				
			||||||
 | 
					            return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if format_spec == 'best' or format_spec is None:
 | 
					        if format_spec == 'best' or format_spec is None:
 | 
				
			||||||
            return available_formats[-1]
 | 
					            return available_formats[-1]
 | 
				
			||||||
        elif format_spec == 'worst':
 | 
					        elif format_spec == 'worst':
 | 
				
			||||||
@@ -790,7 +939,7 @@ class YoutubeDL(object):
 | 
				
			|||||||
            if video_formats:
 | 
					            if video_formats:
 | 
				
			||||||
                return video_formats[0]
 | 
					                return video_formats[0]
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            extensions = ['mp4', 'flv', 'webm', '3gp', 'm4a']
 | 
					            extensions = ['mp4', 'flv', 'webm', '3gp', 'm4a', 'mp3', 'ogg', 'aac', 'wav']
 | 
				
			||||||
            if format_spec in extensions:
 | 
					            if format_spec in extensions:
 | 
				
			||||||
                filter_f = lambda f: f['ext'] == format_spec
 | 
					                filter_f = lambda f: f['ext'] == format_spec
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
@@ -800,6 +949,24 @@ class YoutubeDL(object):
 | 
				
			|||||||
                return matches[-1]
 | 
					                return matches[-1]
 | 
				
			||||||
        return None
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _calc_headers(self, info_dict):
 | 
				
			||||||
 | 
					        res = std_headers.copy()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        add_headers = info_dict.get('http_headers')
 | 
				
			||||||
 | 
					        if add_headers:
 | 
				
			||||||
 | 
					            res.update(add_headers)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cookies = self._calc_cookies(info_dict)
 | 
				
			||||||
 | 
					        if cookies:
 | 
				
			||||||
 | 
					            res['Cookie'] = cookies
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return res
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _calc_cookies(self, info_dict):
 | 
				
			||||||
 | 
					        pr = compat_urllib_request.Request(info_dict['url'])
 | 
				
			||||||
 | 
					        self.cookiejar.add_cookie_header(pr)
 | 
				
			||||||
 | 
					        return pr.get_header('Cookie')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def process_video_result(self, info_dict, download=True):
 | 
					    def process_video_result(self, info_dict, download=True):
 | 
				
			||||||
        assert info_dict.get('_type', 'video') == 'video'
 | 
					        assert info_dict.get('_type', 'video') == 'video'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -814,12 +981,19 @@ class YoutubeDL(object):
 | 
				
			|||||||
            info_dict['playlist_index'] = None
 | 
					            info_dict['playlist_index'] = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        thumbnails = info_dict.get('thumbnails')
 | 
					        thumbnails = info_dict.get('thumbnails')
 | 
				
			||||||
 | 
					        if thumbnails is None:
 | 
				
			||||||
 | 
					            thumbnail = info_dict.get('thumbnail')
 | 
				
			||||||
 | 
					            if thumbnail:
 | 
				
			||||||
 | 
					                info_dict['thumbnails'] = thumbnails = [{'url': thumbnail}]
 | 
				
			||||||
        if thumbnails:
 | 
					        if thumbnails:
 | 
				
			||||||
            thumbnails.sort(key=lambda t: (
 | 
					            thumbnails.sort(key=lambda t: (
 | 
				
			||||||
                t.get('width'), t.get('height'), t.get('url')))
 | 
					                t.get('preference'), t.get('width'), t.get('height'),
 | 
				
			||||||
            for t in thumbnails:
 | 
					                t.get('id'), t.get('url')))
 | 
				
			||||||
 | 
					            for i, t in enumerate(thumbnails):
 | 
				
			||||||
                if 'width' in t and 'height' in t:
 | 
					                if 'width' in t and 'height' in t:
 | 
				
			||||||
                    t['resolution'] = '%dx%d' % (t['width'], t['height'])
 | 
					                    t['resolution'] = '%dx%d' % (t['width'], t['height'])
 | 
				
			||||||
 | 
					                if t.get('id') is None:
 | 
				
			||||||
 | 
					                    t['id'] = '%d' % i
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if thumbnails and 'thumbnail' not in info_dict:
 | 
					        if thumbnails and 'thumbnail' not in info_dict:
 | 
				
			||||||
            info_dict['thumbnail'] = thumbnails[-1]['url']
 | 
					            info_dict['thumbnail'] = thumbnails[-1]['url']
 | 
				
			||||||
@@ -836,6 +1010,15 @@ class YoutubeDL(object):
 | 
				
			|||||||
                info_dict['timestamp'])
 | 
					                info_dict['timestamp'])
 | 
				
			||||||
            info_dict['upload_date'] = upload_date.strftime('%Y%m%d')
 | 
					            info_dict['upload_date'] = upload_date.strftime('%Y%m%d')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.params.get('listsubtitles', False):
 | 
				
			||||||
 | 
					            if 'automatic_captions' in info_dict:
 | 
				
			||||||
 | 
					                self.list_subtitles(info_dict['id'], info_dict.get('automatic_captions'), 'automatic captions')
 | 
				
			||||||
 | 
					            self.list_subtitles(info_dict['id'], info_dict.get('subtitles'), 'subtitles')
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        info_dict['requested_subtitles'] = self.process_subtitles(
 | 
				
			||||||
 | 
					            info_dict['id'], info_dict.get('subtitles'),
 | 
				
			||||||
 | 
					            info_dict.get('automatic_captions'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # This extractors handle format selection themselves
 | 
					        # This extractors handle format selection themselves
 | 
				
			||||||
        if info_dict['extractor'] in ['Youku']:
 | 
					        if info_dict['extractor'] in ['Youku']:
 | 
				
			||||||
            if download:
 | 
					            if download:
 | 
				
			||||||
@@ -868,6 +1051,11 @@ class YoutubeDL(object):
 | 
				
			|||||||
            # Automatically determine file extension if missing
 | 
					            # Automatically determine file extension if missing
 | 
				
			||||||
            if 'ext' not in format:
 | 
					            if 'ext' not in format:
 | 
				
			||||||
                format['ext'] = determine_ext(format['url']).lower()
 | 
					                format['ext'] = determine_ext(format['url']).lower()
 | 
				
			||||||
 | 
					            # Add HTTP headers, so that external programs can use them from the
 | 
				
			||||||
 | 
					            # json output
 | 
				
			||||||
 | 
					            full_format_info = info_dict.copy()
 | 
				
			||||||
 | 
					            full_format_info.update(format)
 | 
				
			||||||
 | 
					            format['http_headers'] = self._calc_headers(full_format_info)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        format_limit = self.params.get('format_limit', None)
 | 
					        format_limit = self.params.get('format_limit', None)
 | 
				
			||||||
        if format_limit:
 | 
					        if format_limit:
 | 
				
			||||||
@@ -883,9 +1071,12 @@ class YoutubeDL(object):
 | 
				
			|||||||
            # element in the 'formats' field in info_dict is info_dict itself,
 | 
					            # element in the 'formats' field in info_dict is info_dict itself,
 | 
				
			||||||
            # wich can't be exported to json
 | 
					            # wich can't be exported to json
 | 
				
			||||||
            info_dict['formats'] = formats
 | 
					            info_dict['formats'] = formats
 | 
				
			||||||
        if self.params.get('listformats', None):
 | 
					        if self.params.get('listformats'):
 | 
				
			||||||
            self.list_formats(info_dict)
 | 
					            self.list_formats(info_dict)
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
 | 
					        if self.params.get('list_thumbnails'):
 | 
				
			||||||
 | 
					            self.list_thumbnails(info_dict)
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        req_format = self.params.get('format')
 | 
					        req_format = self.params.get('format')
 | 
				
			||||||
        if req_format is None:
 | 
					        if req_format is None:
 | 
				
			||||||
@@ -913,10 +1104,26 @@ class YoutubeDL(object):
 | 
				
			|||||||
                                                  'contain the video, try using '
 | 
					                                                  'contain the video, try using '
 | 
				
			||||||
                                                  '"-f %s+%s"' % (format_2, format_1))
 | 
					                                                  '"-f %s+%s"' % (format_2, format_1))
 | 
				
			||||||
                                return
 | 
					                                return
 | 
				
			||||||
 | 
					                            output_ext = (
 | 
				
			||||||
 | 
					                                formats_info[0]['ext']
 | 
				
			||||||
 | 
					                                if self.params.get('merge_output_format') is None
 | 
				
			||||||
 | 
					                                else self.params['merge_output_format'])
 | 
				
			||||||
                            selected_format = {
 | 
					                            selected_format = {
 | 
				
			||||||
                                'requested_formats': formats_info,
 | 
					                                'requested_formats': formats_info,
 | 
				
			||||||
                                'format': rf,
 | 
					                                'format': '%s+%s' % (formats_info[0].get('format'),
 | 
				
			||||||
                                'ext': formats_info[0]['ext'],
 | 
					                                                     formats_info[1].get('format')),
 | 
				
			||||||
 | 
					                                'format_id': '%s+%s' % (formats_info[0].get('format_id'),
 | 
				
			||||||
 | 
					                                                        formats_info[1].get('format_id')),
 | 
				
			||||||
 | 
					                                'width': formats_info[0].get('width'),
 | 
				
			||||||
 | 
					                                'height': formats_info[0].get('height'),
 | 
				
			||||||
 | 
					                                'resolution': formats_info[0].get('resolution'),
 | 
				
			||||||
 | 
					                                'fps': formats_info[0].get('fps'),
 | 
				
			||||||
 | 
					                                'vcodec': formats_info[0].get('vcodec'),
 | 
				
			||||||
 | 
					                                'vbr': formats_info[0].get('vbr'),
 | 
				
			||||||
 | 
					                                'stretched_ratio': formats_info[0].get('stretched_ratio'),
 | 
				
			||||||
 | 
					                                'acodec': formats_info[1].get('acodec'),
 | 
				
			||||||
 | 
					                                'abr': formats_info[1].get('abr'),
 | 
				
			||||||
 | 
					                                'ext': output_ext,
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                        else:
 | 
					                        else:
 | 
				
			||||||
                            selected_format = None
 | 
					                            selected_format = None
 | 
				
			||||||
@@ -940,6 +1147,55 @@ class YoutubeDL(object):
 | 
				
			|||||||
        info_dict.update(formats_to_download[-1])
 | 
					        info_dict.update(formats_to_download[-1])
 | 
				
			||||||
        return info_dict
 | 
					        return info_dict
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def process_subtitles(self, video_id, normal_subtitles, automatic_captions):
 | 
				
			||||||
 | 
					        """Select the requested subtitles and their format"""
 | 
				
			||||||
 | 
					        available_subs = {}
 | 
				
			||||||
 | 
					        if normal_subtitles and self.params.get('writesubtitles'):
 | 
				
			||||||
 | 
					            available_subs.update(normal_subtitles)
 | 
				
			||||||
 | 
					        if automatic_captions and self.params.get('writeautomaticsub'):
 | 
				
			||||||
 | 
					            for lang, cap_info in automatic_captions.items():
 | 
				
			||||||
 | 
					                if lang not in available_subs:
 | 
				
			||||||
 | 
					                    available_subs[lang] = cap_info
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (not self.params.get('writesubtitles') and not
 | 
				
			||||||
 | 
					                self.params.get('writeautomaticsub') or not
 | 
				
			||||||
 | 
					                available_subs):
 | 
				
			||||||
 | 
					            return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.params.get('allsubtitles', False):
 | 
				
			||||||
 | 
					            requested_langs = available_subs.keys()
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            if self.params.get('subtitleslangs', False):
 | 
				
			||||||
 | 
					                requested_langs = self.params.get('subtitleslangs')
 | 
				
			||||||
 | 
					            elif 'en' in available_subs:
 | 
				
			||||||
 | 
					                requested_langs = ['en']
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                requested_langs = [list(available_subs.keys())[0]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        formats_query = self.params.get('subtitlesformat', 'best')
 | 
				
			||||||
 | 
					        formats_preference = formats_query.split('/') if formats_query else []
 | 
				
			||||||
 | 
					        subs = {}
 | 
				
			||||||
 | 
					        for lang in requested_langs:
 | 
				
			||||||
 | 
					            formats = available_subs.get(lang)
 | 
				
			||||||
 | 
					            if formats is None:
 | 
				
			||||||
 | 
					                self.report_warning('%s subtitles not available for %s' % (lang, video_id))
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            for ext in formats_preference:
 | 
				
			||||||
 | 
					                if ext == 'best':
 | 
				
			||||||
 | 
					                    f = formats[-1]
 | 
				
			||||||
 | 
					                    break
 | 
				
			||||||
 | 
					                matches = list(filter(lambda f: f['ext'] == ext, formats))
 | 
				
			||||||
 | 
					                if matches:
 | 
				
			||||||
 | 
					                    f = matches[-1]
 | 
				
			||||||
 | 
					                    break
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                f = formats[-1]
 | 
				
			||||||
 | 
					                self.report_warning(
 | 
				
			||||||
 | 
					                    'No subtitle format found matching "%s" for language %s, '
 | 
				
			||||||
 | 
					                    'using %s' % (formats_query, lang, f['ext']))
 | 
				
			||||||
 | 
					            subs[lang] = f
 | 
				
			||||||
 | 
					        return subs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def process_info(self, info_dict):
 | 
					    def process_info(self, info_dict):
 | 
				
			||||||
        """Process a single resolved IE result."""
 | 
					        """Process a single resolved IE result."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -960,14 +1216,14 @@ class YoutubeDL(object):
 | 
				
			|||||||
        if 'format' not in info_dict:
 | 
					        if 'format' not in info_dict:
 | 
				
			||||||
            info_dict['format'] = info_dict['ext']
 | 
					            info_dict['format'] = info_dict['ext']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        reason = self._match_entry(info_dict)
 | 
					        reason = self._match_entry(info_dict, incomplete=False)
 | 
				
			||||||
        if reason is not None:
 | 
					        if reason is not None:
 | 
				
			||||||
            self.to_screen('[download] ' + reason)
 | 
					            self.to_screen('[download] ' + reason)
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self._num_downloads += 1
 | 
					        self._num_downloads += 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        filename = self.prepare_filename(info_dict)
 | 
					        info_dict['_filename'] = filename = self.prepare_filename(info_dict)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Forced printings
 | 
					        # Forced printings
 | 
				
			||||||
        if self.params.get('forcetitle', False):
 | 
					        if self.params.get('forcetitle', False):
 | 
				
			||||||
@@ -992,10 +1248,7 @@ class YoutubeDL(object):
 | 
				
			|||||||
        if self.params.get('forceformat', False):
 | 
					        if self.params.get('forceformat', False):
 | 
				
			||||||
            self.to_stdout(info_dict['format'])
 | 
					            self.to_stdout(info_dict['format'])
 | 
				
			||||||
        if self.params.get('forcejson', False):
 | 
					        if self.params.get('forcejson', False):
 | 
				
			||||||
            info_dict['_filename'] = filename
 | 
					 | 
				
			||||||
            self.to_stdout(json.dumps(info_dict))
 | 
					            self.to_stdout(json.dumps(info_dict))
 | 
				
			||||||
        if self.params.get('dump_single_json', False):
 | 
					 | 
				
			||||||
            info_dict['_filename'] = filename
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Do nothing else if in simulate mode
 | 
					        # Do nothing else if in simulate mode
 | 
				
			||||||
        if self.params.get('simulate', False):
 | 
					        if self.params.get('simulate', False):
 | 
				
			||||||
@@ -1016,13 +1269,13 @@ class YoutubeDL(object):
 | 
				
			|||||||
            descfn = filename + '.description'
 | 
					            descfn = filename + '.description'
 | 
				
			||||||
            if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(descfn)):
 | 
					            if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(descfn)):
 | 
				
			||||||
                self.to_screen('[info] Video description is already present')
 | 
					                self.to_screen('[info] Video description is already present')
 | 
				
			||||||
 | 
					            elif info_dict.get('description') is None:
 | 
				
			||||||
 | 
					                self.report_warning('There\'s no description to write.')
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                try:
 | 
					                try:
 | 
				
			||||||
                    self.to_screen('[info] Writing video description to: ' + descfn)
 | 
					                    self.to_screen('[info] Writing video description to: ' + descfn)
 | 
				
			||||||
                    with io.open(encodeFilename(descfn), 'w', encoding='utf-8') as descfile:
 | 
					                    with io.open(encodeFilename(descfn), 'w', encoding='utf-8') as descfile:
 | 
				
			||||||
                        descfile.write(info_dict['description'])
 | 
					                        descfile.write(info_dict['description'])
 | 
				
			||||||
                except (KeyError, TypeError):
 | 
					 | 
				
			||||||
                    self.report_warning('There\'s no description to write.')
 | 
					 | 
				
			||||||
                except (OSError, IOError):
 | 
					                except (OSError, IOError):
 | 
				
			||||||
                    self.report_error('Cannot write description file ' + descfn)
 | 
					                    self.report_error('Cannot write description file ' + descfn)
 | 
				
			||||||
                    return
 | 
					                    return
 | 
				
			||||||
@@ -1045,15 +1298,23 @@ class YoutubeDL(object):
 | 
				
			|||||||
        subtitles_are_requested = any([self.params.get('writesubtitles', False),
 | 
					        subtitles_are_requested = any([self.params.get('writesubtitles', False),
 | 
				
			||||||
                                       self.params.get('writeautomaticsub')])
 | 
					                                       self.params.get('writeautomaticsub')])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if subtitles_are_requested and 'subtitles' in info_dict and info_dict['subtitles']:
 | 
					        if subtitles_are_requested and info_dict.get('requested_subtitles'):
 | 
				
			||||||
            # subtitles download errors are already managed as troubles in relevant IE
 | 
					            # subtitles download errors are already managed as troubles in relevant IE
 | 
				
			||||||
            # that way it will silently go on when used with unsupporting IE
 | 
					            # that way it will silently go on when used with unsupporting IE
 | 
				
			||||||
            subtitles = info_dict['subtitles']
 | 
					            subtitles = info_dict['requested_subtitles']
 | 
				
			||||||
            sub_format = self.params.get('subtitlesformat', 'srt')
 | 
					            ie = self.get_info_extractor(info_dict['extractor_key'])
 | 
				
			||||||
            for sub_lang in subtitles.keys():
 | 
					            for sub_lang, sub_info in subtitles.items():
 | 
				
			||||||
                sub = subtitles[sub_lang]
 | 
					                sub_format = sub_info['ext']
 | 
				
			||||||
                if sub is None:
 | 
					                if sub_info.get('data') is not None:
 | 
				
			||||||
                    continue
 | 
					                    sub_data = sub_info['data']
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    try:
 | 
				
			||||||
 | 
					                        sub_data = ie._download_webpage(
 | 
				
			||||||
 | 
					                            sub_info['url'], info_dict['id'], note=False)
 | 
				
			||||||
 | 
					                    except ExtractorError as err:
 | 
				
			||||||
 | 
					                        self.report_warning('Unable to download subtitle for "%s": %s' %
 | 
				
			||||||
 | 
					                                            (sub_lang, compat_str(err.cause)))
 | 
				
			||||||
 | 
					                        continue
 | 
				
			||||||
                try:
 | 
					                try:
 | 
				
			||||||
                    sub_filename = subtitles_filename(filename, sub_lang, sub_format)
 | 
					                    sub_filename = subtitles_filename(filename, sub_lang, sub_format)
 | 
				
			||||||
                    if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(sub_filename)):
 | 
					                    if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(sub_filename)):
 | 
				
			||||||
@@ -1061,7 +1322,7 @@ class YoutubeDL(object):
 | 
				
			|||||||
                    else:
 | 
					                    else:
 | 
				
			||||||
                        self.to_screen('[info] Writing video subtitles to: ' + sub_filename)
 | 
					                        self.to_screen('[info] Writing video subtitles to: ' + sub_filename)
 | 
				
			||||||
                        with io.open(encodeFilename(sub_filename), 'w', encoding='utf-8') as subfile:
 | 
					                        with io.open(encodeFilename(sub_filename), 'w', encoding='utf-8') as subfile:
 | 
				
			||||||
                            subfile.write(sub)
 | 
					                            subfile.write(sub_data)
 | 
				
			||||||
                except (OSError, IOError):
 | 
					                except (OSError, IOError):
 | 
				
			||||||
                    self.report_error('Cannot write subtitles file ' + sub_filename)
 | 
					                    self.report_error('Cannot write subtitles file ' + sub_filename)
 | 
				
			||||||
                    return
 | 
					                    return
 | 
				
			||||||
@@ -1078,86 +1339,103 @@ class YoutubeDL(object):
 | 
				
			|||||||
                    self.report_error('Cannot write metadata to JSON file ' + infofn)
 | 
					                    self.report_error('Cannot write metadata to JSON file ' + infofn)
 | 
				
			||||||
                    return
 | 
					                    return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self.params.get('writethumbnail', False):
 | 
					        self._write_thumbnails(info_dict, filename)
 | 
				
			||||||
            if info_dict.get('thumbnail') is not None:
 | 
					 | 
				
			||||||
                thumb_format = determine_ext(info_dict['thumbnail'], 'jpg')
 | 
					 | 
				
			||||||
                thumb_filename = os.path.splitext(filename)[0] + '.' + thumb_format
 | 
					 | 
				
			||||||
                if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(thumb_filename)):
 | 
					 | 
				
			||||||
                    self.to_screen('[%s] %s: Thumbnail is already present' %
 | 
					 | 
				
			||||||
                                   (info_dict['extractor'], info_dict['id']))
 | 
					 | 
				
			||||||
                else:
 | 
					 | 
				
			||||||
                    self.to_screen('[%s] %s: Downloading thumbnail ...' %
 | 
					 | 
				
			||||||
                                   (info_dict['extractor'], info_dict['id']))
 | 
					 | 
				
			||||||
                    try:
 | 
					 | 
				
			||||||
                        uf = self.urlopen(info_dict['thumbnail'])
 | 
					 | 
				
			||||||
                        with open(thumb_filename, 'wb') as thumbf:
 | 
					 | 
				
			||||||
                            shutil.copyfileobj(uf, thumbf)
 | 
					 | 
				
			||||||
                        self.to_screen('[%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('Unable to download thumbnail "%s": %s' %
 | 
					 | 
				
			||||||
                                            (info_dict['thumbnail'], compat_str(err)))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if not self.params.get('skip_download', False):
 | 
					        if not self.params.get('skip_download', False):
 | 
				
			||||||
            if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(filename)):
 | 
					            try:
 | 
				
			||||||
                success = True
 | 
					                def dl(name, info):
 | 
				
			||||||
            else:
 | 
					                    fd = get_suitable_downloader(info, self.params)(self, self.params)
 | 
				
			||||||
                try:
 | 
					                    for ph in self._progress_hooks:
 | 
				
			||||||
                    def dl(name, info):
 | 
					                        fd.add_progress_hook(ph)
 | 
				
			||||||
                        fd = get_suitable_downloader(info)(self, self.params)
 | 
					                    if self.params.get('verbose'):
 | 
				
			||||||
                        for ph in self._progress_hooks:
 | 
					                        self.to_stdout('[debug] Invoking downloader on %r' % info.get('url'))
 | 
				
			||||||
                            fd.add_progress_hook(ph)
 | 
					                    return fd.download(name, info)
 | 
				
			||||||
                        if self.params.get('verbose'):
 | 
					
 | 
				
			||||||
                            self.to_stdout('[debug] Invoking downloader on %r' % info.get('url'))
 | 
					                if info_dict.get('requested_formats') is not None:
 | 
				
			||||||
                        return fd.download(name, info)
 | 
					                    downloaded = []
 | 
				
			||||||
                    if info_dict.get('requested_formats') is not None:
 | 
					                    success = True
 | 
				
			||||||
                        downloaded = []
 | 
					                    merger = FFmpegMergerPP(self, not self.params.get('keepvideo'))
 | 
				
			||||||
                        success = True
 | 
					                    if not merger.available:
 | 
				
			||||||
                        merger = FFmpegMergerPP(self, not self.params.get('keepvideo'))
 | 
					                        postprocessors = []
 | 
				
			||||||
                        if not merger._executable:
 | 
					                        self.report_warning('You have requested multiple '
 | 
				
			||||||
                            postprocessors = []
 | 
					                                            'formats but ffmpeg or avconv are not installed.'
 | 
				
			||||||
                            self.report_warning('You have requested multiple '
 | 
					                                            ' The formats won\'t be merged')
 | 
				
			||||||
                                                'formats but ffmpeg or avconv are not installed.'
 | 
					 | 
				
			||||||
                                                ' The formats won\'t be merged')
 | 
					 | 
				
			||||||
                        else:
 | 
					 | 
				
			||||||
                            postprocessors = [merger]
 | 
					 | 
				
			||||||
                        for f in info_dict['requested_formats']:
 | 
					 | 
				
			||||||
                            new_info = dict(info_dict)
 | 
					 | 
				
			||||||
                            new_info.update(f)
 | 
					 | 
				
			||||||
                            fname = self.prepare_filename(new_info)
 | 
					 | 
				
			||||||
                            fname = prepend_extension(fname, 'f%s' % f['format_id'])
 | 
					 | 
				
			||||||
                            downloaded.append(fname)
 | 
					 | 
				
			||||||
                            partial_success = dl(fname, new_info)
 | 
					 | 
				
			||||||
                            success = success and partial_success
 | 
					 | 
				
			||||||
                        info_dict['__postprocessors'] = postprocessors
 | 
					 | 
				
			||||||
                        info_dict['__files_to_merge'] = downloaded
 | 
					 | 
				
			||||||
                    else:
 | 
					                    else:
 | 
				
			||||||
                        # Just a single file
 | 
					                        postprocessors = [merger]
 | 
				
			||||||
                        success = dl(filename, info_dict)
 | 
					                    for f in info_dict['requested_formats']:
 | 
				
			||||||
                except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
 | 
					                        new_info = dict(info_dict)
 | 
				
			||||||
                    self.report_error('unable to download video data: %s' % str(err))
 | 
					                        new_info.update(f)
 | 
				
			||||||
                    return
 | 
					                        fname = self.prepare_filename(new_info)
 | 
				
			||||||
                except (OSError, IOError) as err:
 | 
					                        fname = prepend_extension(fname, 'f%s' % f['format_id'])
 | 
				
			||||||
                    raise UnavailableVideoError(err)
 | 
					                        downloaded.append(fname)
 | 
				
			||||||
                except (ContentTooShortError, ) as err:
 | 
					                        partial_success = dl(fname, new_info)
 | 
				
			||||||
                    self.report_error('content too short (expected %s bytes and served %s)' % (err.expected, err.downloaded))
 | 
					                        success = success and partial_success
 | 
				
			||||||
                    return
 | 
					                    info_dict['__postprocessors'] = postprocessors
 | 
				
			||||||
 | 
					                    info_dict['__files_to_merge'] = downloaded
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    # Just a single file
 | 
				
			||||||
 | 
					                    success = dl(filename, info_dict)
 | 
				
			||||||
 | 
					            except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
 | 
				
			||||||
 | 
					                self.report_error('unable to download video data: %s' % str(err))
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					            except (OSError, IOError) as err:
 | 
				
			||||||
 | 
					                raise UnavailableVideoError(err)
 | 
				
			||||||
 | 
					            except (ContentTooShortError, ) as err:
 | 
				
			||||||
 | 
					                self.report_error('content too short (expected %s bytes and served %s)' % (err.expected, err.downloaded))
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if success:
 | 
					            if success:
 | 
				
			||||||
 | 
					                # Fixup content
 | 
				
			||||||
 | 
					                fixup_policy = self.params.get('fixup')
 | 
				
			||||||
 | 
					                if fixup_policy is None:
 | 
				
			||||||
 | 
					                    fixup_policy = 'detect_or_warn'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                stretched_ratio = info_dict.get('stretched_ratio')
 | 
				
			||||||
 | 
					                if stretched_ratio is not None and stretched_ratio != 1:
 | 
				
			||||||
 | 
					                    if fixup_policy == 'warn':
 | 
				
			||||||
 | 
					                        self.report_warning('%s: Non-uniform pixel ratio (%s)' % (
 | 
				
			||||||
 | 
					                            info_dict['id'], stretched_ratio))
 | 
				
			||||||
 | 
					                    elif fixup_policy == 'detect_or_warn':
 | 
				
			||||||
 | 
					                        stretched_pp = FFmpegFixupStretchedPP(self)
 | 
				
			||||||
 | 
					                        if stretched_pp.available:
 | 
				
			||||||
 | 
					                            info_dict.setdefault('__postprocessors', [])
 | 
				
			||||||
 | 
					                            info_dict['__postprocessors'].append(stretched_pp)
 | 
				
			||||||
 | 
					                        else:
 | 
				
			||||||
 | 
					                            self.report_warning(
 | 
				
			||||||
 | 
					                                '%s: Non-uniform pixel ratio (%s). Install ffmpeg or avconv to fix this automatically.' % (
 | 
				
			||||||
 | 
					                                    info_dict['id'], stretched_ratio))
 | 
				
			||||||
 | 
					                    else:
 | 
				
			||||||
 | 
					                        assert fixup_policy in ('ignore', 'never')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if info_dict.get('requested_formats') is None and info_dict.get('container') == 'm4a_dash':
 | 
				
			||||||
 | 
					                    if fixup_policy == 'warn':
 | 
				
			||||||
 | 
					                        self.report_warning('%s: writing DASH m4a. Only some players support this container.' % (
 | 
				
			||||||
 | 
					                            info_dict['id']))
 | 
				
			||||||
 | 
					                    elif fixup_policy == 'detect_or_warn':
 | 
				
			||||||
 | 
					                        fixup_pp = FFmpegFixupM4aPP(self)
 | 
				
			||||||
 | 
					                        if fixup_pp.available:
 | 
				
			||||||
 | 
					                            info_dict.setdefault('__postprocessors', [])
 | 
				
			||||||
 | 
					                            info_dict['__postprocessors'].append(fixup_pp)
 | 
				
			||||||
 | 
					                        else:
 | 
				
			||||||
 | 
					                            self.report_warning(
 | 
				
			||||||
 | 
					                                '%s: writing DASH m4a. Only some players support this container. Install ffmpeg or avconv to fix this automatically.' % (
 | 
				
			||||||
 | 
					                                    info_dict['id']))
 | 
				
			||||||
 | 
					                    else:
 | 
				
			||||||
 | 
					                        assert fixup_policy in ('ignore', 'never')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                try:
 | 
					                try:
 | 
				
			||||||
                    self.post_process(filename, info_dict)
 | 
					                    self.post_process(filename, info_dict)
 | 
				
			||||||
                except (PostProcessingError) as err:
 | 
					                except (PostProcessingError) as err:
 | 
				
			||||||
                    self.report_error('postprocessing: %s' % str(err))
 | 
					                    self.report_error('postprocessing: %s' % str(err))
 | 
				
			||||||
                    return
 | 
					                    return
 | 
				
			||||||
 | 
					                self.record_download_archive(info_dict)
 | 
				
			||||||
        self.record_download_archive(info_dict)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def download(self, url_list):
 | 
					    def download(self, url_list):
 | 
				
			||||||
        """Download a given list of URLs."""
 | 
					        """Download a given list of URLs."""
 | 
				
			||||||
        outtmpl = self.params.get('outtmpl', DEFAULT_OUTTMPL)
 | 
					        outtmpl = self.params.get('outtmpl', DEFAULT_OUTTMPL)
 | 
				
			||||||
        if (len(url_list) > 1 and
 | 
					        if (len(url_list) > 1 and
 | 
				
			||||||
                '%' not in outtmpl
 | 
					                '%' not in outtmpl and
 | 
				
			||||||
                and self.params.get('max_downloads') != 1):
 | 
					                self.params.get('max_downloads') != 1):
 | 
				
			||||||
            raise SameFileError(outtmpl)
 | 
					            raise SameFileError(outtmpl)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for url in url_list:
 | 
					        for url in url_list:
 | 
				
			||||||
@@ -1176,8 +1454,11 @@ class YoutubeDL(object):
 | 
				
			|||||||
        return self._download_retcode
 | 
					        return self._download_retcode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def download_with_info_file(self, info_filename):
 | 
					    def download_with_info_file(self, info_filename):
 | 
				
			||||||
        with io.open(info_filename, 'r', encoding='utf-8') as f:
 | 
					        with contextlib.closing(fileinput.FileInput(
 | 
				
			||||||
            info = json.load(f)
 | 
					                [info_filename], mode='r',
 | 
				
			||||||
 | 
					                openhook=fileinput.hook_encoded('utf-8'))) as f:
 | 
				
			||||||
 | 
					            # FileInput doesn't have a read method, we can't call json.load
 | 
				
			||||||
 | 
					            info = json.loads('\n'.join(f))
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            self.process_ie_result(info, download=True)
 | 
					            self.process_ie_result(info, download=True)
 | 
				
			||||||
        except DownloadError:
 | 
					        except DownloadError:
 | 
				
			||||||
@@ -1193,14 +1474,15 @@ class YoutubeDL(object):
 | 
				
			|||||||
        """Run all the postprocessors on the given file."""
 | 
					        """Run all the postprocessors on the given file."""
 | 
				
			||||||
        info = dict(ie_info)
 | 
					        info = dict(ie_info)
 | 
				
			||||||
        info['filepath'] = filename
 | 
					        info['filepath'] = filename
 | 
				
			||||||
        keep_video = None
 | 
					 | 
				
			||||||
        pps_chain = []
 | 
					        pps_chain = []
 | 
				
			||||||
        if ie_info.get('__postprocessors') is not None:
 | 
					        if ie_info.get('__postprocessors') is not None:
 | 
				
			||||||
            pps_chain.extend(ie_info['__postprocessors'])
 | 
					            pps_chain.extend(ie_info['__postprocessors'])
 | 
				
			||||||
        pps_chain.extend(self._pps)
 | 
					        pps_chain.extend(self._pps)
 | 
				
			||||||
        for pp in pps_chain:
 | 
					        for pp in pps_chain:
 | 
				
			||||||
 | 
					            keep_video = None
 | 
				
			||||||
 | 
					            old_filename = info['filepath']
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                keep_video_wish, new_info = pp.run(info)
 | 
					                keep_video_wish, info = pp.run(info)
 | 
				
			||||||
                if keep_video_wish is not None:
 | 
					                if keep_video_wish is not None:
 | 
				
			||||||
                    if keep_video_wish:
 | 
					                    if keep_video_wish:
 | 
				
			||||||
                        keep_video = keep_video_wish
 | 
					                        keep_video = keep_video_wish
 | 
				
			||||||
@@ -1209,12 +1491,12 @@ class YoutubeDL(object):
 | 
				
			|||||||
                        keep_video = keep_video_wish
 | 
					                        keep_video = keep_video_wish
 | 
				
			||||||
            except PostProcessingError as e:
 | 
					            except PostProcessingError as e:
 | 
				
			||||||
                self.report_error(e.msg)
 | 
					                self.report_error(e.msg)
 | 
				
			||||||
        if keep_video is False and not self.params.get('keepvideo', False):
 | 
					            if keep_video is False and not self.params.get('keepvideo', False):
 | 
				
			||||||
            try:
 | 
					                try:
 | 
				
			||||||
                self.to_screen('Deleting original file %s (pass -k to keep)' % filename)
 | 
					                    self.to_screen('Deleting original file %s (pass -k to keep)' % old_filename)
 | 
				
			||||||
                os.remove(encodeFilename(filename))
 | 
					                    os.remove(encodeFilename(old_filename))
 | 
				
			||||||
            except (IOError, OSError):
 | 
					                except (IOError, OSError):
 | 
				
			||||||
                self.report_warning('Unable to remove downloaded video file')
 | 
					                    self.report_warning('Unable to remove downloaded video file')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _make_archive_id(self, info_dict):
 | 
					    def _make_archive_id(self, info_dict):
 | 
				
			||||||
        # Future-proof against any change in case
 | 
					        # Future-proof against any change in case
 | 
				
			||||||
@@ -1323,27 +1605,46 @@ class YoutubeDL(object):
 | 
				
			|||||||
        return res
 | 
					        return res
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def list_formats(self, info_dict):
 | 
					    def list_formats(self, info_dict):
 | 
				
			||||||
        def line(format, idlen=20):
 | 
					 | 
				
			||||||
            return (('%-' + compat_str(idlen + 1) + 's%-10s%-12s%s') % (
 | 
					 | 
				
			||||||
                format['format_id'],
 | 
					 | 
				
			||||||
                format['ext'],
 | 
					 | 
				
			||||||
                self.format_resolution(format),
 | 
					 | 
				
			||||||
                self._format_note(format),
 | 
					 | 
				
			||||||
            ))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        formats = info_dict.get('formats', [info_dict])
 | 
					        formats = info_dict.get('formats', [info_dict])
 | 
				
			||||||
        idlen = max(len('format code'),
 | 
					        table = [
 | 
				
			||||||
                    max(len(f['format_id']) for f in formats))
 | 
					            [f['format_id'], f['ext'], self.format_resolution(f), self._format_note(f)]
 | 
				
			||||||
        formats_s = [line(f, idlen) for f in formats]
 | 
					            for f in formats
 | 
				
			||||||
 | 
					            if f.get('preference') is None or f['preference'] >= -1000]
 | 
				
			||||||
        if len(formats) > 1:
 | 
					        if len(formats) > 1:
 | 
				
			||||||
            formats_s[0] += (' ' if self._format_note(formats[0]) else '') + '(worst)'
 | 
					            table[-1][-1] += (' ' if table[-1][-1] else '') + '(best)'
 | 
				
			||||||
            formats_s[-1] += (' ' if self._format_note(formats[-1]) else '') + '(best)'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        header_line = line({
 | 
					        header_line = ['format code', 'extension', 'resolution', 'note']
 | 
				
			||||||
            'format_id': 'format code', 'ext': 'extension',
 | 
					        self.to_screen(
 | 
				
			||||||
            'resolution': 'resolution', 'format_note': 'note'}, idlen=idlen)
 | 
					            '[info] Available formats for %s:\n%s' %
 | 
				
			||||||
        self.to_screen('[info] Available formats for %s:\n%s\n%s' %
 | 
					            (info_dict['id'], render_table(header_line, table)))
 | 
				
			||||||
                       (info_dict['id'], header_line, '\n'.join(formats_s)))
 | 
					
 | 
				
			||||||
 | 
					    def list_thumbnails(self, info_dict):
 | 
				
			||||||
 | 
					        thumbnails = info_dict.get('thumbnails')
 | 
				
			||||||
 | 
					        if not thumbnails:
 | 
				
			||||||
 | 
					            tn_url = info_dict.get('thumbnail')
 | 
				
			||||||
 | 
					            if tn_url:
 | 
				
			||||||
 | 
					                thumbnails = [{'id': '0', 'url': tn_url}]
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                self.to_screen(
 | 
				
			||||||
 | 
					                    '[info] No thumbnails present for %s' % info_dict['id'])
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.to_screen(
 | 
				
			||||||
 | 
					            '[info] Thumbnails for %s:' % info_dict['id'])
 | 
				
			||||||
 | 
					        self.to_screen(render_table(
 | 
				
			||||||
 | 
					            ['ID', 'width', 'height', 'URL'],
 | 
				
			||||||
 | 
					            [[t['id'], t.get('width', 'unknown'), t.get('height', 'unknown'), t['url']] for t in thumbnails]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def list_subtitles(self, video_id, subtitles, name='subtitles'):
 | 
				
			||||||
 | 
					        if not subtitles:
 | 
				
			||||||
 | 
					            self.to_screen('%s has no %s' % (video_id, name))
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        self.to_screen(
 | 
				
			||||||
 | 
					            'Available %s for %s:' % (name, video_id))
 | 
				
			||||||
 | 
					        self.to_screen(render_table(
 | 
				
			||||||
 | 
					            ['Language', 'formats'],
 | 
				
			||||||
 | 
					            [[lang, ', '.join(f['ext'] for f in reversed(formats))]
 | 
				
			||||||
 | 
					                for lang, formats in subtitles.items()]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def urlopen(self, req):
 | 
					    def urlopen(self, req):
 | 
				
			||||||
        """ Start an HTTP download """
 | 
					        """ Start an HTTP download """
 | 
				
			||||||
@@ -1354,7 +1655,7 @@ class YoutubeDL(object):
 | 
				
			|||||||
        # urllib chokes on URLs with non-ASCII characters (see http://bugs.python.org/issue3991)
 | 
					        # urllib chokes on URLs with non-ASCII characters (see http://bugs.python.org/issue3991)
 | 
				
			||||||
        # To work around aforementioned issue we will replace request's original URL with
 | 
					        # To work around aforementioned issue we will replace request's original URL with
 | 
				
			||||||
        # percent-encoded one
 | 
					        # percent-encoded one
 | 
				
			||||||
        req_is_string = isinstance(req, basestring if sys.version_info < (3, 0) else compat_str)
 | 
					        req_is_string = isinstance(req, compat_basestring)
 | 
				
			||||||
        url = req if req_is_string else req.get_full_url()
 | 
					        url = req if req_is_string else req.get_full_url()
 | 
				
			||||||
        url_escaped = escape_url(url)
 | 
					        url_escaped = escape_url(url)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1406,7 +1707,7 @@ class YoutubeDL(object):
 | 
				
			|||||||
        self._write_string('[debug] Python version %s - %s\n' % (
 | 
					        self._write_string('[debug] Python version %s - %s\n' % (
 | 
				
			||||||
            platform.python_version(), platform_name()))
 | 
					            platform.python_version(), platform_name()))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        exe_versions = FFmpegPostProcessor.get_versions()
 | 
					        exe_versions = FFmpegPostProcessor.get_versions(self)
 | 
				
			||||||
        exe_versions['rtmpdump'] = rtmpdump_version()
 | 
					        exe_versions['rtmpdump'] = rtmpdump_version()
 | 
				
			||||||
        exe_str = ', '.join(
 | 
					        exe_str = ', '.join(
 | 
				
			||||||
            '%s %s' % (exe, v)
 | 
					            '%s %s' % (exe, v)
 | 
				
			||||||
@@ -1423,6 +1724,17 @@ class YoutubeDL(object):
 | 
				
			|||||||
                proxy_map.update(handler.proxies)
 | 
					                proxy_map.update(handler.proxies)
 | 
				
			||||||
        self._write_string('[debug] Proxy map: ' + compat_str(proxy_map) + '\n')
 | 
					        self._write_string('[debug] Proxy map: ' + compat_str(proxy_map) + '\n')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.params.get('call_home', False):
 | 
				
			||||||
 | 
					            ipaddr = self.urlopen('https://yt-dl.org/ip').read().decode('utf-8')
 | 
				
			||||||
 | 
					            self._write_string('[debug] Public IP address: %s\n' % ipaddr)
 | 
				
			||||||
 | 
					            latest_version = self.urlopen(
 | 
				
			||||||
 | 
					                'https://yt-dl.org/latest/version').read().decode('utf-8')
 | 
				
			||||||
 | 
					            if version_tuple(latest_version) > version_tuple(__version__):
 | 
				
			||||||
 | 
					                self.report_warning(
 | 
				
			||||||
 | 
					                    'You are using an outdated version (newest version: %s)! '
 | 
				
			||||||
 | 
					                    'See https://yt-dl.org/update if you need help updating.' %
 | 
				
			||||||
 | 
					                    latest_version)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _setup_opener(self):
 | 
					    def _setup_opener(self):
 | 
				
			||||||
        timeout_val = self.params.get('socket_timeout')
 | 
					        timeout_val = self.params.get('socket_timeout')
 | 
				
			||||||
        self._socket_timeout = 600 if timeout_val is None else float(timeout_val)
 | 
					        self._socket_timeout = 600 if timeout_val is None else float(timeout_val)
 | 
				
			||||||
@@ -1453,9 +1765,8 @@ class YoutubeDL(object):
 | 
				
			|||||||
        proxy_handler = compat_urllib_request.ProxyHandler(proxies)
 | 
					        proxy_handler = compat_urllib_request.ProxyHandler(proxies)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        debuglevel = 1 if self.params.get('debug_printtraffic') else 0
 | 
					        debuglevel = 1 if self.params.get('debug_printtraffic') else 0
 | 
				
			||||||
        https_handler = make_HTTPS_handler(
 | 
					        https_handler = make_HTTPS_handler(self.params, debuglevel=debuglevel)
 | 
				
			||||||
            self.params.get('nocheckcertificate', False), debuglevel=debuglevel)
 | 
					        ydlh = YoutubeDLHandler(self.params, debuglevel=debuglevel)
 | 
				
			||||||
        ydlh = YoutubeDLHandler(debuglevel=debuglevel)
 | 
					 | 
				
			||||||
        opener = compat_urllib_request.build_opener(
 | 
					        opener = compat_urllib_request.build_opener(
 | 
				
			||||||
            https_handler, proxy_handler, cookie_processor, ydlh)
 | 
					            https_handler, proxy_handler, cookie_processor, ydlh)
 | 
				
			||||||
        # Delete the default user-agent header, which would otherwise apply in
 | 
					        # Delete the default user-agent header, which would otherwise apply in
 | 
				
			||||||
@@ -1479,3 +1790,39 @@ class YoutubeDL(object):
 | 
				
			|||||||
        if encoding is None:
 | 
					        if encoding is None:
 | 
				
			||||||
            encoding = preferredencoding()
 | 
					            encoding = preferredencoding()
 | 
				
			||||||
        return encoding
 | 
					        return encoding
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _write_thumbnails(self, info_dict, filename):
 | 
				
			||||||
 | 
					        if self.params.get('writethumbnail', False):
 | 
				
			||||||
 | 
					            thumbnails = info_dict.get('thumbnails')
 | 
				
			||||||
 | 
					            if thumbnails:
 | 
				
			||||||
 | 
					                thumbnails = [thumbnails[-1]]
 | 
				
			||||||
 | 
					        elif self.params.get('write_all_thumbnails', False):
 | 
				
			||||||
 | 
					            thumbnails = info_dict.get('thumbnails')
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not thumbnails:
 | 
				
			||||||
 | 
					            # No thumbnails present, so return immediately
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for t in thumbnails:
 | 
				
			||||||
 | 
					            thumb_ext = determine_ext(t['url'], 'jpg')
 | 
				
			||||||
 | 
					            suffix = '_%s' % t['id'] if len(thumbnails) > 1 else ''
 | 
				
			||||||
 | 
					            thumb_display_id = '%s ' % t['id'] if len(thumbnails) > 1 else ''
 | 
				
			||||||
 | 
					            thumb_filename = os.path.splitext(filename)[0] + suffix + '.' + thumb_ext
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(thumb_filename)):
 | 
				
			||||||
 | 
					                self.to_screen('[%s] %s: Thumbnail %sis already present' %
 | 
				
			||||||
 | 
					                               (info_dict['extractor'], info_dict['id'], thumb_display_id))
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                self.to_screen('[%s] %s: Downloading thumbnail %s...' %
 | 
				
			||||||
 | 
					                               (info_dict['extractor'], info_dict['id'], thumb_display_id))
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    uf = self.urlopen(t['url'])
 | 
				
			||||||
 | 
					                    with open(thumb_filename, 'wb') as thumbf:
 | 
				
			||||||
 | 
					                        shutil.copyfileobj(uf, thumbf)
 | 
				
			||||||
 | 
					                    self.to_screen('[%s] %s: Writing thumbnail %sto: %s' %
 | 
				
			||||||
 | 
					                                   (info_dict['extractor'], info_dict['id'], thumb_display_id, thumb_filename))
 | 
				
			||||||
 | 
					                except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
 | 
				
			||||||
 | 
					                    self.report_warning('Unable to download thumbnail "%s": %s' %
 | 
				
			||||||
 | 
					                                        (t['url'], compat_str(err)))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,7 @@ import codecs
 | 
				
			|||||||
import io
 | 
					import io
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
import random
 | 
					import random
 | 
				
			||||||
 | 
					import shlex
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -23,9 +24,10 @@ from .compat import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
from .utils import (
 | 
					from .utils import (
 | 
				
			||||||
    DateRange,
 | 
					    DateRange,
 | 
				
			||||||
    DEFAULT_OUTTMPL,
 | 
					 | 
				
			||||||
    decodeOption,
 | 
					    decodeOption,
 | 
				
			||||||
 | 
					    DEFAULT_OUTTMPL,
 | 
				
			||||||
    DownloadError,
 | 
					    DownloadError,
 | 
				
			||||||
 | 
					    match_filter_func,
 | 
				
			||||||
    MaxDownloadsReached,
 | 
					    MaxDownloadsReached,
 | 
				
			||||||
    preferredencoding,
 | 
					    preferredencoding,
 | 
				
			||||||
    read_batch_urls,
 | 
					    read_batch_urls,
 | 
				
			||||||
@@ -38,7 +40,7 @@ from .update import update_self
 | 
				
			|||||||
from .downloader import (
 | 
					from .downloader import (
 | 
				
			||||||
    FileDownloader,
 | 
					    FileDownloader,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from .extractor import gen_extractors
 | 
					from .extractor import gen_extractors, list_extractors
 | 
				
			||||||
from .YoutubeDL import YoutubeDL
 | 
					from .YoutubeDL import YoutubeDL
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -95,24 +97,22 @@ def _real_main(argv=None):
 | 
				
			|||||||
    _enc = preferredencoding()
 | 
					    _enc = preferredencoding()
 | 
				
			||||||
    all_urls = [url.decode(_enc, 'ignore') if isinstance(url, bytes) else url for url in all_urls]
 | 
					    all_urls = [url.decode(_enc, 'ignore') if isinstance(url, bytes) else url for url in all_urls]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    extractors = gen_extractors()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if opts.list_extractors:
 | 
					    if opts.list_extractors:
 | 
				
			||||||
        for ie in sorted(extractors, key=lambda ie: ie.IE_NAME.lower()):
 | 
					        for ie in list_extractors(opts.age_limit):
 | 
				
			||||||
            compat_print(ie.IE_NAME + (' (CURRENTLY BROKEN)' if not ie._WORKING else ''))
 | 
					            compat_print(ie.IE_NAME + (' (CURRENTLY BROKEN)' if not ie._WORKING else ''))
 | 
				
			||||||
            matchedUrls = [url for url in all_urls if ie.suitable(url)]
 | 
					            matchedUrls = [url for url in all_urls if ie.suitable(url)]
 | 
				
			||||||
            for mu in matchedUrls:
 | 
					            for mu in matchedUrls:
 | 
				
			||||||
                compat_print('  ' + mu)
 | 
					                compat_print('  ' + mu)
 | 
				
			||||||
        sys.exit(0)
 | 
					        sys.exit(0)
 | 
				
			||||||
    if opts.list_extractor_descriptions:
 | 
					    if opts.list_extractor_descriptions:
 | 
				
			||||||
        for ie in sorted(extractors, key=lambda ie: ie.IE_NAME.lower()):
 | 
					        for ie in list_extractors(opts.age_limit):
 | 
				
			||||||
            if not ie._WORKING:
 | 
					            if not ie._WORKING:
 | 
				
			||||||
                continue
 | 
					                continue
 | 
				
			||||||
            desc = getattr(ie, 'IE_DESC', ie.IE_NAME)
 | 
					            desc = getattr(ie, 'IE_DESC', ie.IE_NAME)
 | 
				
			||||||
            if desc is False:
 | 
					            if desc is False:
 | 
				
			||||||
                continue
 | 
					                continue
 | 
				
			||||||
            if hasattr(ie, 'SEARCH_KEY'):
 | 
					            if hasattr(ie, 'SEARCH_KEY'):
 | 
				
			||||||
                _SEARCHES = ('cute kittens', 'slithering pythons', 'falling cat', 'angry poodle', 'purple fish', 'running tortoise', 'sleeping bunny')
 | 
					                _SEARCHES = ('cute kittens', 'slithering pythons', 'falling cat', 'angry poodle', 'purple fish', 'running tortoise', 'sleeping bunny', 'burping cow')
 | 
				
			||||||
                _COUNTS = ('', '5', '10', 'all')
 | 
					                _COUNTS = ('', '5', '10', 'all')
 | 
				
			||||||
                desc += ' (Example: "%s%s:%s" )' % (ie.SEARCH_KEY, random.choice(_COUNTS), random.choice(_SEARCHES))
 | 
					                desc += ' (Example: "%s%s:%s" )' % (ie.SEARCH_KEY, random.choice(_COUNTS), random.choice(_SEARCHES))
 | 
				
			||||||
            compat_print(desc)
 | 
					            compat_print(desc)
 | 
				
			||||||
@@ -145,10 +145,13 @@ def _real_main(argv=None):
 | 
				
			|||||||
            parser.error('invalid max_filesize specified')
 | 
					            parser.error('invalid max_filesize specified')
 | 
				
			||||||
        opts.max_filesize = numeric_limit
 | 
					        opts.max_filesize = numeric_limit
 | 
				
			||||||
    if opts.retries is not None:
 | 
					    if opts.retries is not None:
 | 
				
			||||||
        try:
 | 
					        if opts.retries in ('inf', 'infinite'):
 | 
				
			||||||
            opts.retries = int(opts.retries)
 | 
					            opts_retries = float('inf')
 | 
				
			||||||
        except (TypeError, ValueError):
 | 
					        else:
 | 
				
			||||||
            parser.error('invalid retry count specified')
 | 
					            try:
 | 
				
			||||||
 | 
					                opts_retries = int(opts.retries)
 | 
				
			||||||
 | 
					            except (TypeError, ValueError):
 | 
				
			||||||
 | 
					                parser.error('invalid retry count specified')
 | 
				
			||||||
    if opts.buffersize is not None:
 | 
					    if opts.buffersize is not None:
 | 
				
			||||||
        numeric_buffersize = FileDownloader.parse_bytes(opts.buffersize)
 | 
					        numeric_buffersize = FileDownloader.parse_bytes(opts.buffersize)
 | 
				
			||||||
        if numeric_buffersize is None:
 | 
					        if numeric_buffersize is None:
 | 
				
			||||||
@@ -168,6 +171,10 @@ def _real_main(argv=None):
 | 
				
			|||||||
    if opts.recodevideo is not None:
 | 
					    if opts.recodevideo is not None:
 | 
				
			||||||
        if opts.recodevideo not in ['mp4', 'flv', 'webm', 'ogg', 'mkv']:
 | 
					        if opts.recodevideo not in ['mp4', 'flv', 'webm', 'ogg', 'mkv']:
 | 
				
			||||||
            parser.error('invalid video recode format specified')
 | 
					            parser.error('invalid video recode format specified')
 | 
				
			||||||
 | 
					    if opts.convertsubtitles is not None:
 | 
				
			||||||
 | 
					        if opts.convertsubtitles not in ['srt', 'vtt', 'ass']:
 | 
				
			||||||
 | 
					            parser.error('invalid subtitle format specified')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if opts.date is not None:
 | 
					    if opts.date is not None:
 | 
				
			||||||
        date = DateRange.day(opts.date)
 | 
					        date = DateRange.day(opts.date)
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
@@ -186,20 +193,21 @@ def _real_main(argv=None):
 | 
				
			|||||||
        # In Python 2, sys.argv is a bytestring (also note http://bugs.python.org/issue2128 for Windows systems)
 | 
					        # In Python 2, sys.argv is a bytestring (also note http://bugs.python.org/issue2128 for Windows systems)
 | 
				
			||||||
        if opts.outtmpl is not None:
 | 
					        if opts.outtmpl is not None:
 | 
				
			||||||
            opts.outtmpl = opts.outtmpl.decode(preferredencoding())
 | 
					            opts.outtmpl = opts.outtmpl.decode(preferredencoding())
 | 
				
			||||||
    outtmpl = ((opts.outtmpl is not None and opts.outtmpl)
 | 
					    outtmpl = ((opts.outtmpl is not None and opts.outtmpl) or
 | 
				
			||||||
               or (opts.format == '-1' and opts.usetitle and '%(title)s-%(id)s-%(format)s.%(ext)s')
 | 
					               (opts.format == '-1' and opts.usetitle and '%(title)s-%(id)s-%(format)s.%(ext)s') or
 | 
				
			||||||
               or (opts.format == '-1' and '%(id)s-%(format)s.%(ext)s')
 | 
					               (opts.format == '-1' and '%(id)s-%(format)s.%(ext)s') or
 | 
				
			||||||
               or (opts.usetitle and opts.autonumber and '%(autonumber)s-%(title)s-%(id)s.%(ext)s')
 | 
					               (opts.usetitle and opts.autonumber and '%(autonumber)s-%(title)s-%(id)s.%(ext)s') or
 | 
				
			||||||
               or (opts.usetitle and '%(title)s-%(id)s.%(ext)s')
 | 
					               (opts.usetitle and '%(title)s-%(id)s.%(ext)s') or
 | 
				
			||||||
               or (opts.useid and '%(id)s.%(ext)s')
 | 
					               (opts.useid and '%(id)s.%(ext)s') or
 | 
				
			||||||
               or (opts.autonumber and '%(autonumber)s-%(id)s.%(ext)s')
 | 
					               (opts.autonumber and '%(autonumber)s-%(id)s.%(ext)s') or
 | 
				
			||||||
               or DEFAULT_OUTTMPL)
 | 
					               DEFAULT_OUTTMPL)
 | 
				
			||||||
    if not os.path.splitext(outtmpl)[1] and opts.extractaudio:
 | 
					    if not os.path.splitext(outtmpl)[1] and opts.extractaudio:
 | 
				
			||||||
        parser.error('Cannot download a video and extract audio into the same'
 | 
					        parser.error('Cannot download a video and extract audio into the same'
 | 
				
			||||||
                     ' file! Use "{0}.%(ext)s" instead of "{0}" as the output'
 | 
					                     ' file! Use "{0}.%(ext)s" instead of "{0}" as the output'
 | 
				
			||||||
                     ' template'.format(outtmpl))
 | 
					                     ' template'.format(outtmpl))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    any_printing = opts.geturl or opts.gettitle or opts.getid or opts.getthumbnail or opts.getdescription or opts.getfilename or opts.getformat or opts.getduration or opts.dumpjson or opts.dump_single_json
 | 
					    any_getting = opts.geturl or opts.gettitle or opts.getid or opts.getthumbnail or opts.getdescription or opts.getfilename or opts.getformat or opts.getduration or opts.dumpjson or opts.dump_single_json
 | 
				
			||||||
 | 
					    any_printing = opts.print_json
 | 
				
			||||||
    download_archive_fn = compat_expanduser(opts.download_archive) if opts.download_archive is not None else opts.download_archive
 | 
					    download_archive_fn = compat_expanduser(opts.download_archive) if opts.download_archive is not None else opts.download_archive
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # PostProcessors
 | 
					    # PostProcessors
 | 
				
			||||||
@@ -219,10 +227,14 @@ def _real_main(argv=None):
 | 
				
			|||||||
            'key': 'FFmpegVideoConvertor',
 | 
					            'key': 'FFmpegVideoConvertor',
 | 
				
			||||||
            'preferedformat': opts.recodevideo,
 | 
					            'preferedformat': opts.recodevideo,
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
 | 
					    if opts.convertsubtitles:
 | 
				
			||||||
 | 
					        postprocessors.append({
 | 
				
			||||||
 | 
					            'key': 'FFmpegSubtitlesConvertor',
 | 
				
			||||||
 | 
					            'format': opts.convertsubtitles,
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
    if opts.embedsubtitles:
 | 
					    if opts.embedsubtitles:
 | 
				
			||||||
        postprocessors.append({
 | 
					        postprocessors.append({
 | 
				
			||||||
            'key': 'FFmpegEmbedSubtitle',
 | 
					            'key': 'FFmpegEmbedSubtitle',
 | 
				
			||||||
            'subtitlesformat': opts.subtitlesformat,
 | 
					 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
    if opts.xattrs:
 | 
					    if opts.xattrs:
 | 
				
			||||||
        postprocessors.append({'key': 'XAttrMetadata'})
 | 
					        postprocessors.append({'key': 'XAttrMetadata'})
 | 
				
			||||||
@@ -238,6 +250,18 @@ def _real_main(argv=None):
 | 
				
			|||||||
            'verboseOutput': opts.verbose,
 | 
					            'verboseOutput': opts.verbose,
 | 
				
			||||||
            'exec_cmd': opts.exec_cmd,
 | 
					            'exec_cmd': opts.exec_cmd,
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
 | 
					    if opts.xattr_set_filesize:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            import xattr
 | 
				
			||||||
 | 
					            xattr  # Confuse flake8
 | 
				
			||||||
 | 
					        except ImportError:
 | 
				
			||||||
 | 
					            parser.error('setting filesize xattr requested but python-xattr is not available')
 | 
				
			||||||
 | 
					    external_downloader_args = None
 | 
				
			||||||
 | 
					    if opts.external_downloader_args:
 | 
				
			||||||
 | 
					        external_downloader_args = shlex.split(opts.external_downloader_args)
 | 
				
			||||||
 | 
					    match_filter = (
 | 
				
			||||||
 | 
					        None if opts.match_filter is None
 | 
				
			||||||
 | 
					        else match_filter_func(opts.match_filter))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ydl_opts = {
 | 
					    ydl_opts = {
 | 
				
			||||||
        'usenetrc': opts.usenetrc,
 | 
					        'usenetrc': opts.usenetrc,
 | 
				
			||||||
@@ -245,7 +269,7 @@ def _real_main(argv=None):
 | 
				
			|||||||
        'password': opts.password,
 | 
					        'password': opts.password,
 | 
				
			||||||
        'twofactor': opts.twofactor,
 | 
					        'twofactor': opts.twofactor,
 | 
				
			||||||
        'videopassword': opts.videopassword,
 | 
					        'videopassword': opts.videopassword,
 | 
				
			||||||
        'quiet': (opts.quiet or any_printing),
 | 
					        'quiet': (opts.quiet or any_getting or any_printing),
 | 
				
			||||||
        'no_warnings': opts.no_warnings,
 | 
					        'no_warnings': opts.no_warnings,
 | 
				
			||||||
        'forceurl': opts.geturl,
 | 
					        'forceurl': opts.geturl,
 | 
				
			||||||
        'forcetitle': opts.gettitle,
 | 
					        'forcetitle': opts.gettitle,
 | 
				
			||||||
@@ -255,9 +279,9 @@ def _real_main(argv=None):
 | 
				
			|||||||
        'forceduration': opts.getduration,
 | 
					        'forceduration': opts.getduration,
 | 
				
			||||||
        'forcefilename': opts.getfilename,
 | 
					        'forcefilename': opts.getfilename,
 | 
				
			||||||
        'forceformat': opts.getformat,
 | 
					        'forceformat': opts.getformat,
 | 
				
			||||||
        'forcejson': opts.dumpjson,
 | 
					        'forcejson': opts.dumpjson or opts.print_json,
 | 
				
			||||||
        'dump_single_json': opts.dump_single_json,
 | 
					        'dump_single_json': opts.dump_single_json,
 | 
				
			||||||
        'simulate': opts.simulate or any_printing,
 | 
					        'simulate': opts.simulate or any_getting,
 | 
				
			||||||
        'skip_download': opts.skip_download,
 | 
					        'skip_download': opts.skip_download,
 | 
				
			||||||
        'format': opts.format,
 | 
					        'format': opts.format,
 | 
				
			||||||
        'format_limit': opts.format_limit,
 | 
					        'format_limit': opts.format_limit,
 | 
				
			||||||
@@ -268,7 +292,7 @@ def _real_main(argv=None):
 | 
				
			|||||||
        'ignoreerrors': opts.ignoreerrors,
 | 
					        'ignoreerrors': opts.ignoreerrors,
 | 
				
			||||||
        'ratelimit': opts.ratelimit,
 | 
					        'ratelimit': opts.ratelimit,
 | 
				
			||||||
        'nooverwrites': opts.nooverwrites,
 | 
					        'nooverwrites': opts.nooverwrites,
 | 
				
			||||||
        'retries': opts.retries,
 | 
					        'retries': opts_retries,
 | 
				
			||||||
        'buffersize': opts.buffersize,
 | 
					        'buffersize': opts.buffersize,
 | 
				
			||||||
        'noresizebuffer': opts.noresizebuffer,
 | 
					        'noresizebuffer': opts.noresizebuffer,
 | 
				
			||||||
        'continuedl': opts.continue_dl,
 | 
					        'continuedl': opts.continue_dl,
 | 
				
			||||||
@@ -286,6 +310,7 @@ def _real_main(argv=None):
 | 
				
			|||||||
        'writeannotations': opts.writeannotations,
 | 
					        'writeannotations': opts.writeannotations,
 | 
				
			||||||
        'writeinfojson': opts.writeinfojson,
 | 
					        'writeinfojson': opts.writeinfojson,
 | 
				
			||||||
        'writethumbnail': opts.writethumbnail,
 | 
					        'writethumbnail': opts.writethumbnail,
 | 
				
			||||||
 | 
					        'write_all_thumbnails': opts.write_all_thumbnails,
 | 
				
			||||||
        'writesubtitles': opts.writesubtitles,
 | 
					        'writesubtitles': opts.writesubtitles,
 | 
				
			||||||
        'writeautomaticsub': opts.writeautomaticsub,
 | 
					        'writeautomaticsub': opts.writeautomaticsub,
 | 
				
			||||||
        'allsubtitles': opts.allsubtitles,
 | 
					        'allsubtitles': opts.allsubtitles,
 | 
				
			||||||
@@ -324,7 +349,21 @@ def _real_main(argv=None):
 | 
				
			|||||||
        'encoding': opts.encoding,
 | 
					        'encoding': opts.encoding,
 | 
				
			||||||
        'exec_cmd': opts.exec_cmd,
 | 
					        'exec_cmd': opts.exec_cmd,
 | 
				
			||||||
        'extract_flat': opts.extract_flat,
 | 
					        'extract_flat': opts.extract_flat,
 | 
				
			||||||
 | 
					        'merge_output_format': opts.merge_output_format,
 | 
				
			||||||
        'postprocessors': postprocessors,
 | 
					        'postprocessors': postprocessors,
 | 
				
			||||||
 | 
					        'fixup': opts.fixup,
 | 
				
			||||||
 | 
					        'source_address': opts.source_address,
 | 
				
			||||||
 | 
					        'call_home': opts.call_home,
 | 
				
			||||||
 | 
					        'sleep_interval': opts.sleep_interval,
 | 
				
			||||||
 | 
					        'external_downloader': opts.external_downloader,
 | 
				
			||||||
 | 
					        'list_thumbnails': opts.list_thumbnails,
 | 
				
			||||||
 | 
					        'playlist_items': opts.playlist_items,
 | 
				
			||||||
 | 
					        'xattr_set_filesize': opts.xattr_set_filesize,
 | 
				
			||||||
 | 
					        'match_filter': match_filter,
 | 
				
			||||||
 | 
					        'no_color': opts.no_color,
 | 
				
			||||||
 | 
					        'ffmpeg_location': opts.ffmpeg_location,
 | 
				
			||||||
 | 
					        'hls_prefer_native': opts.hls_prefer_native,
 | 
				
			||||||
 | 
					        'external_downloader_args': external_downloader_args,
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with YoutubeDL(ydl_opts) as ydl:
 | 
					    with YoutubeDL(ydl_opts) as ydl:
 | 
				
			||||||
@@ -342,7 +381,9 @@ def _real_main(argv=None):
 | 
				
			|||||||
                sys.exit()
 | 
					                sys.exit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            ydl.warn_if_short_id(sys.argv[1:] if argv is None else argv)
 | 
					            ydl.warn_if_short_id(sys.argv[1:] if argv is None else argv)
 | 
				
			||||||
            parser.error('you must provide at least one URL')
 | 
					            parser.error(
 | 
				
			||||||
 | 
					                'You must provide at least one URL.\n'
 | 
				
			||||||
 | 
					                'Type youtube-dl --help to see a list of all options.')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            if opts.load_info_filename is not None:
 | 
					            if opts.load_info_filename is not None:
 | 
				
			||||||
@@ -365,3 +406,5 @@ def main(argv=None):
 | 
				
			|||||||
        sys.exit('ERROR: fixed output name but more than one file to download')
 | 
					        sys.exit('ERROR: fixed output name but more than one file to download')
 | 
				
			||||||
    except KeyboardInterrupt:
 | 
					    except KeyboardInterrupt:
 | 
				
			||||||
        sys.exit('\nERROR: Interrupted by user')
 | 
					        sys.exit('\nERROR: Interrupted by user')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					__all__ = ['main', 'YoutubeDL', 'gen_extractors', 'list_extractors']
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,5 @@
 | 
				
			|||||||
from __future__ import unicode_literals
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
__all__ = ['aes_encrypt', 'key_expansion', 'aes_ctr_decrypt', 'aes_cbc_decrypt', 'aes_decrypt_text']
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import base64
 | 
					import base64
 | 
				
			||||||
from math import ceil
 | 
					from math import ceil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -329,3 +327,5 @@ def inc(data):
 | 
				
			|||||||
            data[i] = data[i] + 1
 | 
					            data[i] = data[i] + 1
 | 
				
			||||||
            break
 | 
					            break
 | 
				
			||||||
    return data
 | 
					    return data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					__all__ = ['aes_encrypt', 'key_expansion', 'aes_ctr_decrypt', 'aes_cbc_decrypt', 'aes_decrypt_text']
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,12 @@
 | 
				
			|||||||
from __future__ import unicode_literals
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import collections
 | 
				
			||||||
import getpass
 | 
					import getpass
 | 
				
			||||||
import optparse
 | 
					import optparse
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
import re
 | 
					import re
 | 
				
			||||||
 | 
					import shutil
 | 
				
			||||||
 | 
					import socket
 | 
				
			||||||
import subprocess
 | 
					import subprocess
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -70,6 +73,11 @@ try:
 | 
				
			|||||||
except ImportError:
 | 
					except ImportError:
 | 
				
			||||||
    compat_subprocess_get_DEVNULL = lambda: open(os.path.devnull, 'w')
 | 
					    compat_subprocess_get_DEVNULL = lambda: open(os.path.devnull, 'w')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					try:
 | 
				
			||||||
 | 
					    import http.server as compat_http_server
 | 
				
			||||||
 | 
					except ImportError:
 | 
				
			||||||
 | 
					    import BaseHTTPServer as compat_http_server
 | 
				
			||||||
 | 
					
 | 
				
			||||||
try:
 | 
					try:
 | 
				
			||||||
    from urllib.parse import unquote as compat_urllib_parse_unquote
 | 
					    from urllib.parse import unquote as compat_urllib_parse_unquote
 | 
				
			||||||
except ImportError:
 | 
					except ImportError:
 | 
				
			||||||
@@ -108,6 +116,26 @@ except ImportError:
 | 
				
			|||||||
            string += pct_sequence.decode(encoding, errors)
 | 
					            string += pct_sequence.decode(encoding, errors)
 | 
				
			||||||
        return string
 | 
					        return string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					try:
 | 
				
			||||||
 | 
					    compat_str = unicode  # Python 2
 | 
				
			||||||
 | 
					except NameError:
 | 
				
			||||||
 | 
					    compat_str = str
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					try:
 | 
				
			||||||
 | 
					    compat_basestring = basestring  # Python 2
 | 
				
			||||||
 | 
					except NameError:
 | 
				
			||||||
 | 
					    compat_basestring = str
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					try:
 | 
				
			||||||
 | 
					    compat_chr = unichr  # Python 2
 | 
				
			||||||
 | 
					except NameError:
 | 
				
			||||||
 | 
					    compat_chr = chr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					try:
 | 
				
			||||||
 | 
					    from xml.etree.ElementTree import ParseError as compat_xml_parse_error
 | 
				
			||||||
 | 
					except ImportError:  # Python 2.6
 | 
				
			||||||
 | 
					    from xml.parsers.expat import ExpatError as compat_xml_parse_error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
try:
 | 
					try:
 | 
				
			||||||
    from urllib.parse import parse_qs as compat_parse_qs
 | 
					    from urllib.parse import parse_qs as compat_parse_qs
 | 
				
			||||||
@@ -117,7 +145,7 @@ except ImportError:  # Python 2
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def _parse_qsl(qs, keep_blank_values=False, strict_parsing=False,
 | 
					    def _parse_qsl(qs, keep_blank_values=False, strict_parsing=False,
 | 
				
			||||||
                   encoding='utf-8', errors='replace'):
 | 
					                   encoding='utf-8', errors='replace'):
 | 
				
			||||||
        qs, _coerce_result = qs, unicode
 | 
					        qs, _coerce_result = qs, compat_str
 | 
				
			||||||
        pairs = [s2 for s1 in qs.split('&') for s2 in s1.split(';')]
 | 
					        pairs = [s2 for s1 in qs.split('&') for s2 in s1.split(';')]
 | 
				
			||||||
        r = []
 | 
					        r = []
 | 
				
			||||||
        for name_value in pairs:
 | 
					        for name_value in pairs:
 | 
				
			||||||
@@ -156,21 +184,6 @@ except ImportError:  # Python 2
 | 
				
			|||||||
                parsed_result[name] = [value]
 | 
					                parsed_result[name] = [value]
 | 
				
			||||||
        return parsed_result
 | 
					        return parsed_result
 | 
				
			||||||
 | 
					
 | 
				
			||||||
try:
 | 
					 | 
				
			||||||
    compat_str = unicode  # Python 2
 | 
					 | 
				
			||||||
except NameError:
 | 
					 | 
				
			||||||
    compat_str = str
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
try:
 | 
					 | 
				
			||||||
    compat_chr = unichr  # Python 2
 | 
					 | 
				
			||||||
except NameError:
 | 
					 | 
				
			||||||
    compat_chr = chr
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
try:
 | 
					 | 
				
			||||||
    from xml.etree.ElementTree import ParseError as compat_xml_parse_error
 | 
					 | 
				
			||||||
except ImportError:  # Python 2.6
 | 
					 | 
				
			||||||
    from xml.parsers.expat import ExpatError as compat_xml_parse_error
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
try:
 | 
					try:
 | 
				
			||||||
    from shlex import quote as shlex_quote
 | 
					    from shlex import quote as shlex_quote
 | 
				
			||||||
except ImportError:  # Python < 3.3
 | 
					except ImportError:  # Python < 3.3
 | 
				
			||||||
@@ -307,6 +320,32 @@ else:
 | 
				
			|||||||
    compat_kwargs = lambda kwargs: kwargs
 | 
					    compat_kwargs = lambda kwargs: kwargs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if sys.version_info < (2, 7):
 | 
				
			||||||
 | 
					    def compat_socket_create_connection(address, timeout, source_address=None):
 | 
				
			||||||
 | 
					        host, port = address
 | 
				
			||||||
 | 
					        err = None
 | 
				
			||||||
 | 
					        for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
 | 
				
			||||||
 | 
					            af, socktype, proto, canonname, sa = res
 | 
				
			||||||
 | 
					            sock = None
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                sock = socket.socket(af, socktype, proto)
 | 
				
			||||||
 | 
					                sock.settimeout(timeout)
 | 
				
			||||||
 | 
					                if source_address:
 | 
				
			||||||
 | 
					                    sock.bind(source_address)
 | 
				
			||||||
 | 
					                sock.connect(sa)
 | 
				
			||||||
 | 
					                return sock
 | 
				
			||||||
 | 
					            except socket.error as _:
 | 
				
			||||||
 | 
					                err = _
 | 
				
			||||||
 | 
					                if sock is not None:
 | 
				
			||||||
 | 
					                    sock.close()
 | 
				
			||||||
 | 
					        if err is not None:
 | 
				
			||||||
 | 
					            raise err
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            raise socket.error("getaddrinfo returns an empty list")
 | 
				
			||||||
 | 
					else:
 | 
				
			||||||
 | 
					    compat_socket_create_connection = socket.create_connection
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Fix https://github.com/rg3/youtube-dl/issues/4223
 | 
					# Fix https://github.com/rg3/youtube-dl/issues/4223
 | 
				
			||||||
# See http://bugs.python.org/issue9161 for what is broken
 | 
					# See http://bugs.python.org/issue9161 for what is broken
 | 
				
			||||||
def workaround_optparse_bug9161():
 | 
					def workaround_optparse_bug9161():
 | 
				
			||||||
@@ -327,21 +366,52 @@ def workaround_optparse_bug9161():
 | 
				
			|||||||
            return real_add_option(self, *bargs, **bkwargs)
 | 
					            return real_add_option(self, *bargs, **bkwargs)
 | 
				
			||||||
        optparse.OptionGroup.add_option = _compat_add_option
 | 
					        optparse.OptionGroup.add_option = _compat_add_option
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if hasattr(shutil, 'get_terminal_size'):  # Python >= 3.3
 | 
				
			||||||
 | 
					    compat_get_terminal_size = shutil.get_terminal_size
 | 
				
			||||||
 | 
					else:
 | 
				
			||||||
 | 
					    _terminal_size = collections.namedtuple('terminal_size', ['columns', 'lines'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def compat_get_terminal_size():
 | 
				
			||||||
 | 
					        columns = compat_getenv('COLUMNS', None)
 | 
				
			||||||
 | 
					        if columns:
 | 
				
			||||||
 | 
					            columns = int(columns)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            columns = None
 | 
				
			||||||
 | 
					        lines = compat_getenv('LINES', None)
 | 
				
			||||||
 | 
					        if lines:
 | 
				
			||||||
 | 
					            lines = int(lines)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            lines = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            sp = subprocess.Popen(
 | 
				
			||||||
 | 
					                ['stty', 'size'],
 | 
				
			||||||
 | 
					                stdout=subprocess.PIPE, stderr=subprocess.PIPE)
 | 
				
			||||||
 | 
					            out, err = sp.communicate()
 | 
				
			||||||
 | 
					            lines, columns = map(int, out.split())
 | 
				
			||||||
 | 
					        except:
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					        return _terminal_size(columns, lines)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
__all__ = [
 | 
					__all__ = [
 | 
				
			||||||
    'compat_HTTPError',
 | 
					    'compat_HTTPError',
 | 
				
			||||||
 | 
					    'compat_basestring',
 | 
				
			||||||
    'compat_chr',
 | 
					    'compat_chr',
 | 
				
			||||||
    'compat_cookiejar',
 | 
					    'compat_cookiejar',
 | 
				
			||||||
    'compat_expanduser',
 | 
					    'compat_expanduser',
 | 
				
			||||||
 | 
					    'compat_get_terminal_size',
 | 
				
			||||||
    'compat_getenv',
 | 
					    'compat_getenv',
 | 
				
			||||||
    'compat_getpass',
 | 
					    'compat_getpass',
 | 
				
			||||||
    'compat_html_entities',
 | 
					    'compat_html_entities',
 | 
				
			||||||
    'compat_html_parser',
 | 
					    'compat_html_parser',
 | 
				
			||||||
    'compat_http_client',
 | 
					    'compat_http_client',
 | 
				
			||||||
 | 
					    'compat_http_server',
 | 
				
			||||||
    'compat_kwargs',
 | 
					    'compat_kwargs',
 | 
				
			||||||
    'compat_ord',
 | 
					    'compat_ord',
 | 
				
			||||||
    'compat_parse_qs',
 | 
					    'compat_parse_qs',
 | 
				
			||||||
    'compat_print',
 | 
					    'compat_print',
 | 
				
			||||||
 | 
					    'compat_socket_create_connection',
 | 
				
			||||||
    'compat_str',
 | 
					    'compat_str',
 | 
				
			||||||
    'compat_subprocess_get_DEVNULL',
 | 
					    'compat_subprocess_get_DEVNULL',
 | 
				
			||||||
    'compat_urllib_error',
 | 
					    'compat_urllib_error',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,35 +1,44 @@
 | 
				
			|||||||
from __future__ import unicode_literals
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .common import FileDownloader
 | 
					from .common import FileDownloader
 | 
				
			||||||
 | 
					from .external import get_external_downloader
 | 
				
			||||||
 | 
					from .f4m import F4mFD
 | 
				
			||||||
from .hls import HlsFD
 | 
					from .hls import HlsFD
 | 
				
			||||||
from .hls import NativeHlsFD
 | 
					from .hls import NativeHlsFD
 | 
				
			||||||
from .http import HttpFD
 | 
					from .http import HttpFD
 | 
				
			||||||
from .mplayer import MplayerFD
 | 
					from .mplayer import MplayerFD
 | 
				
			||||||
from .rtmp import RtmpFD
 | 
					from .rtmp import RtmpFD
 | 
				
			||||||
from .f4m import F4mFD
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
from ..utils import (
 | 
					from ..utils import (
 | 
				
			||||||
    determine_ext,
 | 
					    determine_protocol,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PROTOCOL_MAP = {
 | 
				
			||||||
 | 
					    'rtmp': RtmpFD,
 | 
				
			||||||
 | 
					    'm3u8_native': NativeHlsFD,
 | 
				
			||||||
 | 
					    'm3u8': HlsFD,
 | 
				
			||||||
 | 
					    'mms': MplayerFD,
 | 
				
			||||||
 | 
					    'rtsp': MplayerFD,
 | 
				
			||||||
 | 
					    'f4m': F4mFD,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_suitable_downloader(info_dict):
 | 
					
 | 
				
			||||||
 | 
					def get_suitable_downloader(info_dict, params={}):
 | 
				
			||||||
    """Get the downloader class that can handle the info dict."""
 | 
					    """Get the downloader class that can handle the info dict."""
 | 
				
			||||||
    url = info_dict['url']
 | 
					    protocol = determine_protocol(info_dict)
 | 
				
			||||||
    protocol = info_dict.get('protocol')
 | 
					    info_dict['protocol'] = protocol
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if url.startswith('rtmp'):
 | 
					    external_downloader = params.get('external_downloader')
 | 
				
			||||||
        return RtmpFD
 | 
					    if external_downloader is not None:
 | 
				
			||||||
    if protocol == 'm3u8_native':
 | 
					        ed = get_external_downloader(external_downloader)
 | 
				
			||||||
 | 
					        if ed.supports(info_dict):
 | 
				
			||||||
 | 
					            return ed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if protocol == 'm3u8' and params.get('hls_prefer_native'):
 | 
				
			||||||
        return NativeHlsFD
 | 
					        return NativeHlsFD
 | 
				
			||||||
    if (protocol == 'm3u8') or (protocol is None and determine_ext(url) == 'm3u8'):
 | 
					
 | 
				
			||||||
        return HlsFD
 | 
					    return PROTOCOL_MAP.get(protocol, HttpFD)
 | 
				
			||||||
    if url.startswith('mms') or url.startswith('rtsp'):
 | 
					
 | 
				
			||||||
        return MplayerFD
 | 
					 | 
				
			||||||
    if determine_ext(url) == 'f4m':
 | 
					 | 
				
			||||||
        return F4mFD
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        return HttpFD
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
__all__ = [
 | 
					__all__ = [
 | 
				
			||||||
    'get_suitable_downloader',
 | 
					    'get_suitable_downloader',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
from __future__ import unicode_literals
 | 
					from __future__ import division, unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
import re
 | 
					import re
 | 
				
			||||||
@@ -25,21 +25,25 @@ class FileDownloader(object):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    Available options:
 | 
					    Available options:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    verbose:           Print additional info to stdout.
 | 
					    verbose:            Print additional info to stdout.
 | 
				
			||||||
    quiet:             Do not print messages to stdout.
 | 
					    quiet:              Do not print messages to stdout.
 | 
				
			||||||
    ratelimit:         Download speed limit, in bytes/sec.
 | 
					    ratelimit:          Download speed limit, in bytes/sec.
 | 
				
			||||||
    retries:           Number of times to retry for HTTP error 5xx
 | 
					    retries:            Number of times to retry for HTTP error 5xx
 | 
				
			||||||
    buffersize:        Size of download buffer in bytes.
 | 
					    buffersize:         Size of download buffer in bytes.
 | 
				
			||||||
    noresizebuffer:    Do not automatically resize the download buffer.
 | 
					    noresizebuffer:     Do not automatically resize the download buffer.
 | 
				
			||||||
    continuedl:        Try to continue downloads if possible.
 | 
					    continuedl:         Try to continue downloads if possible.
 | 
				
			||||||
    noprogress:        Do not print the progress bar.
 | 
					    noprogress:         Do not print the progress bar.
 | 
				
			||||||
    logtostderr:       Log messages to stderr instead of stdout.
 | 
					    logtostderr:        Log messages to stderr instead of stdout.
 | 
				
			||||||
    consoletitle:      Display progress in console window's titlebar.
 | 
					    consoletitle:       Display progress in console window's titlebar.
 | 
				
			||||||
    nopart:            Do not use temporary .part files.
 | 
					    nopart:             Do not use temporary .part files.
 | 
				
			||||||
    updatetime:        Use the Last-modified header to set output file timestamps.
 | 
					    updatetime:         Use the Last-modified header to set output file timestamps.
 | 
				
			||||||
    test:              Download only first bytes to test the downloader.
 | 
					    test:               Download only first bytes to test the downloader.
 | 
				
			||||||
    min_filesize:      Skip files smaller than this size
 | 
					    min_filesize:       Skip files smaller than this size
 | 
				
			||||||
    max_filesize:      Skip files larger than this size
 | 
					    max_filesize:       Skip files larger than this size
 | 
				
			||||||
 | 
					    xattr_set_filesize: Set ytdl.filesize user xattribute with expected size.
 | 
				
			||||||
 | 
					                        (experimenatal)
 | 
				
			||||||
 | 
					    external_downloader_args:  A list of additional command-line arguments for the
 | 
				
			||||||
 | 
					                        external downloader.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Subclasses of this one must re-define the real_download method.
 | 
					    Subclasses of this one must re-define the real_download method.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
@@ -52,6 +56,7 @@ class FileDownloader(object):
 | 
				
			|||||||
        self.ydl = ydl
 | 
					        self.ydl = ydl
 | 
				
			||||||
        self._progress_hooks = []
 | 
					        self._progress_hooks = []
 | 
				
			||||||
        self.params = params
 | 
					        self.params = params
 | 
				
			||||||
 | 
					        self.add_progress_hook(self.report_progress)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @staticmethod
 | 
					    @staticmethod
 | 
				
			||||||
    def format_seconds(seconds):
 | 
					    def format_seconds(seconds):
 | 
				
			||||||
@@ -224,42 +229,64 @@ class FileDownloader(object):
 | 
				
			|||||||
            self.to_screen(clear_line + fullmsg, skip_eol=not is_last_line)
 | 
					            self.to_screen(clear_line + fullmsg, skip_eol=not is_last_line)
 | 
				
			||||||
        self.to_console_title('youtube-dl ' + msg)
 | 
					        self.to_console_title('youtube-dl ' + msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def report_progress(self, percent, data_len_str, speed, eta):
 | 
					    def report_progress(self, s):
 | 
				
			||||||
        """Report download progress."""
 | 
					        if s['status'] == 'finished':
 | 
				
			||||||
        if self.params.get('noprogress', False):
 | 
					            if self.params.get('noprogress', False):
 | 
				
			||||||
 | 
					                self.to_screen('[download] Download completed')
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                s['_total_bytes_str'] = format_bytes(s['total_bytes'])
 | 
				
			||||||
 | 
					                if s.get('elapsed') is not None:
 | 
				
			||||||
 | 
					                    s['_elapsed_str'] = self.format_seconds(s['elapsed'])
 | 
				
			||||||
 | 
					                    msg_template = '100%% of %(_total_bytes_str)s in %(_elapsed_str)s'
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    msg_template = '100%% of %(_total_bytes_str)s'
 | 
				
			||||||
 | 
					                self._report_progress_status(
 | 
				
			||||||
 | 
					                    msg_template % s, is_last_line=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.params.get('noprogress'):
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
        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)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        msg = ('%s of %s at %s ETA %s' %
 | 
					        if s['status'] != 'downloading':
 | 
				
			||||||
               (percent_str, data_len_str, speed_str, eta_str))
 | 
					 | 
				
			||||||
        self._report_progress_status(msg)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def report_progress_live_stream(self, downloaded_data_len, speed, elapsed):
 | 
					 | 
				
			||||||
        if self.params.get('noprogress', False):
 | 
					 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
        downloaded_str = format_bytes(downloaded_data_len)
 | 
					 | 
				
			||||||
        speed_str = self.format_speed(speed)
 | 
					 | 
				
			||||||
        elapsed_str = FileDownloader.format_seconds(elapsed)
 | 
					 | 
				
			||||||
        msg = '%s at %s (%s)' % (downloaded_str, speed_str, elapsed_str)
 | 
					 | 
				
			||||||
        self._report_progress_status(msg)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def report_finish(self, data_len_str, tot_time):
 | 
					        if s.get('eta') is not None:
 | 
				
			||||||
        """Report download finished."""
 | 
					            s['_eta_str'] = self.format_eta(s['eta'])
 | 
				
			||||||
        if self.params.get('noprogress', False):
 | 
					 | 
				
			||||||
            self.to_screen('[download] Download completed')
 | 
					 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            self._report_progress_status(
 | 
					            s['_eta_str'] = 'Unknown ETA'
 | 
				
			||||||
                ('100%% of %s in %s' %
 | 
					
 | 
				
			||||||
                 (data_len_str, self.format_seconds(tot_time))),
 | 
					        if s.get('total_bytes') and s.get('downloaded_bytes') is not None:
 | 
				
			||||||
                is_last_line=True)
 | 
					            s['_percent_str'] = self.format_percent(100 * s['downloaded_bytes'] / s['total_bytes'])
 | 
				
			||||||
 | 
					        elif s.get('total_bytes_estimate') and s.get('downloaded_bytes') is not None:
 | 
				
			||||||
 | 
					            s['_percent_str'] = self.format_percent(100 * s['downloaded_bytes'] / s['total_bytes_estimate'])
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            if s.get('downloaded_bytes') == 0:
 | 
				
			||||||
 | 
					                s['_percent_str'] = self.format_percent(0)
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                s['_percent_str'] = 'Unknown %'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if s.get('speed') is not None:
 | 
				
			||||||
 | 
					            s['_speed_str'] = self.format_speed(s['speed'])
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            s['_speed_str'] = 'Unknown speed'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if s.get('total_bytes') is not None:
 | 
				
			||||||
 | 
					            s['_total_bytes_str'] = format_bytes(s['total_bytes'])
 | 
				
			||||||
 | 
					            msg_template = '%(_percent_str)s of %(_total_bytes_str)s at %(_speed_str)s ETA %(_eta_str)s'
 | 
				
			||||||
 | 
					        elif s.get('total_bytes_estimate') is not None:
 | 
				
			||||||
 | 
					            s['_total_bytes_estimate_str'] = format_bytes(s['total_bytes_estimate'])
 | 
				
			||||||
 | 
					            msg_template = '%(_percent_str)s of ~%(_total_bytes_estimate_str)s at %(_speed_str)s ETA %(_eta_str)s'
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            if s.get('downloaded_bytes') is not None:
 | 
				
			||||||
 | 
					                s['_downloaded_bytes_str'] = format_bytes(s['downloaded_bytes'])
 | 
				
			||||||
 | 
					                if s.get('elapsed'):
 | 
				
			||||||
 | 
					                    s['_elapsed_str'] = self.format_seconds(s['elapsed'])
 | 
				
			||||||
 | 
					                    msg_template = '%(_downloaded_bytes_str)s at %(_speed_str)s (%(_elapsed_str)s)'
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    msg_template = '%(_downloaded_bytes_str)s at %(_speed_str)s'
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                msg_template = '%(_percent_str)s % at %(_speed_str)s ETA %(_eta_str)s'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._report_progress_status(msg_template % s)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def report_resuming_byte(self, resume_len):
 | 
					    def report_resuming_byte(self, resume_len):
 | 
				
			||||||
        """Report attempt to resume at given byte."""
 | 
					        """Report attempt to resume at given byte."""
 | 
				
			||||||
@@ -284,8 +311,20 @@ class FileDownloader(object):
 | 
				
			|||||||
        """Download to a filename using the info from info_dict
 | 
					        """Download to a filename using the info from info_dict
 | 
				
			||||||
        Return True on success and False otherwise
 | 
					        Return True on success and False otherwise
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        nooverwrites_and_exists = (
 | 
				
			||||||
 | 
					            self.params.get('nooverwrites', False) and
 | 
				
			||||||
 | 
					            os.path.exists(encodeFilename(filename))
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        continuedl_and_exists = (
 | 
				
			||||||
 | 
					            self.params.get('continuedl', False) and
 | 
				
			||||||
 | 
					            os.path.isfile(encodeFilename(filename)) and
 | 
				
			||||||
 | 
					            not self.params.get('nopart', False)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Check file already present
 | 
					        # Check file already present
 | 
				
			||||||
        if self.params.get('continuedl', False) and os.path.isfile(encodeFilename(filename)) and not self.params.get('nopart', False):
 | 
					        if filename != '-' and nooverwrites_and_exists or continuedl_and_exists:
 | 
				
			||||||
            self.report_file_already_downloaded(filename)
 | 
					            self.report_file_already_downloaded(filename)
 | 
				
			||||||
            self._hook_progress({
 | 
					            self._hook_progress({
 | 
				
			||||||
                'filename': filename,
 | 
					                'filename': filename,
 | 
				
			||||||
@@ -294,6 +333,11 @@ class FileDownloader(object):
 | 
				
			|||||||
            })
 | 
					            })
 | 
				
			||||||
            return True
 | 
					            return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        sleep_interval = self.params.get('sleep_interval')
 | 
				
			||||||
 | 
					        if sleep_interval:
 | 
				
			||||||
 | 
					            self.to_screen('[download] Sleeping %s seconds...' % sleep_interval)
 | 
				
			||||||
 | 
					            time.sleep(sleep_interval)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return self.real_download(filename, info_dict)
 | 
					        return self.real_download(filename, info_dict)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def real_download(self, filename, info_dict):
 | 
					    def real_download(self, filename, info_dict):
 | 
				
			||||||
@@ -308,3 +352,24 @@ class FileDownloader(object):
 | 
				
			|||||||
        # See YoutubeDl.py (search for progress_hooks) for a description of
 | 
					        # See YoutubeDl.py (search for progress_hooks) for a description of
 | 
				
			||||||
        # this interface
 | 
					        # this interface
 | 
				
			||||||
        self._progress_hooks.append(ph)
 | 
					        self._progress_hooks.append(ph)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _debug_cmd(self, args, subprocess_encoding, exe=None):
 | 
				
			||||||
 | 
					        if not self.params.get('verbose', False):
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if exe is None:
 | 
				
			||||||
 | 
					            exe = os.path.basename(args[0])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if subprocess_encoding:
 | 
				
			||||||
 | 
					            str_args = [
 | 
				
			||||||
 | 
					                a.decode(subprocess_encoding) if isinstance(a, bytes) else a
 | 
				
			||||||
 | 
					                for a in args]
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            str_args = args
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            import pipes
 | 
				
			||||||
 | 
					            shell_quote = lambda args: ' '.join(map(pipes.quote, str_args))
 | 
				
			||||||
 | 
					        except ImportError:
 | 
				
			||||||
 | 
					            shell_quote = repr
 | 
				
			||||||
 | 
					        self.to_screen('[debug] %s command line: %s' % (
 | 
				
			||||||
 | 
					            exe, shell_quote(str_args)))
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										135
									
								
								youtube_dl/downloader/external.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								youtube_dl/downloader/external.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,135 @@
 | 
				
			|||||||
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import os.path
 | 
				
			||||||
 | 
					import subprocess
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .common import FileDownloader
 | 
				
			||||||
 | 
					from ..utils import (
 | 
				
			||||||
 | 
					    encodeFilename,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ExternalFD(FileDownloader):
 | 
				
			||||||
 | 
					    def real_download(self, filename, info_dict):
 | 
				
			||||||
 | 
					        self.report_destination(filename)
 | 
				
			||||||
 | 
					        tmpfilename = self.temp_name(filename)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        retval = self._call_downloader(tmpfilename, info_dict)
 | 
				
			||||||
 | 
					        if retval == 0:
 | 
				
			||||||
 | 
					            fsize = os.path.getsize(encodeFilename(tmpfilename))
 | 
				
			||||||
 | 
					            self.to_screen('\r[%s] Downloaded %s bytes' % (self.get_basename(), 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('\n')
 | 
				
			||||||
 | 
					            self.report_error('%s exited with code %d' % (
 | 
				
			||||||
 | 
					                self.get_basename(), retval))
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def get_basename(cls):
 | 
				
			||||||
 | 
					        return cls.__name__[:-2].lower()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def exe(self):
 | 
				
			||||||
 | 
					        return self.params.get('external_downloader')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def supports(cls, info_dict):
 | 
				
			||||||
 | 
					        return info_dict['protocol'] in ('http', 'https', 'ftp', 'ftps')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _source_address(self, command_option):
 | 
				
			||||||
 | 
					        source_address = self.params.get('source_address')
 | 
				
			||||||
 | 
					        if source_address is None:
 | 
				
			||||||
 | 
					            return []
 | 
				
			||||||
 | 
					        return [command_option, source_address]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _configuration_args(self, default=[]):
 | 
				
			||||||
 | 
					        ex_args = self.params.get('external_downloader_args')
 | 
				
			||||||
 | 
					        if ex_args is None:
 | 
				
			||||||
 | 
					            return default
 | 
				
			||||||
 | 
					        assert isinstance(ex_args, list)
 | 
				
			||||||
 | 
					        return ex_args
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _call_downloader(self, tmpfilename, info_dict):
 | 
				
			||||||
 | 
					        """ Either overwrite this or implement _make_cmd """
 | 
				
			||||||
 | 
					        cmd = self._make_cmd(tmpfilename, info_dict)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if sys.platform == 'win32' and sys.version_info < (3, 0):
 | 
				
			||||||
 | 
					            # Windows subprocess module does not actually support Unicode
 | 
				
			||||||
 | 
					            # on Python 2.x
 | 
				
			||||||
 | 
					            # See http://stackoverflow.com/a/9951851/35070
 | 
				
			||||||
 | 
					            subprocess_encoding = sys.getfilesystemencoding()
 | 
				
			||||||
 | 
					            cmd = [a.encode(subprocess_encoding, 'ignore') for a in cmd]
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            subprocess_encoding = None
 | 
				
			||||||
 | 
					        self._debug_cmd(cmd, subprocess_encoding)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        p = subprocess.Popen(
 | 
				
			||||||
 | 
					            cmd, stderr=subprocess.PIPE)
 | 
				
			||||||
 | 
					        _, stderr = p.communicate()
 | 
				
			||||||
 | 
					        if p.returncode != 0:
 | 
				
			||||||
 | 
					            self.to_stderr(stderr)
 | 
				
			||||||
 | 
					        return p.returncode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CurlFD(ExternalFD):
 | 
				
			||||||
 | 
					    def _make_cmd(self, tmpfilename, info_dict):
 | 
				
			||||||
 | 
					        cmd = [self.exe, '--location', '-o', tmpfilename]
 | 
				
			||||||
 | 
					        for key, val in info_dict['http_headers'].items():
 | 
				
			||||||
 | 
					            cmd += ['--header', '%s: %s' % (key, val)]
 | 
				
			||||||
 | 
					        cmd += self._source_address('--interface')
 | 
				
			||||||
 | 
					        cmd += self._configuration_args()
 | 
				
			||||||
 | 
					        cmd += ['--', info_dict['url']]
 | 
				
			||||||
 | 
					        return cmd
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class WgetFD(ExternalFD):
 | 
				
			||||||
 | 
					    def _make_cmd(self, tmpfilename, info_dict):
 | 
				
			||||||
 | 
					        cmd = [self.exe, '-O', tmpfilename, '-nv', '--no-cookies']
 | 
				
			||||||
 | 
					        for key, val in info_dict['http_headers'].items():
 | 
				
			||||||
 | 
					            cmd += ['--header', '%s: %s' % (key, val)]
 | 
				
			||||||
 | 
					        cmd += self._source_address('--bind-address')
 | 
				
			||||||
 | 
					        cmd += self._configuration_args()
 | 
				
			||||||
 | 
					        cmd += ['--', info_dict['url']]
 | 
				
			||||||
 | 
					        return cmd
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Aria2cFD(ExternalFD):
 | 
				
			||||||
 | 
					    def _make_cmd(self, tmpfilename, info_dict):
 | 
				
			||||||
 | 
					        cmd = [self.exe, '-c']
 | 
				
			||||||
 | 
					        cmd += self._configuration_args([
 | 
				
			||||||
 | 
					            '--min-split-size', '1M', '--max-connection-per-server', '4'])
 | 
				
			||||||
 | 
					        dn = os.path.dirname(tmpfilename)
 | 
				
			||||||
 | 
					        if dn:
 | 
				
			||||||
 | 
					            cmd += ['--dir', dn]
 | 
				
			||||||
 | 
					        cmd += ['--out', os.path.basename(tmpfilename)]
 | 
				
			||||||
 | 
					        for key, val in info_dict['http_headers'].items():
 | 
				
			||||||
 | 
					            cmd += ['--header', '%s: %s' % (key, val)]
 | 
				
			||||||
 | 
					        cmd += self._source_address('--interface')
 | 
				
			||||||
 | 
					        cmd += ['--', info_dict['url']]
 | 
				
			||||||
 | 
					        return cmd
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_BY_NAME = dict(
 | 
				
			||||||
 | 
					    (klass.get_basename(), klass)
 | 
				
			||||||
 | 
					    for name, klass in globals().items()
 | 
				
			||||||
 | 
					    if name.endswith('FD') and name != 'ExternalFD'
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def list_external_downloaders():
 | 
				
			||||||
 | 
					    return sorted(_BY_NAME.keys())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_external_downloader(external_downloader):
 | 
				
			||||||
 | 
					    """ Given the name of the executable, see whether we support the given
 | 
				
			||||||
 | 
					        downloader . """
 | 
				
			||||||
 | 
					    bn = os.path.basename(external_downloader)
 | 
				
			||||||
 | 
					    return _BY_NAME[bn]
 | 
				
			||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
from __future__ import unicode_literals
 | 
					from __future__ import division, unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import base64
 | 
					import base64
 | 
				
			||||||
import io
 | 
					import io
 | 
				
			||||||
@@ -11,11 +11,11 @@ from .common import FileDownloader
 | 
				
			|||||||
from .http import HttpFD
 | 
					from .http import HttpFD
 | 
				
			||||||
from ..compat import (
 | 
					from ..compat import (
 | 
				
			||||||
    compat_urlparse,
 | 
					    compat_urlparse,
 | 
				
			||||||
 | 
					    compat_urllib_error,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from ..utils import (
 | 
					from ..utils import (
 | 
				
			||||||
    struct_pack,
 | 
					    struct_pack,
 | 
				
			||||||
    struct_unpack,
 | 
					    struct_unpack,
 | 
				
			||||||
    format_bytes,
 | 
					 | 
				
			||||||
    encodeFilename,
 | 
					    encodeFilename,
 | 
				
			||||||
    sanitize_open,
 | 
					    sanitize_open,
 | 
				
			||||||
    xpath_text,
 | 
					    xpath_text,
 | 
				
			||||||
@@ -122,7 +122,8 @@ class FlvReader(io.BytesIO):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        self.read_unsigned_int()  # BootstrapinfoVersion
 | 
					        self.read_unsigned_int()  # BootstrapinfoVersion
 | 
				
			||||||
        # Profile,Live,Update,Reserved
 | 
					        # Profile,Live,Update,Reserved
 | 
				
			||||||
        self.read(1)
 | 
					        flags = self.read_unsigned_char()
 | 
				
			||||||
 | 
					        live = flags & 0x20 != 0
 | 
				
			||||||
        # time scale
 | 
					        # time scale
 | 
				
			||||||
        self.read_unsigned_int()
 | 
					        self.read_unsigned_int()
 | 
				
			||||||
        # CurrentMediaTime
 | 
					        # CurrentMediaTime
 | 
				
			||||||
@@ -161,6 +162,7 @@ class FlvReader(io.BytesIO):
 | 
				
			|||||||
        return {
 | 
					        return {
 | 
				
			||||||
            'segments': segments,
 | 
					            'segments': segments,
 | 
				
			||||||
            'fragments': fragments,
 | 
					            'fragments': fragments,
 | 
				
			||||||
 | 
					            'live': live,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def read_bootstrap_info(self):
 | 
					    def read_bootstrap_info(self):
 | 
				
			||||||
@@ -177,34 +179,47 @@ def build_fragments_list(boot_info):
 | 
				
			|||||||
    """ Return a list of (segment, fragment) for each fragment in the video """
 | 
					    """ Return a list of (segment, fragment) for each fragment in the video """
 | 
				
			||||||
    res = []
 | 
					    res = []
 | 
				
			||||||
    segment_run_table = boot_info['segments'][0]
 | 
					    segment_run_table = boot_info['segments'][0]
 | 
				
			||||||
    # I've only found videos with one segment
 | 
					 | 
				
			||||||
    segment_run_entry = segment_run_table['segment_run'][0]
 | 
					 | 
				
			||||||
    n_frags = segment_run_entry[1]
 | 
					 | 
				
			||||||
    fragment_run_entry_table = boot_info['fragments'][0]['fragments']
 | 
					    fragment_run_entry_table = boot_info['fragments'][0]['fragments']
 | 
				
			||||||
    first_frag_number = fragment_run_entry_table[0]['first']
 | 
					    first_frag_number = fragment_run_entry_table[0]['first']
 | 
				
			||||||
    for (i, frag_number) in zip(range(1, n_frags + 1), itertools.count(first_frag_number)):
 | 
					    fragments_counter = itertools.count(first_frag_number)
 | 
				
			||||||
        res.append((1, frag_number))
 | 
					    for segment, fragments_count in segment_run_table['segment_run']:
 | 
				
			||||||
 | 
					        for _ in range(fragments_count):
 | 
				
			||||||
 | 
					            res.append((segment, next(fragments_counter)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if boot_info['live']:
 | 
				
			||||||
 | 
					        res = res[-2:]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return res
 | 
					    return res
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def write_flv_header(stream, metadata):
 | 
					def write_unsigned_int(stream, val):
 | 
				
			||||||
    """Writes the FLV header and the metadata to stream"""
 | 
					    stream.write(struct_pack('!I', val))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def write_unsigned_int_24(stream, val):
 | 
				
			||||||
 | 
					    stream.write(struct_pack('!I', val)[1:])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def write_flv_header(stream):
 | 
				
			||||||
 | 
					    """Writes the FLV header to stream"""
 | 
				
			||||||
    # FLV header
 | 
					    # FLV header
 | 
				
			||||||
    stream.write(b'FLV\x01')
 | 
					    stream.write(b'FLV\x01')
 | 
				
			||||||
    stream.write(b'\x05')
 | 
					    stream.write(b'\x05')
 | 
				
			||||||
    stream.write(b'\x00\x00\x00\x09')
 | 
					    stream.write(b'\x00\x00\x00\x09')
 | 
				
			||||||
    # FLV File body
 | 
					 | 
				
			||||||
    stream.write(b'\x00\x00\x00\x00')
 | 
					    stream.write(b'\x00\x00\x00\x00')
 | 
				
			||||||
    # FLVTAG
 | 
					
 | 
				
			||||||
    # Script data
 | 
					
 | 
				
			||||||
    stream.write(b'\x12')
 | 
					def write_metadata_tag(stream, metadata):
 | 
				
			||||||
    # Size of the metadata with 3 bytes
 | 
					    """Writes optional metadata tag to stream"""
 | 
				
			||||||
    stream.write(struct_pack('!L', len(metadata))[1:])
 | 
					    SCRIPT_TAG = b'\x12'
 | 
				
			||||||
    stream.write(b'\x00\x00\x00\x00\x00\x00\x00')
 | 
					    FLV_TAG_HEADER_LEN = 11
 | 
				
			||||||
    stream.write(metadata)
 | 
					
 | 
				
			||||||
    # Magic numbers extracted from the output files produced by AdobeHDS.php
 | 
					    if metadata:
 | 
				
			||||||
    #(https://github.com/K-S-V/Scripts)
 | 
					        stream.write(SCRIPT_TAG)
 | 
				
			||||||
    stream.write(b'\x00\x00\x01\x73')
 | 
					        write_unsigned_int_24(stream, len(metadata))
 | 
				
			||||||
 | 
					        stream.write(b'\x00\x00\x00\x00\x00\x00\x00')
 | 
				
			||||||
 | 
					        stream.write(metadata)
 | 
				
			||||||
 | 
					        write_unsigned_int(stream, FLV_TAG_HEADER_LEN + len(metadata))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _add_ns(prop):
 | 
					def _add_ns(prop):
 | 
				
			||||||
@@ -221,11 +236,90 @@ class F4mFD(FileDownloader):
 | 
				
			|||||||
    A downloader for f4m manifests or AdobeHDS.
 | 
					    A downloader for f4m manifests or AdobeHDS.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _get_unencrypted_media(self, doc):
 | 
				
			||||||
 | 
					        media = doc.findall(_add_ns('media'))
 | 
				
			||||||
 | 
					        if not media:
 | 
				
			||||||
 | 
					            self.report_error('No media found')
 | 
				
			||||||
 | 
					        for e in (doc.findall(_add_ns('drmAdditionalHeader')) +
 | 
				
			||||||
 | 
					                  doc.findall(_add_ns('drmAdditionalHeaderSet'))):
 | 
				
			||||||
 | 
					            # If id attribute is missing it's valid for all media nodes
 | 
				
			||||||
 | 
					            # without drmAdditionalHeaderId or drmAdditionalHeaderSetId attribute
 | 
				
			||||||
 | 
					            if 'id' not in e.attrib:
 | 
				
			||||||
 | 
					                self.report_error('Missing ID in f4m DRM')
 | 
				
			||||||
 | 
					        media = list(filter(lambda e: 'drmAdditionalHeaderId' not in e.attrib and
 | 
				
			||||||
 | 
					                                      'drmAdditionalHeaderSetId' not in e.attrib,
 | 
				
			||||||
 | 
					                            media))
 | 
				
			||||||
 | 
					        if not media:
 | 
				
			||||||
 | 
					            self.report_error('Unsupported DRM')
 | 
				
			||||||
 | 
					        return media
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _get_bootstrap_from_url(self, bootstrap_url):
 | 
				
			||||||
 | 
					        bootstrap = self.ydl.urlopen(bootstrap_url).read()
 | 
				
			||||||
 | 
					        return read_bootstrap_info(bootstrap)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _update_live_fragments(self, bootstrap_url, latest_fragment):
 | 
				
			||||||
 | 
					        fragments_list = []
 | 
				
			||||||
 | 
					        retries = 30
 | 
				
			||||||
 | 
					        while (not fragments_list) and (retries > 0):
 | 
				
			||||||
 | 
					            boot_info = self._get_bootstrap_from_url(bootstrap_url)
 | 
				
			||||||
 | 
					            fragments_list = build_fragments_list(boot_info)
 | 
				
			||||||
 | 
					            fragments_list = [f for f in fragments_list if f[1] > latest_fragment]
 | 
				
			||||||
 | 
					            if not fragments_list:
 | 
				
			||||||
 | 
					                # Retry after a while
 | 
				
			||||||
 | 
					                time.sleep(5.0)
 | 
				
			||||||
 | 
					                retries -= 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not fragments_list:
 | 
				
			||||||
 | 
					            self.report_error('Failed to update fragments')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return fragments_list
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _parse_bootstrap_node(self, node, base_url):
 | 
				
			||||||
 | 
					        if node.text is None:
 | 
				
			||||||
 | 
					            bootstrap_url = compat_urlparse.urljoin(
 | 
				
			||||||
 | 
					                base_url, node.attrib['url'])
 | 
				
			||||||
 | 
					            boot_info = self._get_bootstrap_from_url(bootstrap_url)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            bootstrap_url = None
 | 
				
			||||||
 | 
					            bootstrap = base64.b64decode(node.text)
 | 
				
			||||||
 | 
					            boot_info = read_bootstrap_info(bootstrap)
 | 
				
			||||||
 | 
					        return (boot_info, bootstrap_url)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def real_download(self, filename, info_dict):
 | 
					    def real_download(self, filename, info_dict):
 | 
				
			||||||
        man_url = info_dict['url']
 | 
					        man_url = info_dict['url']
 | 
				
			||||||
        requested_bitrate = info_dict.get('tbr')
 | 
					        requested_bitrate = info_dict.get('tbr')
 | 
				
			||||||
        self.to_screen('[download] Downloading f4m manifest')
 | 
					        self.to_screen('[download] Downloading f4m manifest')
 | 
				
			||||||
        manifest = self.ydl.urlopen(man_url).read()
 | 
					        manifest = self.ydl.urlopen(man_url).read()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        doc = etree.fromstring(manifest)
 | 
				
			||||||
 | 
					        formats = [(int(f.attrib.get('bitrate', -1)), f)
 | 
				
			||||||
 | 
					                   for f in self._get_unencrypted_media(doc)]
 | 
				
			||||||
 | 
					        if requested_bitrate is None:
 | 
				
			||||||
 | 
					            # get the best format
 | 
				
			||||||
 | 
					            formats = sorted(formats, key=lambda f: f[0])
 | 
				
			||||||
 | 
					            rate, media = formats[-1]
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            rate, media = list(filter(
 | 
				
			||||||
 | 
					                lambda f: int(f[0]) == requested_bitrate, formats))[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        base_url = compat_urlparse.urljoin(man_url, media.attrib['url'])
 | 
				
			||||||
 | 
					        bootstrap_node = doc.find(_add_ns('bootstrapInfo'))
 | 
				
			||||||
 | 
					        boot_info, bootstrap_url = self._parse_bootstrap_node(bootstrap_node, base_url)
 | 
				
			||||||
 | 
					        live = boot_info['live']
 | 
				
			||||||
 | 
					        metadata_node = media.find(_add_ns('metadata'))
 | 
				
			||||||
 | 
					        if metadata_node is not None:
 | 
				
			||||||
 | 
					            metadata = base64.b64decode(metadata_node.text)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            metadata = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fragments_list = build_fragments_list(boot_info)
 | 
				
			||||||
 | 
					        if self.params.get('test', False):
 | 
				
			||||||
 | 
					            # We only download the first fragment
 | 
				
			||||||
 | 
					            fragments_list = fragments_list[:1]
 | 
				
			||||||
 | 
					        total_frags = len(fragments_list)
 | 
				
			||||||
 | 
					        # For some akamai manifests we'll need to add a query to the fragment url
 | 
				
			||||||
 | 
					        akamai_pv = xpath_text(doc, _add_ns('pv-2.0'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.report_destination(filename)
 | 
					        self.report_destination(filename)
 | 
				
			||||||
        http_dl = HttpQuietDownloader(
 | 
					        http_dl = HttpQuietDownloader(
 | 
				
			||||||
            self.ydl,
 | 
					            self.ydl,
 | 
				
			||||||
@@ -237,93 +331,101 @@ class F4mFD(FileDownloader):
 | 
				
			|||||||
                'test': self.params.get('test', False),
 | 
					                'test': self.params.get('test', False),
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					 | 
				
			||||||
        doc = etree.fromstring(manifest)
 | 
					 | 
				
			||||||
        formats = [(int(f.attrib.get('bitrate', -1)), f) for f in doc.findall(_add_ns('media'))]
 | 
					 | 
				
			||||||
        if requested_bitrate is None:
 | 
					 | 
				
			||||||
            # get the best format
 | 
					 | 
				
			||||||
            formats = sorted(formats, key=lambda f: f[0])
 | 
					 | 
				
			||||||
            rate, media = formats[-1]
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            rate, media = list(filter(
 | 
					 | 
				
			||||||
                lambda f: int(f[0]) == requested_bitrate, formats))[0]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        base_url = compat_urlparse.urljoin(man_url, media.attrib['url'])
 | 
					 | 
				
			||||||
        bootstrap_node = doc.find(_add_ns('bootstrapInfo'))
 | 
					 | 
				
			||||||
        if bootstrap_node.text is None:
 | 
					 | 
				
			||||||
            bootstrap_url = compat_urlparse.urljoin(
 | 
					 | 
				
			||||||
                base_url, bootstrap_node.attrib['url'])
 | 
					 | 
				
			||||||
            bootstrap = self.ydl.urlopen(bootstrap_url).read()
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            bootstrap = base64.b64decode(bootstrap_node.text)
 | 
					 | 
				
			||||||
        metadata = base64.b64decode(media.find(_add_ns('metadata')).text)
 | 
					 | 
				
			||||||
        boot_info = read_bootstrap_info(bootstrap)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        fragments_list = build_fragments_list(boot_info)
 | 
					 | 
				
			||||||
        if self.params.get('test', False):
 | 
					 | 
				
			||||||
            # We only download the first fragment
 | 
					 | 
				
			||||||
            fragments_list = fragments_list[:1]
 | 
					 | 
				
			||||||
        total_frags = len(fragments_list)
 | 
					 | 
				
			||||||
        # For some akamai manifests we'll need to add a query to the fragment url
 | 
					 | 
				
			||||||
        akamai_pv = xpath_text(doc, _add_ns('pv-2.0'))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        tmpfilename = self.temp_name(filename)
 | 
					        tmpfilename = self.temp_name(filename)
 | 
				
			||||||
        (dest_stream, tmpfilename) = sanitize_open(tmpfilename, 'wb')
 | 
					        (dest_stream, tmpfilename) = sanitize_open(tmpfilename, 'wb')
 | 
				
			||||||
        write_flv_header(dest_stream, metadata)
 | 
					
 | 
				
			||||||
 | 
					        write_flv_header(dest_stream)
 | 
				
			||||||
 | 
					        if not live:
 | 
				
			||||||
 | 
					            write_metadata_tag(dest_stream, metadata)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # This dict stores the download progress, it's updated by the progress
 | 
					        # This dict stores the download progress, it's updated by the progress
 | 
				
			||||||
        # hook
 | 
					        # hook
 | 
				
			||||||
        state = {
 | 
					        state = {
 | 
				
			||||||
 | 
					            'status': 'downloading',
 | 
				
			||||||
            'downloaded_bytes': 0,
 | 
					            'downloaded_bytes': 0,
 | 
				
			||||||
            'frag_counter': 0,
 | 
					            'frag_index': 0,
 | 
				
			||||||
 | 
					            'frag_count': total_frags,
 | 
				
			||||||
 | 
					            'filename': filename,
 | 
				
			||||||
 | 
					            'tmpfilename': tmpfilename,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        start = time.time()
 | 
					        start = time.time()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def frag_progress_hook(status):
 | 
					        def frag_progress_hook(s):
 | 
				
			||||||
            frag_total_bytes = status.get('total_bytes', 0)
 | 
					            if s['status'] not in ('downloading', 'finished'):
 | 
				
			||||||
            estimated_size = (state['downloaded_bytes'] +
 | 
					                return
 | 
				
			||||||
                              (total_frags - state['frag_counter']) * frag_total_bytes)
 | 
					
 | 
				
			||||||
            if status['status'] == 'finished':
 | 
					            frag_total_bytes = s.get('total_bytes', 0)
 | 
				
			||||||
 | 
					            if s['status'] == 'finished':
 | 
				
			||||||
                state['downloaded_bytes'] += frag_total_bytes
 | 
					                state['downloaded_bytes'] += frag_total_bytes
 | 
				
			||||||
                state['frag_counter'] += 1
 | 
					                state['frag_index'] += 1
 | 
				
			||||||
                progress = self.calc_percent(state['frag_counter'], total_frags)
 | 
					
 | 
				
			||||||
                byte_counter = state['downloaded_bytes']
 | 
					            estimated_size = (
 | 
				
			||||||
 | 
					                (state['downloaded_bytes'] + frag_total_bytes) /
 | 
				
			||||||
 | 
					                (state['frag_index'] + 1) * total_frags)
 | 
				
			||||||
 | 
					            time_now = time.time()
 | 
				
			||||||
 | 
					            state['total_bytes_estimate'] = estimated_size
 | 
				
			||||||
 | 
					            state['elapsed'] = time_now - start
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if s['status'] == 'finished':
 | 
				
			||||||
 | 
					                progress = self.calc_percent(state['frag_index'], total_frags)
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                frag_downloaded_bytes = status['downloaded_bytes']
 | 
					                frag_downloaded_bytes = s['downloaded_bytes']
 | 
				
			||||||
                byte_counter = state['downloaded_bytes'] + frag_downloaded_bytes
 | 
					 | 
				
			||||||
                frag_progress = self.calc_percent(frag_downloaded_bytes,
 | 
					                frag_progress = self.calc_percent(frag_downloaded_bytes,
 | 
				
			||||||
                                                  frag_total_bytes)
 | 
					                                                  frag_total_bytes)
 | 
				
			||||||
                progress = self.calc_percent(state['frag_counter'], total_frags)
 | 
					                progress = self.calc_percent(state['frag_index'], total_frags)
 | 
				
			||||||
                progress += frag_progress / float(total_frags)
 | 
					                progress += frag_progress / float(total_frags)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            eta = self.calc_eta(start, time.time(), estimated_size, byte_counter)
 | 
					                state['eta'] = self.calc_eta(
 | 
				
			||||||
            self.report_progress(progress, format_bytes(estimated_size),
 | 
					                    start, time_now, estimated_size, state['downloaded_bytes'] + frag_downloaded_bytes)
 | 
				
			||||||
                                 status.get('speed'), eta)
 | 
					                state['speed'] = s.get('speed')
 | 
				
			||||||
 | 
					            self._hook_progress(state)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        http_dl.add_progress_hook(frag_progress_hook)
 | 
					        http_dl.add_progress_hook(frag_progress_hook)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        frags_filenames = []
 | 
					        frags_filenames = []
 | 
				
			||||||
        for (seg_i, frag_i) in fragments_list:
 | 
					        while fragments_list:
 | 
				
			||||||
 | 
					            seg_i, frag_i = fragments_list.pop(0)
 | 
				
			||||||
            name = 'Seg%d-Frag%d' % (seg_i, frag_i)
 | 
					            name = 'Seg%d-Frag%d' % (seg_i, frag_i)
 | 
				
			||||||
            url = base_url + name
 | 
					            url = base_url + name
 | 
				
			||||||
            if akamai_pv:
 | 
					            if akamai_pv:
 | 
				
			||||||
                url += '?' + akamai_pv.strip(';')
 | 
					                url += '?' + akamai_pv.strip(';')
 | 
				
			||||||
            frag_filename = '%s-%s' % (tmpfilename, name)
 | 
					            frag_filename = '%s-%s' % (tmpfilename, name)
 | 
				
			||||||
            success = http_dl.download(frag_filename, {'url': url})
 | 
					            try:
 | 
				
			||||||
            if not success:
 | 
					                success = http_dl.download(frag_filename, {'url': url})
 | 
				
			||||||
                return False
 | 
					                if not success:
 | 
				
			||||||
            with open(frag_filename, 'rb') as down:
 | 
					                    return False
 | 
				
			||||||
                down_data = down.read()
 | 
					                with open(frag_filename, 'rb') as down:
 | 
				
			||||||
                reader = FlvReader(down_data)
 | 
					                    down_data = down.read()
 | 
				
			||||||
                while True:
 | 
					                    reader = FlvReader(down_data)
 | 
				
			||||||
                    _, box_type, box_data = reader.read_box_info()
 | 
					                    while True:
 | 
				
			||||||
                    if box_type == b'mdat':
 | 
					                        _, box_type, box_data = reader.read_box_info()
 | 
				
			||||||
                        dest_stream.write(box_data)
 | 
					                        if box_type == b'mdat':
 | 
				
			||||||
                        break
 | 
					                            dest_stream.write(box_data)
 | 
				
			||||||
            frags_filenames.append(frag_filename)
 | 
					                            break
 | 
				
			||||||
 | 
					                if live:
 | 
				
			||||||
 | 
					                    os.remove(frag_filename)
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    frags_filenames.append(frag_filename)
 | 
				
			||||||
 | 
					            except (compat_urllib_error.HTTPError, ) as err:
 | 
				
			||||||
 | 
					                if live and (err.code == 404 or err.code == 410):
 | 
				
			||||||
 | 
					                    # We didn't keep up with the live window. Continue
 | 
				
			||||||
 | 
					                    # with the next available fragment.
 | 
				
			||||||
 | 
					                    msg = 'Fragment %d unavailable' % frag_i
 | 
				
			||||||
 | 
					                    self.report_warning(msg)
 | 
				
			||||||
 | 
					                    fragments_list = []
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    raise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if not fragments_list and live and bootstrap_url:
 | 
				
			||||||
 | 
					                fragments_list = self._update_live_fragments(bootstrap_url, frag_i)
 | 
				
			||||||
 | 
					                total_frags += len(fragments_list)
 | 
				
			||||||
 | 
					                if fragments_list and (fragments_list[0][1] > frag_i + 1):
 | 
				
			||||||
 | 
					                    msg = 'Missed %d fragments' % (fragments_list[0][1] - (frag_i + 1))
 | 
				
			||||||
 | 
					                    self.report_warning(msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        dest_stream.close()
 | 
					        dest_stream.close()
 | 
				
			||||||
        self.report_finish(format_bytes(state['downloaded_bytes']), time.time() - start)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        elapsed = time.time() - start
 | 
				
			||||||
        self.try_rename(tmpfilename, filename)
 | 
					        self.try_rename(tmpfilename, filename)
 | 
				
			||||||
        for frag_file in frags_filenames:
 | 
					        for frag_file in frags_filenames:
 | 
				
			||||||
            os.remove(frag_file)
 | 
					            os.remove(frag_file)
 | 
				
			||||||
@@ -334,6 +436,7 @@ class F4mFD(FileDownloader):
 | 
				
			|||||||
            'total_bytes': fsize,
 | 
					            'total_bytes': fsize,
 | 
				
			||||||
            'filename': filename,
 | 
					            'filename': filename,
 | 
				
			||||||
            'status': 'finished',
 | 
					            'status': 'finished',
 | 
				
			||||||
 | 
					            'elapsed': elapsed,
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return True
 | 
					        return True
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,7 +11,7 @@ from ..compat import (
 | 
				
			|||||||
    compat_urllib_request,
 | 
					    compat_urllib_request,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from ..utils import (
 | 
					from ..utils import (
 | 
				
			||||||
    check_executable,
 | 
					    encodeArgument,
 | 
				
			||||||
    encodeFilename,
 | 
					    encodeFilename,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -22,26 +22,21 @@ class HlsFD(FileDownloader):
 | 
				
			|||||||
        self.report_destination(filename)
 | 
					        self.report_destination(filename)
 | 
				
			||||||
        tmpfilename = self.temp_name(filename)
 | 
					        tmpfilename = self.temp_name(filename)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        args = [
 | 
					        ffpp = FFmpegPostProcessor(downloader=self)
 | 
				
			||||||
            '-y', '-i', url, '-f', 'mp4', '-c', 'copy',
 | 
					        if not ffpp.available:
 | 
				
			||||||
            '-bsf:a', 'aac_adtstoasc',
 | 
					 | 
				
			||||||
            encodeFilename(tmpfilename, for_subprocess=True)]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for program in ['avconv', 'ffmpeg']:
 | 
					 | 
				
			||||||
            if check_executable(program, ['-version']):
 | 
					 | 
				
			||||||
                break
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            self.report_error('m3u8 download detected but ffmpeg or avconv could not be found. Please install one.')
 | 
					            self.report_error('m3u8 download detected but ffmpeg or avconv could not be found. Please install one.')
 | 
				
			||||||
            return False
 | 
					            return False
 | 
				
			||||||
        cmd = [program] + args
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        ffpp = FFmpegPostProcessor(downloader=self)
 | 
					 | 
				
			||||||
        ffpp.check_version()
 | 
					        ffpp.check_version()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        retval = subprocess.call(cmd)
 | 
					        args = [
 | 
				
			||||||
 | 
					            encodeArgument(opt)
 | 
				
			||||||
 | 
					            for opt in (ffpp.executable, '-y', '-i', url, '-f', 'mp4', '-c', 'copy', '-bsf:a', 'aac_adtstoasc')]
 | 
				
			||||||
 | 
					        args.append(encodeFilename(tmpfilename, True))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        retval = subprocess.call(args)
 | 
				
			||||||
        if retval == 0:
 | 
					        if retval == 0:
 | 
				
			||||||
            fsize = os.path.getsize(encodeFilename(tmpfilename))
 | 
					            fsize = os.path.getsize(encodeFilename(tmpfilename))
 | 
				
			||||||
            self.to_screen('\r[%s] %s bytes' % (cmd[0], fsize))
 | 
					            self.to_screen('\r[%s] %s bytes' % (args[0], fsize))
 | 
				
			||||||
            self.try_rename(tmpfilename, filename)
 | 
					            self.try_rename(tmpfilename, filename)
 | 
				
			||||||
            self._hook_progress({
 | 
					            self._hook_progress({
 | 
				
			||||||
                'downloaded_bytes': fsize,
 | 
					                'downloaded_bytes': fsize,
 | 
				
			||||||
@@ -52,7 +47,7 @@ class HlsFD(FileDownloader):
 | 
				
			|||||||
            return True
 | 
					            return True
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            self.to_stderr('\n')
 | 
					            self.to_stderr('\n')
 | 
				
			||||||
            self.report_error('%s exited with code %d' % (program, retval))
 | 
					            self.report_error('%s exited with code %d' % (ffpp.basename, retval))
 | 
				
			||||||
            return False
 | 
					            return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,8 @@
 | 
				
			|||||||
from __future__ import unicode_literals
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import errno
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
 | 
					import socket
 | 
				
			||||||
import time
 | 
					import time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .common import FileDownloader
 | 
					from .common import FileDownloader
 | 
				
			||||||
@@ -12,7 +14,6 @@ from ..utils import (
 | 
				
			|||||||
    ContentTooShortError,
 | 
					    ContentTooShortError,
 | 
				
			||||||
    encodeFilename,
 | 
					    encodeFilename,
 | 
				
			||||||
    sanitize_open,
 | 
					    sanitize_open,
 | 
				
			||||||
    format_bytes,
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -24,10 +25,6 @@ class HttpFD(FileDownloader):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        # Do not include the Accept-Encoding header
 | 
					        # Do not include the Accept-Encoding header
 | 
				
			||||||
        headers = {'Youtubedl-no-compression': 'True'}
 | 
					        headers = {'Youtubedl-no-compression': 'True'}
 | 
				
			||||||
        if 'user_agent' in info_dict:
 | 
					 | 
				
			||||||
            headers['Youtubedl-user-agent'] = info_dict['user_agent']
 | 
					 | 
				
			||||||
        if 'http_referer' in info_dict:
 | 
					 | 
				
			||||||
            headers['Referer'] = info_dict['http_referer']
 | 
					 | 
				
			||||||
        add_headers = info_dict.get('http_headers')
 | 
					        add_headers = info_dict.get('http_headers')
 | 
				
			||||||
        if add_headers:
 | 
					        if add_headers:
 | 
				
			||||||
            headers.update(add_headers)
 | 
					            headers.update(add_headers)
 | 
				
			||||||
@@ -103,6 +100,11 @@ class HttpFD(FileDownloader):
 | 
				
			|||||||
                            resume_len = 0
 | 
					                            resume_len = 0
 | 
				
			||||||
                            open_mode = 'wb'
 | 
					                            open_mode = 'wb'
 | 
				
			||||||
                            break
 | 
					                            break
 | 
				
			||||||
 | 
					            except socket.error as e:
 | 
				
			||||||
 | 
					                if e.errno != errno.ECONNRESET:
 | 
				
			||||||
 | 
					                    # Connection reset is no problem, just retry
 | 
				
			||||||
 | 
					                    raise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # Retry
 | 
					            # Retry
 | 
				
			||||||
            count += 1
 | 
					            count += 1
 | 
				
			||||||
            if count <= retries:
 | 
					            if count <= retries:
 | 
				
			||||||
@@ -133,7 +135,6 @@ class HttpFD(FileDownloader):
 | 
				
			|||||||
                self.to_screen('\r[download] File is larger than max-filesize (%s bytes > %s bytes). Aborting.' % (data_len, max_data_len))
 | 
					                self.to_screen('\r[download] File is larger than max-filesize (%s bytes > %s bytes). Aborting.' % (data_len, max_data_len))
 | 
				
			||||||
                return False
 | 
					                return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        data_len_str = format_bytes(data_len)
 | 
					 | 
				
			||||||
        byte_counter = 0 + resume_len
 | 
					        byte_counter = 0 + resume_len
 | 
				
			||||||
        block_size = self.params.get('buffersize', 1024)
 | 
					        block_size = self.params.get('buffersize', 1024)
 | 
				
			||||||
        start = time.time()
 | 
					        start = time.time()
 | 
				
			||||||
@@ -161,6 +162,14 @@ class HttpFD(FileDownloader):
 | 
				
			|||||||
                except (OSError, IOError) as err:
 | 
					                except (OSError, IOError) as err:
 | 
				
			||||||
                    self.report_error('unable to open for writing: %s' % str(err))
 | 
					                    self.report_error('unable to open for writing: %s' % str(err))
 | 
				
			||||||
                    return False
 | 
					                    return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if self.params.get('xattr_set_filesize', False) and data_len is not None:
 | 
				
			||||||
 | 
					                    try:
 | 
				
			||||||
 | 
					                        import xattr
 | 
				
			||||||
 | 
					                        xattr.setxattr(tmpfilename, 'user.ytdl.filesize', str(data_len))
 | 
				
			||||||
 | 
					                    except(OSError, IOError, ImportError) as err:
 | 
				
			||||||
 | 
					                        self.report_error('unable to set filesize xattr: %s' % str(err))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                stream.write(data_block)
 | 
					                stream.write(data_block)
 | 
				
			||||||
            except (IOError, OSError) as err:
 | 
					            except (IOError, OSError) as err:
 | 
				
			||||||
@@ -184,20 +193,19 @@ class HttpFD(FileDownloader):
 | 
				
			|||||||
            # Progress message
 | 
					            # Progress message
 | 
				
			||||||
            speed = self.calc_speed(start, now, byte_counter - resume_len)
 | 
					            speed = self.calc_speed(start, now, byte_counter - resume_len)
 | 
				
			||||||
            if data_len is None:
 | 
					            if data_len is None:
 | 
				
			||||||
                eta = percent = None
 | 
					                eta = None
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                percent = self.calc_percent(byte_counter, data_len)
 | 
					 | 
				
			||||||
                eta = self.calc_eta(start, time.time(), data_len - resume_len, byte_counter - resume_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({
 | 
					            self._hook_progress({
 | 
				
			||||||
 | 
					                'status': 'downloading',
 | 
				
			||||||
                'downloaded_bytes': byte_counter,
 | 
					                'downloaded_bytes': byte_counter,
 | 
				
			||||||
                'total_bytes': data_len,
 | 
					                'total_bytes': data_len,
 | 
				
			||||||
                'tmpfilename': tmpfilename,
 | 
					                'tmpfilename': tmpfilename,
 | 
				
			||||||
                'filename': filename,
 | 
					                'filename': filename,
 | 
				
			||||||
                'status': 'downloading',
 | 
					 | 
				
			||||||
                'eta': eta,
 | 
					                'eta': eta,
 | 
				
			||||||
                'speed': speed,
 | 
					                'speed': speed,
 | 
				
			||||||
 | 
					                'elapsed': now - start,
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if is_test and byte_counter == data_len:
 | 
					            if is_test and byte_counter == data_len:
 | 
				
			||||||
@@ -209,7 +217,13 @@ class HttpFD(FileDownloader):
 | 
				
			|||||||
            return False
 | 
					            return False
 | 
				
			||||||
        if tmpfilename != '-':
 | 
					        if tmpfilename != '-':
 | 
				
			||||||
            stream.close()
 | 
					            stream.close()
 | 
				
			||||||
        self.report_finish(data_len_str, (time.time() - start))
 | 
					
 | 
				
			||||||
 | 
					        self._hook_progress({
 | 
				
			||||||
 | 
					            'downloaded_bytes': byte_counter,
 | 
				
			||||||
 | 
					            'total_bytes': data_len,
 | 
				
			||||||
 | 
					            'tmpfilename': tmpfilename,
 | 
				
			||||||
 | 
					            'status': 'error',
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
        if data_len is not None and byte_counter != data_len:
 | 
					        if data_len is not None and byte_counter != data_len:
 | 
				
			||||||
            raise ContentTooShortError(byte_counter, int(data_len))
 | 
					            raise ContentTooShortError(byte_counter, int(data_len))
 | 
				
			||||||
        self.try_rename(tmpfilename, filename)
 | 
					        self.try_rename(tmpfilename, filename)
 | 
				
			||||||
@@ -223,6 +237,7 @@ class HttpFD(FileDownloader):
 | 
				
			|||||||
            'total_bytes': byte_counter,
 | 
					            'total_bytes': byte_counter,
 | 
				
			||||||
            'filename': filename,
 | 
					            'filename': filename,
 | 
				
			||||||
            'status': 'finished',
 | 
					            'status': 'finished',
 | 
				
			||||||
 | 
					            'elapsed': time.time() - start,
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return True
 | 
					        return True
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,8 +4,8 @@ import os
 | 
				
			|||||||
import subprocess
 | 
					import subprocess
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .common import FileDownloader
 | 
					from .common import FileDownloader
 | 
				
			||||||
from ..compat import compat_subprocess_get_DEVNULL
 | 
					 | 
				
			||||||
from ..utils import (
 | 
					from ..utils import (
 | 
				
			||||||
 | 
					    check_executable,
 | 
				
			||||||
    encodeFilename,
 | 
					    encodeFilename,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -20,11 +20,7 @@ class MplayerFD(FileDownloader):
 | 
				
			|||||||
            'mplayer', '-really-quiet', '-vo', 'null', '-vc', 'dummy',
 | 
					            'mplayer', '-really-quiet', '-vo', 'null', '-vc', 'dummy',
 | 
				
			||||||
            '-dumpstream', '-dumpfile', tmpfilename, url]
 | 
					            '-dumpstream', '-dumpfile', tmpfilename, url]
 | 
				
			||||||
        # Check for mplayer first
 | 
					        # Check for mplayer first
 | 
				
			||||||
        try:
 | 
					        if not check_executable('mplayer', ['-h']):
 | 
				
			||||||
            subprocess.call(
 | 
					 | 
				
			||||||
                ['mplayer', '-h'],
 | 
					 | 
				
			||||||
                stdout=compat_subprocess_get_DEVNULL(), stderr=subprocess.STDOUT)
 | 
					 | 
				
			||||||
        except (OSError, IOError):
 | 
					 | 
				
			||||||
            self.report_error('MMS or RTSP download detected but "%s" could not be run' % args[0])
 | 
					            self.report_error('MMS or RTSP download detected but "%s" could not be run' % args[0])
 | 
				
			||||||
            return False
 | 
					            return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,7 +11,6 @@ from ..compat import compat_str
 | 
				
			|||||||
from ..utils import (
 | 
					from ..utils import (
 | 
				
			||||||
    check_executable,
 | 
					    check_executable,
 | 
				
			||||||
    encodeFilename,
 | 
					    encodeFilename,
 | 
				
			||||||
    format_bytes,
 | 
					 | 
				
			||||||
    get_exe_version,
 | 
					    get_exe_version,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -51,23 +50,23 @@ class RtmpFD(FileDownloader):
 | 
				
			|||||||
                    if not resume_percent:
 | 
					                    if not resume_percent:
 | 
				
			||||||
                        resume_percent = percent
 | 
					                        resume_percent = percent
 | 
				
			||||||
                        resume_downloaded_data_len = downloaded_data_len
 | 
					                        resume_downloaded_data_len = downloaded_data_len
 | 
				
			||||||
                    eta = self.calc_eta(start, time.time(), 100 - resume_percent, percent - resume_percent)
 | 
					                    time_now = time.time()
 | 
				
			||||||
                    speed = self.calc_speed(start, time.time(), downloaded_data_len - resume_downloaded_data_len)
 | 
					                    eta = self.calc_eta(start, time_now, 100 - resume_percent, percent - resume_percent)
 | 
				
			||||||
 | 
					                    speed = self.calc_speed(start, time_now, downloaded_data_len - resume_downloaded_data_len)
 | 
				
			||||||
                    data_len = None
 | 
					                    data_len = None
 | 
				
			||||||
                    if percent > 0:
 | 
					                    if percent > 0:
 | 
				
			||||||
                        data_len = int(downloaded_data_len * 100 / percent)
 | 
					                        data_len = int(downloaded_data_len * 100 / percent)
 | 
				
			||||||
                    data_len_str = '~' + format_bytes(data_len)
 | 
					 | 
				
			||||||
                    self.report_progress(percent, data_len_str, speed, eta)
 | 
					 | 
				
			||||||
                    cursor_in_new_line = False
 | 
					 | 
				
			||||||
                    self._hook_progress({
 | 
					                    self._hook_progress({
 | 
				
			||||||
 | 
					                        'status': 'downloading',
 | 
				
			||||||
                        'downloaded_bytes': downloaded_data_len,
 | 
					                        'downloaded_bytes': downloaded_data_len,
 | 
				
			||||||
                        'total_bytes': data_len,
 | 
					                        'total_bytes_estimate': data_len,
 | 
				
			||||||
                        'tmpfilename': tmpfilename,
 | 
					                        'tmpfilename': tmpfilename,
 | 
				
			||||||
                        'filename': filename,
 | 
					                        'filename': filename,
 | 
				
			||||||
                        'status': 'downloading',
 | 
					 | 
				
			||||||
                        'eta': eta,
 | 
					                        'eta': eta,
 | 
				
			||||||
 | 
					                        'elapsed': time_now - start,
 | 
				
			||||||
                        'speed': speed,
 | 
					                        'speed': speed,
 | 
				
			||||||
                    })
 | 
					                    })
 | 
				
			||||||
 | 
					                    cursor_in_new_line = False
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
                    # no percent for live streams
 | 
					                    # no percent for live streams
 | 
				
			||||||
                    mobj = re.search(r'([0-9]+\.[0-9]{3}) kB / [0-9]+\.[0-9]{2} sec', line)
 | 
					                    mobj = re.search(r'([0-9]+\.[0-9]{3}) kB / [0-9]+\.[0-9]{2} sec', line)
 | 
				
			||||||
@@ -75,15 +74,15 @@ class RtmpFD(FileDownloader):
 | 
				
			|||||||
                        downloaded_data_len = int(float(mobj.group(1)) * 1024)
 | 
					                        downloaded_data_len = int(float(mobj.group(1)) * 1024)
 | 
				
			||||||
                        time_now = time.time()
 | 
					                        time_now = time.time()
 | 
				
			||||||
                        speed = self.calc_speed(start, time_now, downloaded_data_len)
 | 
					                        speed = self.calc_speed(start, time_now, downloaded_data_len)
 | 
				
			||||||
                        self.report_progress_live_stream(downloaded_data_len, speed, time_now - start)
 | 
					 | 
				
			||||||
                        cursor_in_new_line = False
 | 
					 | 
				
			||||||
                        self._hook_progress({
 | 
					                        self._hook_progress({
 | 
				
			||||||
                            'downloaded_bytes': downloaded_data_len,
 | 
					                            'downloaded_bytes': downloaded_data_len,
 | 
				
			||||||
                            'tmpfilename': tmpfilename,
 | 
					                            'tmpfilename': tmpfilename,
 | 
				
			||||||
                            'filename': filename,
 | 
					                            'filename': filename,
 | 
				
			||||||
                            'status': 'downloading',
 | 
					                            'status': 'downloading',
 | 
				
			||||||
 | 
					                            'elapsed': time_now - start,
 | 
				
			||||||
                            'speed': speed,
 | 
					                            'speed': speed,
 | 
				
			||||||
                        })
 | 
					                        })
 | 
				
			||||||
 | 
					                        cursor_in_new_line = False
 | 
				
			||||||
                    elif self.params.get('verbose', False):
 | 
					                    elif self.params.get('verbose', False):
 | 
				
			||||||
                        if not cursor_in_new_line:
 | 
					                        if not cursor_in_new_line:
 | 
				
			||||||
                            self.to_screen('')
 | 
					                            self.to_screen('')
 | 
				
			||||||
@@ -104,6 +103,9 @@ class RtmpFD(FileDownloader):
 | 
				
			|||||||
        live = info_dict.get('rtmp_live', False)
 | 
					        live = info_dict.get('rtmp_live', False)
 | 
				
			||||||
        conn = info_dict.get('rtmp_conn', None)
 | 
					        conn = info_dict.get('rtmp_conn', None)
 | 
				
			||||||
        protocol = info_dict.get('rtmp_protocol', None)
 | 
					        protocol = info_dict.get('rtmp_protocol', None)
 | 
				
			||||||
 | 
					        real_time = info_dict.get('rtmp_real_time', False)
 | 
				
			||||||
 | 
					        no_resume = info_dict.get('no_resume', False)
 | 
				
			||||||
 | 
					        continue_dl = info_dict.get('continuedl', False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.report_destination(filename)
 | 
					        self.report_destination(filename)
 | 
				
			||||||
        tmpfilename = self.temp_name(filename)
 | 
					        tmpfilename = self.temp_name(filename)
 | 
				
			||||||
@@ -117,7 +119,9 @@ class RtmpFD(FileDownloader):
 | 
				
			|||||||
        # Download using rtmpdump. rtmpdump returns exit code 2 when
 | 
					        # Download using rtmpdump. rtmpdump returns exit code 2 when
 | 
				
			||||||
        # the connection was interrumpted and resuming appears to be
 | 
					        # the connection was interrumpted and resuming appears to be
 | 
				
			||||||
        # possible. This is part of rtmpdump's normal usage, AFAIK.
 | 
					        # possible. This is part of rtmpdump's normal usage, AFAIK.
 | 
				
			||||||
        basic_args = ['rtmpdump', '--verbose', '-r', url, '-o', tmpfilename]
 | 
					        basic_args = [
 | 
				
			||||||
 | 
					            'rtmpdump', '--verbose', '-r', url,
 | 
				
			||||||
 | 
					            '-o', encodeFilename(tmpfilename, True)]
 | 
				
			||||||
        if player_url is not None:
 | 
					        if player_url is not None:
 | 
				
			||||||
            basic_args += ['--swfVfy', player_url]
 | 
					            basic_args += ['--swfVfy', player_url]
 | 
				
			||||||
        if page_url is not None:
 | 
					        if page_url is not None:
 | 
				
			||||||
@@ -141,7 +145,14 @@ class RtmpFD(FileDownloader):
 | 
				
			|||||||
            basic_args += ['--conn', conn]
 | 
					            basic_args += ['--conn', conn]
 | 
				
			||||||
        if protocol is not None:
 | 
					        if protocol is not None:
 | 
				
			||||||
            basic_args += ['--protocol', protocol]
 | 
					            basic_args += ['--protocol', protocol]
 | 
				
			||||||
        args = basic_args + [[], ['--resume', '--skip', '1']][not live and self.params.get('continuedl', False)]
 | 
					        if real_time:
 | 
				
			||||||
 | 
					            basic_args += ['--realtime']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        args = basic_args
 | 
				
			||||||
 | 
					        if not no_resume and continue_dl and not live:
 | 
				
			||||||
 | 
					            args += ['--resume']
 | 
				
			||||||
 | 
					        if not live and continue_dl:
 | 
				
			||||||
 | 
					            args += ['--skip', '1']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if sys.platform == 'win32' and sys.version_info < (3, 0):
 | 
					        if sys.platform == 'win32' and sys.version_info < (3, 0):
 | 
				
			||||||
            # Windows subprocess module does not actually support Unicode
 | 
					            # Windows subprocess module does not actually support Unicode
 | 
				
			||||||
@@ -152,19 +163,7 @@ class RtmpFD(FileDownloader):
 | 
				
			|||||||
        else:
 | 
					        else:
 | 
				
			||||||
            subprocess_encoding = None
 | 
					            subprocess_encoding = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self.params.get('verbose', False):
 | 
					        self._debug_cmd(args, subprocess_encoding, exe='rtmpdump')
 | 
				
			||||||
            if subprocess_encoding:
 | 
					 | 
				
			||||||
                str_args = [
 | 
					 | 
				
			||||||
                    a.decode(subprocess_encoding) if isinstance(a, bytes) else a
 | 
					 | 
				
			||||||
                    for a in args]
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                str_args = args
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                import pipes
 | 
					 | 
				
			||||||
                shell_quote = lambda args: ' '.join(map(pipes.quote, str_args))
 | 
					 | 
				
			||||||
            except ImportError:
 | 
					 | 
				
			||||||
                shell_quote = repr
 | 
					 | 
				
			||||||
            self.to_screen('[debug] rtmpdump command line: ' + shell_quote(str_args))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        RD_SUCCESS = 0
 | 
					        RD_SUCCESS = 0
 | 
				
			||||||
        RD_FAILED = 1
 | 
					        RD_FAILED = 1
 | 
				
			||||||
@@ -185,7 +184,7 @@ class RtmpFD(FileDownloader):
 | 
				
			|||||||
            cursize = os.path.getsize(encodeFilename(tmpfilename))
 | 
					            cursize = os.path.getsize(encodeFilename(tmpfilename))
 | 
				
			||||||
            if prevsize == cursize and retval == RD_FAILED:
 | 
					            if prevsize == cursize and retval == RD_FAILED:
 | 
				
			||||||
                break
 | 
					                break
 | 
				
			||||||
             # Some rtmp streams seem abort after ~ 99.8%. Don't complain for those
 | 
					            # Some rtmp streams seem abort after ~ 99.8%. Don't complain for those
 | 
				
			||||||
            if prevsize == cursize and retval == RD_INCOMPLETE and cursize > 1024:
 | 
					            if prevsize == cursize and retval == RD_INCOMPLETE and cursize > 1024:
 | 
				
			||||||
                self.to_screen('[rtmpdump] Could not download the whole video. This can happen for some advertisements.')
 | 
					                self.to_screen('[rtmpdump] Could not download the whole video. This can happen for some advertisements.')
 | 
				
			||||||
                retval = RD_SUCCESS
 | 
					                retval = RD_SUCCESS
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,16 @@
 | 
				
			|||||||
from __future__ import unicode_literals
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .abc import ABCIE
 | 
					from .abc import ABCIE
 | 
				
			||||||
 | 
					from .abc7news import Abc7NewsIE
 | 
				
			||||||
from .academicearth import AcademicEarthCourseIE
 | 
					from .academicearth import AcademicEarthCourseIE
 | 
				
			||||||
from .addanime import AddAnimeIE
 | 
					from .addanime import AddAnimeIE
 | 
				
			||||||
 | 
					from .adobetv import AdobeTVIE
 | 
				
			||||||
from .adultswim import AdultSwimIE
 | 
					from .adultswim import AdultSwimIE
 | 
				
			||||||
 | 
					from .aftenposten import AftenpostenIE
 | 
				
			||||||
from .aftonbladet import AftonbladetIE
 | 
					from .aftonbladet import AftonbladetIE
 | 
				
			||||||
 | 
					from .airmozilla import AirMozillaIE
 | 
				
			||||||
from .aljazeera import AlJazeeraIE
 | 
					from .aljazeera import AlJazeeraIE
 | 
				
			||||||
 | 
					from .alphaporno import AlphaPornoIE
 | 
				
			||||||
from .anitube import AnitubeIE
 | 
					from .anitube import AnitubeIE
 | 
				
			||||||
from .anysex import AnySexIE
 | 
					from .anysex import AnySexIE
 | 
				
			||||||
from .aol import AolIE
 | 
					from .aol import AolIE
 | 
				
			||||||
@@ -23,8 +28,9 @@ from .arte import (
 | 
				
			|||||||
    ArteTVDDCIE,
 | 
					    ArteTVDDCIE,
 | 
				
			||||||
    ArteTVEmbedIE,
 | 
					    ArteTVEmbedIE,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from .audiomack import AudiomackIE
 | 
					from .atresplayer import AtresPlayerIE
 | 
				
			||||||
from .auengine import AUEngineIE
 | 
					from .atttechchannel import ATTTechChannelIE
 | 
				
			||||||
 | 
					from .audiomack import AudiomackIE, AudiomackAlbumIE
 | 
				
			||||||
from .azubu import AzubuIE
 | 
					from .azubu import AzubuIE
 | 
				
			||||||
from .bambuser import BambuserIE, BambuserChannelIE
 | 
					from .bambuser import BambuserIE, BambuserChannelIE
 | 
				
			||||||
from .bandcamp import BandcampIE, BandcampAlbumIE
 | 
					from .bandcamp import BandcampIE, BandcampAlbumIE
 | 
				
			||||||
@@ -44,14 +50,24 @@ from .brightcove import BrightcoveIE
 | 
				
			|||||||
from .buzzfeed import BuzzFeedIE
 | 
					from .buzzfeed import BuzzFeedIE
 | 
				
			||||||
from .byutv import BYUtvIE
 | 
					from .byutv import BYUtvIE
 | 
				
			||||||
from .c56 import C56IE
 | 
					from .c56 import C56IE
 | 
				
			||||||
 | 
					from .camdemy import (
 | 
				
			||||||
 | 
					    CamdemyIE,
 | 
				
			||||||
 | 
					    CamdemyFolderIE
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
from .canal13cl import Canal13clIE
 | 
					from .canal13cl import Canal13clIE
 | 
				
			||||||
from .canalplus import CanalplusIE
 | 
					from .canalplus import CanalplusIE
 | 
				
			||||||
from .canalc2 import Canalc2IE
 | 
					from .canalc2 import Canalc2IE
 | 
				
			||||||
from .cbs import CBSIE
 | 
					from .cbs import CBSIE
 | 
				
			||||||
from .cbsnews import CBSNewsIE
 | 
					from .cbsnews import CBSNewsIE
 | 
				
			||||||
 | 
					from .cbssports import CBSSportsIE
 | 
				
			||||||
 | 
					from .ccc import CCCIE
 | 
				
			||||||
from .ceskatelevize import CeskaTelevizeIE
 | 
					from .ceskatelevize import CeskaTelevizeIE
 | 
				
			||||||
from .channel9 import Channel9IE
 | 
					from .channel9 import Channel9IE
 | 
				
			||||||
from .chilloutzone import ChilloutzoneIE
 | 
					from .chilloutzone import ChilloutzoneIE
 | 
				
			||||||
 | 
					from .chirbit import (
 | 
				
			||||||
 | 
					    ChirbitIE,
 | 
				
			||||||
 | 
					    ChirbitProfileIE,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
from .cinchcast import CinchcastIE
 | 
					from .cinchcast import CinchcastIE
 | 
				
			||||||
from .clipfish import ClipfishIE
 | 
					from .clipfish import ClipfishIE
 | 
				
			||||||
from .cliphunter import CliphunterIE
 | 
					from .cliphunter import CliphunterIE
 | 
				
			||||||
@@ -63,10 +79,13 @@ from .cnet import CNETIE
 | 
				
			|||||||
from .cnn import (
 | 
					from .cnn import (
 | 
				
			||||||
    CNNIE,
 | 
					    CNNIE,
 | 
				
			||||||
    CNNBlogsIE,
 | 
					    CNNBlogsIE,
 | 
				
			||||||
 | 
					    CNNArticleIE,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from .collegehumor import CollegeHumorIE
 | 
					from .collegehumor import CollegeHumorIE
 | 
				
			||||||
 | 
					from .collegerama import CollegeRamaIE
 | 
				
			||||||
from .comedycentral import ComedyCentralIE, ComedyCentralShowsIE
 | 
					from .comedycentral import ComedyCentralIE, ComedyCentralShowsIE
 | 
				
			||||||
from .comcarcoff import ComCarCoffIE
 | 
					from .comcarcoff import ComCarCoffIE
 | 
				
			||||||
 | 
					from .commonmistakes import CommonMistakesIE, UnicodeBOMIE
 | 
				
			||||||
from .condenast import CondeNastIE
 | 
					from .condenast import CondeNastIE
 | 
				
			||||||
from .cracked import CrackedIE
 | 
					from .cracked import CrackedIE
 | 
				
			||||||
from .criterion import CriterionIE
 | 
					from .criterion import CriterionIE
 | 
				
			||||||
@@ -75,6 +94,7 @@ from .crunchyroll import (
 | 
				
			|||||||
    CrunchyrollShowPlaylistIE
 | 
					    CrunchyrollShowPlaylistIE
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from .cspan import CSpanIE
 | 
					from .cspan import CSpanIE
 | 
				
			||||||
 | 
					from .ctsnews import CtsNewsIE
 | 
				
			||||||
from .dailymotion import (
 | 
					from .dailymotion import (
 | 
				
			||||||
    DailymotionIE,
 | 
					    DailymotionIE,
 | 
				
			||||||
    DailymotionPlaylistIE,
 | 
					    DailymotionPlaylistIE,
 | 
				
			||||||
@@ -82,18 +102,22 @@ from .dailymotion import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
from .daum import DaumIE
 | 
					from .daum import DaumIE
 | 
				
			||||||
from .dbtv import DBTVIE
 | 
					from .dbtv import DBTVIE
 | 
				
			||||||
 | 
					from .dctp import DctpTvIE
 | 
				
			||||||
from .deezer import DeezerPlaylistIE
 | 
					from .deezer import DeezerPlaylistIE
 | 
				
			||||||
from .dfb import DFBIE
 | 
					from .dfb import DFBIE
 | 
				
			||||||
from .dotsub import DotsubIE
 | 
					from .dotsub import DotsubIE
 | 
				
			||||||
from .dreisat import DreiSatIE
 | 
					from .dreisat import DreiSatIE
 | 
				
			||||||
 | 
					from .drbonanza import DRBonanzaIE
 | 
				
			||||||
from .drtuber import DrTuberIE
 | 
					from .drtuber import DrTuberIE
 | 
				
			||||||
from .drtv import DRTVIE
 | 
					from .drtv import DRTVIE
 | 
				
			||||||
 | 
					from .dvtv import DVTVIE
 | 
				
			||||||
from .dump import DumpIE
 | 
					from .dump import DumpIE
 | 
				
			||||||
from .defense import DefenseGouvFrIE
 | 
					from .defense import DefenseGouvFrIE
 | 
				
			||||||
from .discovery import DiscoveryIE
 | 
					from .discovery import DiscoveryIE
 | 
				
			||||||
from .divxstage import DivxStageIE
 | 
					from .divxstage import DivxStageIE
 | 
				
			||||||
from .dropbox import DropboxIE
 | 
					from .dropbox import DropboxIE
 | 
				
			||||||
from .ebaumsworld import EbaumsWorldIE
 | 
					from .ebaumsworld import EbaumsWorldIE
 | 
				
			||||||
 | 
					from .echomsk import EchoMskIE
 | 
				
			||||||
from .ehow import EHowIE
 | 
					from .ehow import EHowIE
 | 
				
			||||||
from .eighttracks import EightTracksIE
 | 
					from .eighttracks import EightTracksIE
 | 
				
			||||||
from .einthusan import EinthusanIE
 | 
					from .einthusan import EinthusanIE
 | 
				
			||||||
@@ -103,9 +127,11 @@ from .ellentv import (
 | 
				
			|||||||
    EllenTVClipsIE,
 | 
					    EllenTVClipsIE,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from .elpais import ElPaisIE
 | 
					from .elpais import ElPaisIE
 | 
				
			||||||
 | 
					from .embedly import EmbedlyIE
 | 
				
			||||||
from .empflix import EMPFlixIE
 | 
					from .empflix import EMPFlixIE
 | 
				
			||||||
from .engadget import EngadgetIE
 | 
					from .engadget import EngadgetIE
 | 
				
			||||||
from .eporner import EpornerIE
 | 
					from .eporner import EpornerIE
 | 
				
			||||||
 | 
					from .eroprofile import EroProfileIE
 | 
				
			||||||
from .escapist import EscapistIE
 | 
					from .escapist import EscapistIE
 | 
				
			||||||
from .everyonesmixtape import EveryonesMixtapeIE
 | 
					from .everyonesmixtape import EveryonesMixtapeIE
 | 
				
			||||||
from .exfm import ExfmIE
 | 
					from .exfm import ExfmIE
 | 
				
			||||||
@@ -151,6 +177,7 @@ from .gametrailers import GametrailersIE
 | 
				
			|||||||
from .gdcvault import GDCVaultIE
 | 
					from .gdcvault import GDCVaultIE
 | 
				
			||||||
from .generic import GenericIE
 | 
					from .generic import GenericIE
 | 
				
			||||||
from .giantbomb import GiantBombIE
 | 
					from .giantbomb import GiantBombIE
 | 
				
			||||||
 | 
					from .giga import GigaIE
 | 
				
			||||||
from .glide import GlideIE
 | 
					from .glide import GlideIE
 | 
				
			||||||
from .globo import GloboIE
 | 
					from .globo import GloboIE
 | 
				
			||||||
from .godtube import GodTubeIE
 | 
					from .godtube import GodTubeIE
 | 
				
			||||||
@@ -163,9 +190,14 @@ from .goshgay import GoshgayIE
 | 
				
			|||||||
from .grooveshark import GroovesharkIE
 | 
					from .grooveshark import GroovesharkIE
 | 
				
			||||||
from .groupon import GrouponIE
 | 
					from .groupon import GrouponIE
 | 
				
			||||||
from .hark import HarkIE
 | 
					from .hark import HarkIE
 | 
				
			||||||
 | 
					from .hearthisat import HearThisAtIE
 | 
				
			||||||
from .heise import HeiseIE
 | 
					from .heise import HeiseIE
 | 
				
			||||||
 | 
					from .hellporno import HellPornoIE
 | 
				
			||||||
from .helsinki import HelsinkiIE
 | 
					from .helsinki import HelsinkiIE
 | 
				
			||||||
from .hentaistigma import HentaiStigmaIE
 | 
					from .hentaistigma import HentaiStigmaIE
 | 
				
			||||||
 | 
					from .historicfilms import HistoricFilmsIE
 | 
				
			||||||
 | 
					from .history import HistoryIE
 | 
				
			||||||
 | 
					from .hitbox import HitboxIE, HitboxLiveIE
 | 
				
			||||||
from .hornbunny import HornBunnyIE
 | 
					from .hornbunny import HornBunnyIE
 | 
				
			||||||
from .hostingbulk import HostingBulkIE
 | 
					from .hostingbulk import HostingBulkIE
 | 
				
			||||||
from .hotnewhiphop import HotNewHipHopIE
 | 
					from .hotnewhiphop import HotNewHipHopIE
 | 
				
			||||||
@@ -179,6 +211,7 @@ from .imdb import (
 | 
				
			|||||||
    ImdbIE,
 | 
					    ImdbIE,
 | 
				
			||||||
    ImdbListIE
 | 
					    ImdbListIE
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					from .imgur import ImgurIE
 | 
				
			||||||
from .ina import InaIE
 | 
					from .ina import InaIE
 | 
				
			||||||
from .infoq import InfoQIE
 | 
					from .infoq import InfoQIE
 | 
				
			||||||
from .instagram import InstagramIE, InstagramUserIE
 | 
					from .instagram import InstagramIE, InstagramUserIE
 | 
				
			||||||
@@ -194,7 +227,9 @@ from .jeuxvideo import JeuxVideoIE
 | 
				
			|||||||
from .jove import JoveIE
 | 
					from .jove import JoveIE
 | 
				
			||||||
from .jukebox import JukeboxIE
 | 
					from .jukebox import JukeboxIE
 | 
				
			||||||
from .jpopsukitv import JpopsukiIE
 | 
					from .jpopsukitv import JpopsukiIE
 | 
				
			||||||
 | 
					from .kaltura import KalturaIE
 | 
				
			||||||
from .kankan import KankanIE
 | 
					from .kankan import KankanIE
 | 
				
			||||||
 | 
					from .karaoketv import KaraoketvIE
 | 
				
			||||||
from .keezmovies import KeezMoviesIE
 | 
					from .keezmovies import KeezMoviesIE
 | 
				
			||||||
from .khanacademy import KhanAcademyIE
 | 
					from .khanacademy import KhanAcademyIE
 | 
				
			||||||
from .kickstarter import KickStarterIE
 | 
					from .kickstarter import KickStarterIE
 | 
				
			||||||
@@ -204,6 +239,11 @@ from .krasview import KrasViewIE
 | 
				
			|||||||
from .ku6 import Ku6IE
 | 
					from .ku6 import Ku6IE
 | 
				
			||||||
from .la7 import LA7IE
 | 
					from .la7 import LA7IE
 | 
				
			||||||
from .laola1tv import Laola1TvIE
 | 
					from .laola1tv import Laola1TvIE
 | 
				
			||||||
 | 
					from .letv import (
 | 
				
			||||||
 | 
					    LetvIE,
 | 
				
			||||||
 | 
					    LetvTvIE,
 | 
				
			||||||
 | 
					    LetvPlaylistIE
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
from .lifenews import LifeNewsIE
 | 
					from .lifenews import LifeNewsIE
 | 
				
			||||||
from .liveleak import LiveLeakIE
 | 
					from .liveleak import LiveLeakIE
 | 
				
			||||||
from .livestream import (
 | 
					from .livestream import (
 | 
				
			||||||
@@ -211,6 +251,7 @@ from .livestream import (
 | 
				
			|||||||
    LivestreamOriginalIE,
 | 
					    LivestreamOriginalIE,
 | 
				
			||||||
    LivestreamShortenerIE,
 | 
					    LivestreamShortenerIE,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					from .lnkgo import LnkGoIE
 | 
				
			||||||
from .lrt import LRTIE
 | 
					from .lrt import LRTIE
 | 
				
			||||||
from .lynda import (
 | 
					from .lynda import (
 | 
				
			||||||
    LyndaIE,
 | 
					    LyndaIE,
 | 
				
			||||||
@@ -255,6 +296,7 @@ from .myspace import MySpaceIE, MySpaceAlbumIE
 | 
				
			|||||||
from .myspass import MySpassIE
 | 
					from .myspass import MySpassIE
 | 
				
			||||||
from .myvideo import MyVideoIE
 | 
					from .myvideo import MyVideoIE
 | 
				
			||||||
from .myvidster import MyVidsterIE
 | 
					from .myvidster import MyVidsterIE
 | 
				
			||||||
 | 
					from .nationalgeographic import NationalGeographicIE
 | 
				
			||||||
from .naver import NaverIE
 | 
					from .naver import NaverIE
 | 
				
			||||||
from .nba import NBAIE
 | 
					from .nba import NBAIE
 | 
				
			||||||
from .nbc import (
 | 
					from .nbc import (
 | 
				
			||||||
@@ -263,11 +305,24 @@ from .nbc import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
from .ndr import NDRIE
 | 
					from .ndr import NDRIE
 | 
				
			||||||
from .ndtv import NDTVIE
 | 
					from .ndtv import NDTVIE
 | 
				
			||||||
 | 
					from .netzkino import NetzkinoIE
 | 
				
			||||||
 | 
					from .nerdcubed import NerdCubedFeedIE
 | 
				
			||||||
 | 
					from .nerdist import NerdistIE
 | 
				
			||||||
from .newgrounds import NewgroundsIE
 | 
					from .newgrounds import NewgroundsIE
 | 
				
			||||||
from .newstube import NewstubeIE
 | 
					from .newstube import NewstubeIE
 | 
				
			||||||
 | 
					from .nextmedia import (
 | 
				
			||||||
 | 
					    NextMediaIE,
 | 
				
			||||||
 | 
					    NextMediaActionNewsIE,
 | 
				
			||||||
 | 
					    AppleDailyRealtimeNewsIE,
 | 
				
			||||||
 | 
					    AppleDailyAnimationNewsIE
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
from .nfb import NFBIE
 | 
					from .nfb import NFBIE
 | 
				
			||||||
from .nfl import NFLIE
 | 
					from .nfl import NFLIE
 | 
				
			||||||
from .nhl import NHLIE, NHLVideocenterIE
 | 
					from .nhl import (
 | 
				
			||||||
 | 
					    NHLIE,
 | 
				
			||||||
 | 
					    NHLNewsIE,
 | 
				
			||||||
 | 
					    NHLVideocenterIE,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
from .niconico import NiconicoIE, NiconicoPlaylistIE
 | 
					from .niconico import NiconicoIE, NiconicoPlaylistIE
 | 
				
			||||||
from .ninegag import NineGagIE
 | 
					from .ninegag import NineGagIE
 | 
				
			||||||
from .noco import NocoIE
 | 
					from .noco import NocoIE
 | 
				
			||||||
@@ -278,17 +333,23 @@ from .nowness import NownessIE
 | 
				
			|||||||
from .nowvideo import NowVideoIE
 | 
					from .nowvideo import NowVideoIE
 | 
				
			||||||
from .npo import (
 | 
					from .npo import (
 | 
				
			||||||
    NPOIE,
 | 
					    NPOIE,
 | 
				
			||||||
 | 
					    NPOLiveIE,
 | 
				
			||||||
 | 
					    NPORadioIE,
 | 
				
			||||||
 | 
					    NPORadioFragmentIE,
 | 
				
			||||||
    TegenlichtVproIE,
 | 
					    TegenlichtVproIE,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from .nrk import (
 | 
					from .nrk import (
 | 
				
			||||||
    NRKIE,
 | 
					    NRKIE,
 | 
				
			||||||
    NRKTVIE,
 | 
					    NRKTVIE,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from .ntv import NTVIE
 | 
					from .ntvde import NTVDeIE
 | 
				
			||||||
 | 
					from .ntvru import NTVRuIE
 | 
				
			||||||
from .nytimes import NYTimesIE
 | 
					from .nytimes import NYTimesIE
 | 
				
			||||||
from .nuvid import NuvidIE
 | 
					from .nuvid import NuvidIE
 | 
				
			||||||
 | 
					from .odnoklassniki import OdnoklassnikiIE
 | 
				
			||||||
from .oktoberfesttv import OktoberfestTVIE
 | 
					from .oktoberfesttv import OktoberfestTVIE
 | 
				
			||||||
from .ooyala import OoyalaIE
 | 
					from .ooyala import OoyalaIE
 | 
				
			||||||
 | 
					from .openfilm import OpenFilmIE
 | 
				
			||||||
from .orf import (
 | 
					from .orf import (
 | 
				
			||||||
    ORFTVthekIE,
 | 
					    ORFTVthekIE,
 | 
				
			||||||
    ORFOE1IE,
 | 
					    ORFOE1IE,
 | 
				
			||||||
@@ -305,14 +366,20 @@ from .playfm import PlayFMIE
 | 
				
			|||||||
from .playvid import PlayvidIE
 | 
					from .playvid import PlayvidIE
 | 
				
			||||||
from .podomatic import PodomaticIE
 | 
					from .podomatic import PodomaticIE
 | 
				
			||||||
from .pornhd import PornHdIE
 | 
					from .pornhd import PornHdIE
 | 
				
			||||||
from .pornhub import PornHubIE
 | 
					from .pornhub import (
 | 
				
			||||||
 | 
					    PornHubIE,
 | 
				
			||||||
 | 
					    PornHubPlaylistIE,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
from .pornotube import PornotubeIE
 | 
					from .pornotube import PornotubeIE
 | 
				
			||||||
from .pornoxo import PornoXOIE
 | 
					from .pornoxo import PornoXOIE
 | 
				
			||||||
from .promptfile import PromptFileIE
 | 
					from .promptfile import PromptFileIE
 | 
				
			||||||
from .prosiebensat1 import ProSiebenSat1IE
 | 
					from .prosiebensat1 import ProSiebenSat1IE
 | 
				
			||||||
 | 
					from .puls4 import Puls4IE
 | 
				
			||||||
from .pyvideo import PyvideoIE
 | 
					from .pyvideo import PyvideoIE
 | 
				
			||||||
from .quickvid import QuickVidIE
 | 
					from .quickvid import QuickVidIE
 | 
				
			||||||
 | 
					from .r7 import R7IE
 | 
				
			||||||
from .radiode import RadioDeIE
 | 
					from .radiode import RadioDeIE
 | 
				
			||||||
 | 
					from .radiobremen import RadioBremenIE
 | 
				
			||||||
from .radiofrance import RadioFranceIE
 | 
					from .radiofrance import RadioFranceIE
 | 
				
			||||||
from .rai import RaiIE
 | 
					from .rai import RaiIE
 | 
				
			||||||
from .rbmaradio import RBMARadioIE
 | 
					from .rbmaradio import RBMARadioIE
 | 
				
			||||||
@@ -324,8 +391,10 @@ from .ro220 import Ro220IE
 | 
				
			|||||||
from .rottentomatoes import RottenTomatoesIE
 | 
					from .rottentomatoes import RottenTomatoesIE
 | 
				
			||||||
from .roxwel import RoxwelIE
 | 
					from .roxwel import RoxwelIE
 | 
				
			||||||
from .rtbf import RTBFIE
 | 
					from .rtbf import RTBFIE
 | 
				
			||||||
from .rtlnl import RtlXlIE
 | 
					from .rte import RteIE
 | 
				
			||||||
 | 
					from .rtlnl import RtlNlIE
 | 
				
			||||||
from .rtlnow import RTLnowIE
 | 
					from .rtlnow import RTLnowIE
 | 
				
			||||||
 | 
					from .rtl2 import RTL2IE
 | 
				
			||||||
from .rtp import RTPIE
 | 
					from .rtp import RTPIE
 | 
				
			||||||
from .rts import RTSIE
 | 
					from .rts import RTSIE
 | 
				
			||||||
from .rtve import RTVEALaCartaIE, RTVELiveIE
 | 
					from .rtve import RTVEALaCartaIE, RTVELiveIE
 | 
				
			||||||
@@ -333,15 +402,18 @@ from .ruhd import RUHDIE
 | 
				
			|||||||
from .rutube import (
 | 
					from .rutube import (
 | 
				
			||||||
    RutubeIE,
 | 
					    RutubeIE,
 | 
				
			||||||
    RutubeChannelIE,
 | 
					    RutubeChannelIE,
 | 
				
			||||||
 | 
					    RutubeEmbedIE,
 | 
				
			||||||
    RutubeMovieIE,
 | 
					    RutubeMovieIE,
 | 
				
			||||||
    RutubePersonIE,
 | 
					    RutubePersonIE,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from .rutv import RUTVIE
 | 
					from .rutv import RUTVIE
 | 
				
			||||||
 | 
					from .sandia import SandiaIE
 | 
				
			||||||
from .sapo import SapoIE
 | 
					from .sapo import SapoIE
 | 
				
			||||||
from .savefrom import SaveFromIE
 | 
					from .savefrom import SaveFromIE
 | 
				
			||||||
from .sbs import SBSIE
 | 
					from .sbs import SBSIE
 | 
				
			||||||
from .scivee import SciVeeIE
 | 
					from .scivee import SciVeeIE
 | 
				
			||||||
from .screencast import ScreencastIE
 | 
					from .screencast import ScreencastIE
 | 
				
			||||||
 | 
					from .screencastomatic import ScreencastOMaticIE
 | 
				
			||||||
from .screenwavemedia import CinemassacreIE, ScreenwaveMediaIE, TeamFourIE
 | 
					from .screenwavemedia import CinemassacreIE, ScreenwaveMediaIE, TeamFourIE
 | 
				
			||||||
from .servingsys import ServingSysIE
 | 
					from .servingsys import ServingSysIE
 | 
				
			||||||
from .sexu import SexuIE
 | 
					from .sexu import SexuIE
 | 
				
			||||||
@@ -366,7 +438,10 @@ from .soundcloud import (
 | 
				
			|||||||
    SoundcloudUserIE,
 | 
					    SoundcloudUserIE,
 | 
				
			||||||
    SoundcloudPlaylistIE
 | 
					    SoundcloudPlaylistIE
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from .soundgasm import SoundgasmIE
 | 
					from .soundgasm import (
 | 
				
			||||||
 | 
					    SoundgasmIE,
 | 
				
			||||||
 | 
					    SoundgasmProfileIE
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
from .southpark import (
 | 
					from .southpark import (
 | 
				
			||||||
    SouthParkIE,
 | 
					    SouthParkIE,
 | 
				
			||||||
    SouthparkDeIE,
 | 
					    SouthparkDeIE,
 | 
				
			||||||
@@ -384,7 +459,9 @@ from .stanfordoc import StanfordOpenClassroomIE
 | 
				
			|||||||
from .steam import SteamIE
 | 
					from .steam import SteamIE
 | 
				
			||||||
from .streamcloud import StreamcloudIE
 | 
					from .streamcloud import StreamcloudIE
 | 
				
			||||||
from .streamcz import StreamCZIE
 | 
					from .streamcz import StreamCZIE
 | 
				
			||||||
 | 
					from .streetvoice import StreetVoiceIE
 | 
				
			||||||
from .sunporno import SunPornoIE
 | 
					from .sunporno import SunPornoIE
 | 
				
			||||||
 | 
					from .svtplay import SVTPlayIE
 | 
				
			||||||
from .swrmediathek import SWRMediathekIE
 | 
					from .swrmediathek import SWRMediathekIE
 | 
				
			||||||
from .syfy import SyfyIE
 | 
					from .syfy import SyfyIE
 | 
				
			||||||
from .sztvhu import SztvHuIE
 | 
					from .sztvhu import SztvHuIE
 | 
				
			||||||
@@ -402,8 +479,10 @@ from .ted import TEDIE
 | 
				
			|||||||
from .telebruxelles import TeleBruxellesIE
 | 
					from .telebruxelles import TeleBruxellesIE
 | 
				
			||||||
from .telecinco import TelecincoIE
 | 
					from .telecinco import TelecincoIE
 | 
				
			||||||
from .telemb import TeleMBIE
 | 
					from .telemb import TeleMBIE
 | 
				
			||||||
 | 
					from .teletask import TeleTaskIE
 | 
				
			||||||
from .tenplay import TenPlayIE
 | 
					from .tenplay import TenPlayIE
 | 
				
			||||||
from .testurl import TestURLIE
 | 
					from .testurl import TestURLIE
 | 
				
			||||||
 | 
					from .testtube import TestTubeIE
 | 
				
			||||||
from .tf1 import TF1IE
 | 
					from .tf1 import TF1IE
 | 
				
			||||||
from .theonion import TheOnionIE
 | 
					from .theonion import TheOnionIE
 | 
				
			||||||
from .theplatform import ThePlatformIE
 | 
					from .theplatform import ThePlatformIE
 | 
				
			||||||
@@ -428,11 +507,21 @@ from .tumblr import TumblrIE
 | 
				
			|||||||
from .tunein import TuneInIE
 | 
					from .tunein import TuneInIE
 | 
				
			||||||
from .turbo import TurboIE
 | 
					from .turbo import TurboIE
 | 
				
			||||||
from .tutv import TutvIE
 | 
					from .tutv import TutvIE
 | 
				
			||||||
 | 
					from .tv4 import TV4IE
 | 
				
			||||||
from .tvigle import TvigleIE
 | 
					from .tvigle import TvigleIE
 | 
				
			||||||
from .tvp import TvpIE
 | 
					from .tvp import TvpIE, TvpSeriesIE
 | 
				
			||||||
from .tvplay import TVPlayIE
 | 
					from .tvplay import TVPlayIE
 | 
				
			||||||
 | 
					from .tweakers import TweakersIE
 | 
				
			||||||
from .twentyfourvideo import TwentyFourVideoIE
 | 
					from .twentyfourvideo import TwentyFourVideoIE
 | 
				
			||||||
from .twitch import TwitchIE
 | 
					from .twitch import (
 | 
				
			||||||
 | 
					    TwitchVideoIE,
 | 
				
			||||||
 | 
					    TwitchChapterIE,
 | 
				
			||||||
 | 
					    TwitchVodIE,
 | 
				
			||||||
 | 
					    TwitchProfileIE,
 | 
				
			||||||
 | 
					    TwitchPastBroadcastsIE,
 | 
				
			||||||
 | 
					    TwitchBookmarksIE,
 | 
				
			||||||
 | 
					    TwitchStreamIE,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
from .ubu import UbuIE
 | 
					from .ubu import UbuIE
 | 
				
			||||||
from .udemy import (
 | 
					from .udemy import (
 | 
				
			||||||
    UdemyIE,
 | 
					    UdemyIE,
 | 
				
			||||||
@@ -460,6 +549,7 @@ from .videott import VideoTtIE
 | 
				
			|||||||
from .videoweed import VideoWeedIE
 | 
					from .videoweed import VideoWeedIE
 | 
				
			||||||
from .vidme import VidmeIE
 | 
					from .vidme import VidmeIE
 | 
				
			||||||
from .vidzi import VidziIE
 | 
					from .vidzi import VidziIE
 | 
				
			||||||
 | 
					from .vier import VierIE, VierVideosIE
 | 
				
			||||||
from .vimeo import (
 | 
					from .vimeo import (
 | 
				
			||||||
    VimeoIE,
 | 
					    VimeoIE,
 | 
				
			||||||
    VimeoAlbumIE,
 | 
					    VimeoAlbumIE,
 | 
				
			||||||
@@ -495,11 +585,13 @@ from .wdr import (
 | 
				
			|||||||
    WDRMobileIE,
 | 
					    WDRMobileIE,
 | 
				
			||||||
    WDRMausIE,
 | 
					    WDRMausIE,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					from .webofstories import WebOfStoriesIE
 | 
				
			||||||
from .weibo import WeiboIE
 | 
					from .weibo import WeiboIE
 | 
				
			||||||
from .wimp import WimpIE
 | 
					from .wimp import WimpIE
 | 
				
			||||||
from .wistia import WistiaIE
 | 
					from .wistia import WistiaIE
 | 
				
			||||||
from .worldstarhiphop import WorldStarHipHopIE
 | 
					from .worldstarhiphop import WorldStarHipHopIE
 | 
				
			||||||
from .wrzuta import WrzutaIE
 | 
					from .wrzuta import WrzutaIE
 | 
				
			||||||
 | 
					from .wsj import WSJIE
 | 
				
			||||||
from .xbef import XBefIE
 | 
					from .xbef import XBefIE
 | 
				
			||||||
from .xboxclips import XboxClipsIE
 | 
					from .xboxclips import XboxClipsIE
 | 
				
			||||||
from .xhamster import XHamsterIE
 | 
					from .xhamster import XHamsterIE
 | 
				
			||||||
@@ -507,10 +599,13 @@ from .xminus import XMinusIE
 | 
				
			|||||||
from .xnxx import XNXXIE
 | 
					from .xnxx import XNXXIE
 | 
				
			||||||
from .xvideos import XVideosIE
 | 
					from .xvideos import XVideosIE
 | 
				
			||||||
from .xtube import XTubeUserIE, XTubeIE
 | 
					from .xtube import XTubeUserIE, XTubeIE
 | 
				
			||||||
 | 
					from .xuite import XuiteIE
 | 
				
			||||||
 | 
					from .xxxymovies import XXXYMoviesIE
 | 
				
			||||||
from .yahoo import (
 | 
					from .yahoo import (
 | 
				
			||||||
    YahooIE,
 | 
					    YahooIE,
 | 
				
			||||||
    YahooSearchIE,
 | 
					    YahooSearchIE,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					from .yam import YamIE
 | 
				
			||||||
from .yesjapan import YesJapanIE
 | 
					from .yesjapan import YesJapanIE
 | 
				
			||||||
from .ynet import YnetIE
 | 
					from .ynet import YnetIE
 | 
				
			||||||
from .youjizz import YouJizzIE
 | 
					from .youjizz import YouJizzIE
 | 
				
			||||||
@@ -529,11 +624,12 @@ from .youtube import (
 | 
				
			|||||||
    YoutubeSearchURLIE,
 | 
					    YoutubeSearchURLIE,
 | 
				
			||||||
    YoutubeShowIE,
 | 
					    YoutubeShowIE,
 | 
				
			||||||
    YoutubeSubscriptionsIE,
 | 
					    YoutubeSubscriptionsIE,
 | 
				
			||||||
    YoutubeTopListIE,
 | 
					    YoutubeTruncatedIDIE,
 | 
				
			||||||
    YoutubeTruncatedURLIE,
 | 
					    YoutubeTruncatedURLIE,
 | 
				
			||||||
    YoutubeUserIE,
 | 
					    YoutubeUserIE,
 | 
				
			||||||
    YoutubeWatchLaterIE,
 | 
					    YoutubeWatchLaterIE,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					from .zapiks import ZapiksIE
 | 
				
			||||||
from .zdf import ZDFIE, ZDFChannelIE
 | 
					from .zdf import ZDFIE, ZDFChannelIE
 | 
				
			||||||
from .zingmp3 import (
 | 
					from .zingmp3 import (
 | 
				
			||||||
    ZingMp3SongIE,
 | 
					    ZingMp3SongIE,
 | 
				
			||||||
@@ -555,6 +651,17 @@ def gen_extractors():
 | 
				
			|||||||
    return [klass() for klass in _ALL_CLASSES]
 | 
					    return [klass() for klass in _ALL_CLASSES]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def list_extractors(age_limit):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Return a list of extractors that are suitable for the given age,
 | 
				
			||||||
 | 
					    sorted by extractor ID.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return sorted(
 | 
				
			||||||
 | 
					        filter(lambda ie: ie.is_suitable(age_limit), gen_extractors()),
 | 
				
			||||||
 | 
					        key=lambda ie: ie.IE_NAME.lower())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_info_extractor(ie_name):
 | 
					def get_info_extractor(ie_name):
 | 
				
			||||||
    """Returns the info extractor class with the given ie_name"""
 | 
					    """Returns the info extractor class with the given ie_name"""
 | 
				
			||||||
    return globals()[ie_name + 'IE']
 | 
					    return globals()[ie_name + 'IE']
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										68
									
								
								youtube_dl/extractor/abc7news.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								youtube_dl/extractor/abc7news.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,68 @@
 | 
				
			|||||||
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import re
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .common import InfoExtractor
 | 
				
			||||||
 | 
					from ..utils import parse_iso8601
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Abc7NewsIE(InfoExtractor):
 | 
				
			||||||
 | 
					    _VALID_URL = r'https?://abc7news\.com(?:/[^/]+/(?P<display_id>[^/]+))?/(?P<id>\d+)'
 | 
				
			||||||
 | 
					    _TESTS = [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            'url': 'http://abc7news.com/entertainment/east-bay-museum-celebrates-vintage-synthesizers/472581/',
 | 
				
			||||||
 | 
					            'info_dict': {
 | 
				
			||||||
 | 
					                'id': '472581',
 | 
				
			||||||
 | 
					                'display_id': 'east-bay-museum-celebrates-vintage-synthesizers',
 | 
				
			||||||
 | 
					                'ext': 'mp4',
 | 
				
			||||||
 | 
					                'title': 'East Bay museum celebrates history of synthesized music',
 | 
				
			||||||
 | 
					                'description': 'md5:a4f10fb2f2a02565c1749d4adbab4b10',
 | 
				
			||||||
 | 
					                'thumbnail': 're:^https?://.*\.jpg$',
 | 
				
			||||||
 | 
					                'timestamp': 1421123075,
 | 
				
			||||||
 | 
					                'upload_date': '20150113',
 | 
				
			||||||
 | 
					                'uploader': 'Jonathan Bloom',
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            'params': {
 | 
				
			||||||
 | 
					                # m3u8 download
 | 
				
			||||||
 | 
					                'skip_download': True,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            'url': 'http://abc7news.com/472581',
 | 
				
			||||||
 | 
					            'only_matching': True,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
 | 
					        mobj = re.match(self._VALID_URL, url)
 | 
				
			||||||
 | 
					        video_id = mobj.group('id')
 | 
				
			||||||
 | 
					        display_id = mobj.group('display_id') or video_id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        webpage = self._download_webpage(url, display_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        m3u8 = self._html_search_meta(
 | 
				
			||||||
 | 
					            'contentURL', webpage, 'm3u8 url', fatal=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        formats = self._extract_m3u8_formats(m3u8, display_id, 'mp4')
 | 
				
			||||||
 | 
					        self._sort_formats(formats)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        title = self._og_search_title(webpage).strip()
 | 
				
			||||||
 | 
					        description = self._og_search_description(webpage).strip()
 | 
				
			||||||
 | 
					        thumbnail = self._og_search_thumbnail(webpage)
 | 
				
			||||||
 | 
					        timestamp = parse_iso8601(self._search_regex(
 | 
				
			||||||
 | 
					            r'<div class="meta">\s*<time class="timeago" datetime="([^"]+)">',
 | 
				
			||||||
 | 
					            webpage, 'upload date', fatal=False))
 | 
				
			||||||
 | 
					        uploader = self._search_regex(
 | 
				
			||||||
 | 
					            r'rel="author">([^<]+)</a>',
 | 
				
			||||||
 | 
					            webpage, 'uploader', default=None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            'id': video_id,
 | 
				
			||||||
 | 
					            'display_id': display_id,
 | 
				
			||||||
 | 
					            'title': title,
 | 
				
			||||||
 | 
					            'description': description,
 | 
				
			||||||
 | 
					            'thumbnail': thumbnail,
 | 
				
			||||||
 | 
					            'timestamp': timestamp,
 | 
				
			||||||
 | 
					            'uploader': uploader,
 | 
				
			||||||
 | 
					            'formats': formats,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
							
								
								
									
										71
									
								
								youtube_dl/extractor/adobetv.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								youtube_dl/extractor/adobetv.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,71 @@
 | 
				
			|||||||
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .common import InfoExtractor
 | 
				
			||||||
 | 
					from ..utils import (
 | 
				
			||||||
 | 
					    parse_duration,
 | 
				
			||||||
 | 
					    unified_strdate,
 | 
				
			||||||
 | 
					    str_to_int,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AdobeTVIE(InfoExtractor):
 | 
				
			||||||
 | 
					    _VALID_URL = r'https?://tv\.adobe\.com/watch/[^/]+/(?P<id>[^/]+)'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    _TEST = {
 | 
				
			||||||
 | 
					        'url': 'http://tv.adobe.com/watch/the-complete-picture-with-julieanne-kost/quick-tip-how-to-draw-a-circle-around-an-object-in-photoshop/',
 | 
				
			||||||
 | 
					        'md5': '9bc5727bcdd55251f35ad311ca74fa1e',
 | 
				
			||||||
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'id': 'quick-tip-how-to-draw-a-circle-around-an-object-in-photoshop',
 | 
				
			||||||
 | 
					            'ext': 'mp4',
 | 
				
			||||||
 | 
					            'title': 'Quick Tip - How to Draw a Circle Around an Object in Photoshop',
 | 
				
			||||||
 | 
					            'description': 'md5:99ec318dc909d7ba2a1f2b038f7d2311',
 | 
				
			||||||
 | 
					            'thumbnail': 're:https?://.*\.jpg$',
 | 
				
			||||||
 | 
					            'upload_date': '20110914',
 | 
				
			||||||
 | 
					            'duration': 60,
 | 
				
			||||||
 | 
					            'view_count': int,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
 | 
					        video_id = self._match_id(url)
 | 
				
			||||||
 | 
					        webpage = self._download_webpage(url, video_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        player = self._parse_json(
 | 
				
			||||||
 | 
					            self._search_regex(r'html5player:\s*({.+?})\s*\n', webpage, 'player'),
 | 
				
			||||||
 | 
					            video_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        title = player.get('title') or self._search_regex(
 | 
				
			||||||
 | 
					            r'data-title="([^"]+)"', webpage, 'title')
 | 
				
			||||||
 | 
					        description = self._og_search_description(webpage)
 | 
				
			||||||
 | 
					        thumbnail = self._og_search_thumbnail(webpage)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        upload_date = unified_strdate(
 | 
				
			||||||
 | 
					            self._html_search_meta('datepublished', webpage, 'upload date'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        duration = parse_duration(
 | 
				
			||||||
 | 
					            self._html_search_meta('duration', webpage, 'duration') or
 | 
				
			||||||
 | 
					            self._search_regex(
 | 
				
			||||||
 | 
					                r'Runtime:\s*(\d{2}:\d{2}:\d{2})',
 | 
				
			||||||
 | 
					                webpage, 'duration', fatal=False))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        view_count = str_to_int(self._search_regex(
 | 
				
			||||||
 | 
					            r'<div class="views">\s*Views?:\s*([\d,.]+)\s*</div>',
 | 
				
			||||||
 | 
					            webpage, 'view count'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        formats = [{
 | 
				
			||||||
 | 
					            'url': source['src'],
 | 
				
			||||||
 | 
					            'format_id': source.get('quality') or source['src'].split('-')[-1].split('.')[0] or None,
 | 
				
			||||||
 | 
					            'tbr': source.get('bitrate'),
 | 
				
			||||||
 | 
					        } for source in player['sources']]
 | 
				
			||||||
 | 
					        self._sort_formats(formats)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            'id': video_id,
 | 
				
			||||||
 | 
					            'title': title,
 | 
				
			||||||
 | 
					            'description': description,
 | 
				
			||||||
 | 
					            'thumbnail': thumbnail,
 | 
				
			||||||
 | 
					            'upload_date': upload_date,
 | 
				
			||||||
 | 
					            'duration': duration,
 | 
				
			||||||
 | 
					            'view_count': view_count,
 | 
				
			||||||
 | 
					            'formats': formats,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
@@ -38,6 +38,7 @@ class AdultSwimIE(InfoExtractor):
 | 
				
			|||||||
            },
 | 
					            },
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
        'info_dict': {
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'id': 'rQxZvXQ4ROaSOqq-or2Mow',
 | 
				
			||||||
            'title': 'Rick and Morty - Pilot',
 | 
					            'title': 'Rick and Morty - Pilot',
 | 
				
			||||||
            'description': "Rick moves in with his daughter's family and establishes himself as a bad influence on his grandson, Morty. "
 | 
					            'description': "Rick moves in with his daughter's family and establishes himself as a bad influence on his grandson, Morty. "
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -55,6 +56,7 @@ class AdultSwimIE(InfoExtractor):
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
        'info_dict': {
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'id': '-t8CamQlQ2aYZ49ItZCFog',
 | 
				
			||||||
            'title': 'American Dad - Putting Francine Out of Business',
 | 
					            'title': 'American Dad - Putting Francine Out of Business',
 | 
				
			||||||
            'description': 'Stan hatches a plan to get Francine out of the real estate business.Watch more American Dad on [adult swim].'
 | 
					            'description': 'Stan hatches a plan to get Francine out of the real estate business.Watch more American Dad on [adult swim].'
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										103
									
								
								youtube_dl/extractor/aftenposten.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								youtube_dl/extractor/aftenposten.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,103 @@
 | 
				
			|||||||
 | 
					# coding: utf-8
 | 
				
			||||||
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import re
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .common import InfoExtractor
 | 
				
			||||||
 | 
					from ..utils import (
 | 
				
			||||||
 | 
					    int_or_none,
 | 
				
			||||||
 | 
					    parse_iso8601,
 | 
				
			||||||
 | 
					    xpath_with_ns,
 | 
				
			||||||
 | 
					    xpath_text,
 | 
				
			||||||
 | 
					    find_xpath_attr,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AftenpostenIE(InfoExtractor):
 | 
				
			||||||
 | 
					    _VALID_URL = r'https?://(?:www\.)?aftenposten\.no/webtv/([^/]+/)*(?P<id>[^/]+)-\d+\.html'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    _TEST = {
 | 
				
			||||||
 | 
					        'url': 'http://www.aftenposten.no/webtv/serier-og-programmer/sweatshopenglish/TRAILER-SWEATSHOP---I-cant-take-any-more-7800835.html?paging=§ion=webtv_serierogprogrammer_sweatshop_sweatshopenglish',
 | 
				
			||||||
 | 
					        'md5': 'fd828cd29774a729bf4d4425fe192972',
 | 
				
			||||||
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'id': '21039',
 | 
				
			||||||
 | 
					            'ext': 'mov',
 | 
				
			||||||
 | 
					            'title': 'TRAILER: "Sweatshop" - I can´t take any more',
 | 
				
			||||||
 | 
					            'description': 'md5:21891f2b0dd7ec2f78d84a50e54f8238',
 | 
				
			||||||
 | 
					            'timestamp': 1416927969,
 | 
				
			||||||
 | 
					            'upload_date': '20141125',
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
 | 
					        display_id = self._match_id(url)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        webpage = self._download_webpage(url, display_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        video_id = self._html_search_regex(
 | 
				
			||||||
 | 
					            r'data-xs-id="(\d+)"', webpage, 'video id')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        data = self._download_xml(
 | 
				
			||||||
 | 
					            'http://frontend.xstream.dk/ap/feed/video/?platform=web&id=%s' % video_id, video_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        NS_MAP = {
 | 
				
			||||||
 | 
					            'atom': 'http://www.w3.org/2005/Atom',
 | 
				
			||||||
 | 
					            'xt': 'http://xstream.dk/',
 | 
				
			||||||
 | 
					            'media': 'http://search.yahoo.com/mrss/',
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        entry = data.find(xpath_with_ns('./atom:entry', NS_MAP))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        title = xpath_text(
 | 
				
			||||||
 | 
					            entry, xpath_with_ns('./atom:title', NS_MAP), 'title')
 | 
				
			||||||
 | 
					        description = xpath_text(
 | 
				
			||||||
 | 
					            entry, xpath_with_ns('./atom:summary', NS_MAP), 'description')
 | 
				
			||||||
 | 
					        timestamp = parse_iso8601(xpath_text(
 | 
				
			||||||
 | 
					            entry, xpath_with_ns('./atom:published', NS_MAP), 'upload date'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        formats = []
 | 
				
			||||||
 | 
					        media_group = entry.find(xpath_with_ns('./media:group', NS_MAP))
 | 
				
			||||||
 | 
					        for media_content in media_group.findall(xpath_with_ns('./media:content', NS_MAP)):
 | 
				
			||||||
 | 
					            media_url = media_content.get('url')
 | 
				
			||||||
 | 
					            if not media_url:
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            tbr = int_or_none(media_content.get('bitrate'))
 | 
				
			||||||
 | 
					            mobj = re.search(r'^(?P<url>rtmp://[^/]+/(?P<app>[^/]+))/(?P<playpath>.+)$', media_url)
 | 
				
			||||||
 | 
					            if mobj:
 | 
				
			||||||
 | 
					                formats.append({
 | 
				
			||||||
 | 
					                    'url': mobj.group('url'),
 | 
				
			||||||
 | 
					                    'play_path': 'mp4:%s' % mobj.group('playpath'),
 | 
				
			||||||
 | 
					                    'app': mobj.group('app'),
 | 
				
			||||||
 | 
					                    'ext': 'flv',
 | 
				
			||||||
 | 
					                    'tbr': tbr,
 | 
				
			||||||
 | 
					                    'format_id': 'rtmp-%d' % tbr,
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                formats.append({
 | 
				
			||||||
 | 
					                    'url': media_url,
 | 
				
			||||||
 | 
					                    'tbr': tbr,
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					        self._sort_formats(formats)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        link = find_xpath_attr(
 | 
				
			||||||
 | 
					            entry, xpath_with_ns('./atom:link', NS_MAP), 'rel', 'original')
 | 
				
			||||||
 | 
					        if link is not None:
 | 
				
			||||||
 | 
					            formats.append({
 | 
				
			||||||
 | 
					                'url': link.get('href'),
 | 
				
			||||||
 | 
					                'format_id': link.get('rel'),
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        thumbnails = [{
 | 
				
			||||||
 | 
					            'url': splash.get('url'),
 | 
				
			||||||
 | 
					            'width': int_or_none(splash.get('width')),
 | 
				
			||||||
 | 
					            'height': int_or_none(splash.get('height')),
 | 
				
			||||||
 | 
					        } for splash in media_group.findall(xpath_with_ns('./xt:splash', NS_MAP))]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            'id': video_id,
 | 
				
			||||||
 | 
					            'title': title,
 | 
				
			||||||
 | 
					            'description': description,
 | 
				
			||||||
 | 
					            'timestamp': timestamp,
 | 
				
			||||||
 | 
					            'formats': formats,
 | 
				
			||||||
 | 
					            'thumbnails': thumbnails,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
@@ -1,8 +1,6 @@
 | 
				
			|||||||
# encoding: utf-8
 | 
					# encoding: utf-8
 | 
				
			||||||
from __future__ import unicode_literals
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import re
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from .common import InfoExtractor
 | 
					from .common import InfoExtractor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -21,9 +19,7 @@ class AftonbladetIE(InfoExtractor):
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _real_extract(self, url):
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
        mobj = re.search(self._VALID_URL, url)
 | 
					        video_id = self._match_id(url)
 | 
				
			||||||
 | 
					 | 
				
			||||||
        video_id = mobj.group('video_id')
 | 
					 | 
				
			||||||
        webpage = self._download_webpage(url, video_id)
 | 
					        webpage = self._download_webpage(url, video_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # find internal video meta data
 | 
					        # find internal video meta data
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										74
									
								
								youtube_dl/extractor/airmozilla.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								youtube_dl/extractor/airmozilla.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,74 @@
 | 
				
			|||||||
 | 
					# coding: utf-8
 | 
				
			||||||
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import re
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .common import InfoExtractor
 | 
				
			||||||
 | 
					from ..utils import (
 | 
				
			||||||
 | 
					    int_or_none,
 | 
				
			||||||
 | 
					    parse_duration,
 | 
				
			||||||
 | 
					    parse_iso8601,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AirMozillaIE(InfoExtractor):
 | 
				
			||||||
 | 
					    _VALID_URL = r'https?://air\.mozilla\.org/(?P<id>[0-9a-z-]+)/?'
 | 
				
			||||||
 | 
					    _TEST = {
 | 
				
			||||||
 | 
					        'url': 'https://air.mozilla.org/privacy-lab-a-meetup-for-privacy-minded-people-in-san-francisco/',
 | 
				
			||||||
 | 
					        'md5': '2e3e7486ba5d180e829d453875b9b8bf',
 | 
				
			||||||
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'id': '6x4q2w',
 | 
				
			||||||
 | 
					            'ext': 'mp4',
 | 
				
			||||||
 | 
					            'title': 'Privacy Lab - a meetup for privacy minded people in San Francisco',
 | 
				
			||||||
 | 
					            'thumbnail': 're:https://\w+\.cloudfront\.net/6x4q2w/poster\.jpg\?t=\d+',
 | 
				
			||||||
 | 
					            'description': 'Brings together privacy professionals and others interested in privacy at for-profits, non-profits, and NGOs in an effort to contribute to the state of the ecosystem...',
 | 
				
			||||||
 | 
					            'timestamp': 1422487800,
 | 
				
			||||||
 | 
					            'upload_date': '20150128',
 | 
				
			||||||
 | 
					            'location': 'SFO Commons',
 | 
				
			||||||
 | 
					            'duration': 3780,
 | 
				
			||||||
 | 
					            'view_count': int,
 | 
				
			||||||
 | 
					            'categories': ['Main'],
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
 | 
					        display_id = self._match_id(url)
 | 
				
			||||||
 | 
					        webpage = self._download_webpage(url, display_id)
 | 
				
			||||||
 | 
					        video_id = self._html_search_regex(r'//vid.ly/(.*?)/embed', webpage, 'id')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        embed_script = self._download_webpage('https://vid.ly/{0}/embed'.format(video_id), video_id)
 | 
				
			||||||
 | 
					        jwconfig = self._search_regex(r'\svar jwconfig = (\{.*?\});\s', embed_script, 'metadata')
 | 
				
			||||||
 | 
					        metadata = self._parse_json(jwconfig, video_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        formats = [{
 | 
				
			||||||
 | 
					            'url': source['file'],
 | 
				
			||||||
 | 
					            'ext': source['type'],
 | 
				
			||||||
 | 
					            'format_id': self._search_regex(r'&format=(.*)$', source['file'], 'video format'),
 | 
				
			||||||
 | 
					            'format': source['label'],
 | 
				
			||||||
 | 
					            'height': int(source['label'].rstrip('p')),
 | 
				
			||||||
 | 
					        } for source in metadata['playlist'][0]['sources']]
 | 
				
			||||||
 | 
					        self._sort_formats(formats)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        view_count = int_or_none(self._html_search_regex(
 | 
				
			||||||
 | 
					            r'Views since archived: ([0-9]+)',
 | 
				
			||||||
 | 
					            webpage, 'view count', fatal=False))
 | 
				
			||||||
 | 
					        timestamp = parse_iso8601(self._html_search_regex(
 | 
				
			||||||
 | 
					            r'<time datetime="(.*?)"', webpage, 'timestamp', fatal=False))
 | 
				
			||||||
 | 
					        duration = parse_duration(self._search_regex(
 | 
				
			||||||
 | 
					            r'Duration:\s*(\d+\s*hours?\s*\d+\s*minutes?)',
 | 
				
			||||||
 | 
					            webpage, 'duration', fatal=False))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            'id': video_id,
 | 
				
			||||||
 | 
					            'title': self._og_search_title(webpage),
 | 
				
			||||||
 | 
					            'formats': formats,
 | 
				
			||||||
 | 
					            'url': self._og_search_url(webpage),
 | 
				
			||||||
 | 
					            'display_id': display_id,
 | 
				
			||||||
 | 
					            'thumbnail': metadata['playlist'][0].get('image'),
 | 
				
			||||||
 | 
					            'description': self._og_search_description(webpage),
 | 
				
			||||||
 | 
					            'timestamp': timestamp,
 | 
				
			||||||
 | 
					            'location': self._html_search_regex(r'Location: (.*)', webpage, 'location', default=None),
 | 
				
			||||||
 | 
					            'duration': duration,
 | 
				
			||||||
 | 
					            'view_count': view_count,
 | 
				
			||||||
 | 
					            'categories': re.findall(r'<a href=".*?" class="channel">(.*?)</a>', webpage),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
@@ -1,3 +1,5 @@
 | 
				
			|||||||
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .common import InfoExtractor
 | 
					from .common import InfoExtractor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -24,9 +26,10 @@ class AlJazeeraIE(InfoExtractor):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            '_type': 'url',
 | 
					            '_type': 'url',
 | 
				
			||||||
            'url':
 | 
					            'url': (
 | 
				
			||||||
                'http://c.brightcove.com/services/viewer/federated_f9?'
 | 
					                'brightcove:'
 | 
				
			||||||
                '&playerKey=AQ~~%2CAAAAmtVJIFk~%2CTVGOQ5ZTwJbeMWnq5d_H4MOM57xfzApc'
 | 
					                'playerKey=AQ~~%2CAAAAmtVJIFk~%2CTVGOQ5ZTwJbeMWnq5d_H4MOM57xfzApc'
 | 
				
			||||||
                '&%40videoPlayer={0}'.format(brightcove_id),
 | 
					                '&%40videoPlayer={0}'.format(brightcove_id)
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
            'ie_key': 'Brightcove',
 | 
					            'ie_key': 'Brightcove',
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										77
									
								
								youtube_dl/extractor/alphaporno.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								youtube_dl/extractor/alphaporno.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,77 @@
 | 
				
			|||||||
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .common import InfoExtractor
 | 
				
			||||||
 | 
					from ..utils import (
 | 
				
			||||||
 | 
					    parse_iso8601,
 | 
				
			||||||
 | 
					    parse_duration,
 | 
				
			||||||
 | 
					    parse_filesize,
 | 
				
			||||||
 | 
					    int_or_none,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AlphaPornoIE(InfoExtractor):
 | 
				
			||||||
 | 
					    _VALID_URL = r'https?://(?:www\.)?alphaporno\.com/videos/(?P<id>[^/]+)'
 | 
				
			||||||
 | 
					    _TEST = {
 | 
				
			||||||
 | 
					        'url': 'http://www.alphaporno.com/videos/sensual-striptease-porn-with-samantha-alexandra/',
 | 
				
			||||||
 | 
					        'md5': 'feb6d3bba8848cd54467a87ad34bd38e',
 | 
				
			||||||
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'id': '258807',
 | 
				
			||||||
 | 
					            'display_id': 'sensual-striptease-porn-with-samantha-alexandra',
 | 
				
			||||||
 | 
					            'ext': 'mp4',
 | 
				
			||||||
 | 
					            'title': 'Sensual striptease porn with Samantha Alexandra',
 | 
				
			||||||
 | 
					            'thumbnail': 're:https?://.*\.jpg$',
 | 
				
			||||||
 | 
					            'timestamp': 1418694611,
 | 
				
			||||||
 | 
					            'upload_date': '20141216',
 | 
				
			||||||
 | 
					            'duration': 387,
 | 
				
			||||||
 | 
					            'filesize_approx': 54120000,
 | 
				
			||||||
 | 
					            'tbr': 1145,
 | 
				
			||||||
 | 
					            'categories': list,
 | 
				
			||||||
 | 
					            'age_limit': 18,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
 | 
					        display_id = self._match_id(url)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        webpage = self._download_webpage(url, display_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        video_id = self._search_regex(
 | 
				
			||||||
 | 
					            r"video_id\s*:\s*'([^']+)'", webpage, 'video id', default=None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        video_url = self._search_regex(
 | 
				
			||||||
 | 
					            r"video_url\s*:\s*'([^']+)'", webpage, 'video url')
 | 
				
			||||||
 | 
					        ext = self._html_search_meta(
 | 
				
			||||||
 | 
					            'encodingFormat', webpage, 'ext', default='.mp4')[1:]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        title = self._search_regex(
 | 
				
			||||||
 | 
					            [r'<meta content="([^"]+)" itemprop="description">',
 | 
				
			||||||
 | 
					             r'class="title" itemprop="name">([^<]+)<'],
 | 
				
			||||||
 | 
					            webpage, 'title')
 | 
				
			||||||
 | 
					        thumbnail = self._html_search_meta('thumbnail', webpage, 'thumbnail')
 | 
				
			||||||
 | 
					        timestamp = parse_iso8601(self._html_search_meta(
 | 
				
			||||||
 | 
					            'uploadDate', webpage, 'upload date'))
 | 
				
			||||||
 | 
					        duration = parse_duration(self._html_search_meta(
 | 
				
			||||||
 | 
					            'duration', webpage, 'duration'))
 | 
				
			||||||
 | 
					        filesize_approx = parse_filesize(self._html_search_meta(
 | 
				
			||||||
 | 
					            'contentSize', webpage, 'file size'))
 | 
				
			||||||
 | 
					        bitrate = int_or_none(self._html_search_meta(
 | 
				
			||||||
 | 
					            'bitrate', webpage, 'bitrate'))
 | 
				
			||||||
 | 
					        categories = self._html_search_meta(
 | 
				
			||||||
 | 
					            'keywords', webpage, 'categories', default='').split(',')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        age_limit = self._rta_search(webpage)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            'id': video_id,
 | 
				
			||||||
 | 
					            'display_id': display_id,
 | 
				
			||||||
 | 
					            'url': video_url,
 | 
				
			||||||
 | 
					            'ext': ext,
 | 
				
			||||||
 | 
					            'title': title,
 | 
				
			||||||
 | 
					            'thumbnail': thumbnail,
 | 
				
			||||||
 | 
					            'timestamp': timestamp,
 | 
				
			||||||
 | 
					            'duration': duration,
 | 
				
			||||||
 | 
					            'filesize_approx': filesize_approx,
 | 
				
			||||||
 | 
					            'tbr': bitrate,
 | 
				
			||||||
 | 
					            'categories': categories,
 | 
				
			||||||
 | 
					            'age_limit': age_limit,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
@@ -20,6 +20,7 @@ class AparatIE(InfoExtractor):
 | 
				
			|||||||
            'id': 'wP8On',
 | 
					            'id': 'wP8On',
 | 
				
			||||||
            'ext': 'mp4',
 | 
					            'ext': 'mp4',
 | 
				
			||||||
            'title': 'تیم گلکسی 11 - زومیت',
 | 
					            'title': 'تیم گلکسی 11 - زومیت',
 | 
				
			||||||
 | 
					            'age_limit': 0,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        # 'skip': 'Extremely unreliable',
 | 
					        # 'skip': 'Extremely unreliable',
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -34,7 +35,8 @@ class AparatIE(InfoExtractor):
 | 
				
			|||||||
                     video_id + '/vt/frame')
 | 
					                     video_id + '/vt/frame')
 | 
				
			||||||
        webpage = self._download_webpage(embed_url, video_id)
 | 
					        webpage = self._download_webpage(embed_url, video_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        video_urls = re.findall(r'fileList\[[0-9]+\]\s*=\s*"([^"]+)"', webpage)
 | 
					        video_urls = [video_url.replace('\\/', '/') for video_url in re.findall(
 | 
				
			||||||
 | 
					            r'(?:fileList\[[0-9]+\]\s*=|"file"\s*:)\s*"([^"]+)"', webpage)]
 | 
				
			||||||
        for i, video_url in enumerate(video_urls):
 | 
					        for i, video_url in enumerate(video_urls):
 | 
				
			||||||
            req = HEADRequest(video_url)
 | 
					            req = HEADRequest(video_url)
 | 
				
			||||||
            res = self._request_webpage(
 | 
					            res = self._request_webpage(
 | 
				
			||||||
@@ -46,7 +48,7 @@ class AparatIE(InfoExtractor):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        title = self._search_regex(r'\s+title:\s*"([^"]+)"', webpage, 'title')
 | 
					        title = self._search_regex(r'\s+title:\s*"([^"]+)"', webpage, 'title')
 | 
				
			||||||
        thumbnail = self._search_regex(
 | 
					        thumbnail = self._search_regex(
 | 
				
			||||||
            r'\s+image:\s*"([^"]+)"', webpage, 'thumbnail', fatal=False)
 | 
					            r'image:\s*"([^"]+)"', webpage, 'thumbnail', fatal=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            'id': video_id,
 | 
					            'id': video_id,
 | 
				
			||||||
@@ -54,4 +56,5 @@ class AparatIE(InfoExtractor):
 | 
				
			|||||||
            'url': video_url,
 | 
					            'url': video_url,
 | 
				
			||||||
            'ext': 'mp4',
 | 
					            'ext': 'mp4',
 | 
				
			||||||
            'thumbnail': thumbnail,
 | 
					            'thumbnail': thumbnail,
 | 
				
			||||||
 | 
					            'age_limit': self._family_friendly_search(webpage),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,9 +11,12 @@ from ..utils import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AppleTrailersIE(InfoExtractor):
 | 
					class AppleTrailersIE(InfoExtractor):
 | 
				
			||||||
    _VALID_URL = r'https?://(?:www\.)?trailers\.apple\.com/trailers/(?P<company>[^/]+)/(?P<movie>[^/]+)'
 | 
					    _VALID_URL = r'https?://(?:www\.)?trailers\.apple\.com/(?:trailers|ca)/(?P<company>[^/]+)/(?P<movie>[^/]+)'
 | 
				
			||||||
    _TEST = {
 | 
					    _TESTS = [{
 | 
				
			||||||
        "url": "http://trailers.apple.com/trailers/wb/manofsteel/",
 | 
					        "url": "http://trailers.apple.com/trailers/wb/manofsteel/",
 | 
				
			||||||
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'id': 'manofsteel',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        "playlist": [
 | 
					        "playlist": [
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                "md5": "d97a8e575432dbcb81b7c3acb741f8a8",
 | 
					                "md5": "d97a8e575432dbcb81b7c3acb741f8a8",
 | 
				
			||||||
@@ -60,7 +63,10 @@ class AppleTrailersIE(InfoExtractor):
 | 
				
			|||||||
                },
 | 
					                },
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
    }
 | 
					    }, {
 | 
				
			||||||
 | 
					        'url': 'http://trailers.apple.com/ca/metropole/autrui/',
 | 
				
			||||||
 | 
					        'only_matching': True,
 | 
				
			||||||
 | 
					    }]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    _JSON_RE = r'iTunes.playURL\((.*?)\);'
 | 
					    _JSON_RE = r'iTunes.playURL\((.*?)\);'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -122,14 +128,15 @@ class AppleTrailersIE(InfoExtractor):
 | 
				
			|||||||
            playlist.append({
 | 
					            playlist.append({
 | 
				
			||||||
                '_type': 'video',
 | 
					                '_type': 'video',
 | 
				
			||||||
                'id': video_id,
 | 
					                'id': video_id,
 | 
				
			||||||
                'title': title,
 | 
					 | 
				
			||||||
                'formats': formats,
 | 
					                'formats': formats,
 | 
				
			||||||
                'title': title,
 | 
					                'title': title,
 | 
				
			||||||
                'duration': duration,
 | 
					                'duration': duration,
 | 
				
			||||||
                'thumbnail': thumbnail,
 | 
					                'thumbnail': thumbnail,
 | 
				
			||||||
                'upload_date': upload_date,
 | 
					                'upload_date': upload_date,
 | 
				
			||||||
                'uploader_id': uploader_id,
 | 
					                'uploader_id': uploader_id,
 | 
				
			||||||
                'user_agent': 'QuickTime compatible (youtube-dl)',
 | 
					                'http_headers': {
 | 
				
			||||||
 | 
					                    'User-Agent': 'QuickTime compatible (youtube-dl)',
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,42 +1,48 @@
 | 
				
			|||||||
from __future__ import unicode_literals
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import json
 | 
					 | 
				
			||||||
import re
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from .common import InfoExtractor
 | 
					from .common import InfoExtractor
 | 
				
			||||||
from ..utils import (
 | 
					from ..utils import unified_strdate
 | 
				
			||||||
    unified_strdate,
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ArchiveOrgIE(InfoExtractor):
 | 
					class ArchiveOrgIE(InfoExtractor):
 | 
				
			||||||
    IE_NAME = 'archive.org'
 | 
					    IE_NAME = 'archive.org'
 | 
				
			||||||
    IE_DESC = 'archive.org videos'
 | 
					    IE_DESC = 'archive.org videos'
 | 
				
			||||||
    _VALID_URL = r'(?:https?://)?(?:www\.)?archive\.org/details/(?P<id>[^?/]+)(?:[?].*)?$'
 | 
					    _VALID_URL = r'https?://(?:www\.)?archive\.org/details/(?P<id>[^?/]+)(?:[?].*)?$'
 | 
				
			||||||
    _TEST = {
 | 
					    _TESTS = [{
 | 
				
			||||||
        "url": "http://archive.org/details/XD300-23_68HighlightsAResearchCntAugHumanIntellect",
 | 
					        'url': 'http://archive.org/details/XD300-23_68HighlightsAResearchCntAugHumanIntellect',
 | 
				
			||||||
        'file': 'XD300-23_68HighlightsAResearchCntAugHumanIntellect.ogv',
 | 
					 | 
				
			||||||
        'md5': '8af1d4cf447933ed3c7f4871162602db',
 | 
					        'md5': '8af1d4cf447933ed3c7f4871162602db',
 | 
				
			||||||
        'info_dict': {
 | 
					        'info_dict': {
 | 
				
			||||||
            "title": "1968 Demo - FJCC Conference Presentation Reel #1",
 | 
					            'id': 'XD300-23_68HighlightsAResearchCntAugHumanIntellect',
 | 
				
			||||||
            "description": "Reel 1 of 3: Also known as the \"Mother of All Demos\", Doug Engelbart's presentation at the Fall Joint Computer Conference in San Francisco, December 9, 1968 titled \"A Research Center for Augmenting Human Intellect.\" For this presentation, Doug and his team astonished the audience by not only relating their research, but demonstrating it live. This was the debut of the mouse, interactive computing, hypermedia, computer supported software engineering, video teleconferencing, etc. See also <a href=\"http://dougengelbart.org/firsts/dougs-1968-demo.html\" rel=\"nofollow\">Doug's 1968 Demo page</a> for more background, highlights, links, and the detailed paper published in this conference proceedings. Filmed on 3 reels: Reel 1 | <a href=\"http://www.archive.org/details/XD300-24_68HighlightsAResearchCntAugHumanIntellect\" rel=\"nofollow\">Reel 2</a> | <a href=\"http://www.archive.org/details/XD300-25_68HighlightsAResearchCntAugHumanIntellect\" rel=\"nofollow\">Reel 3</a>",
 | 
					            'ext': 'ogv',
 | 
				
			||||||
            "upload_date": "19681210",
 | 
					            'title': '1968 Demo - FJCC Conference Presentation Reel #1',
 | 
				
			||||||
            "uploader": "SRI International"
 | 
					            'description': 'md5:1780b464abaca9991d8968c877bb53ed',
 | 
				
			||||||
 | 
					            'upload_date': '19681210',
 | 
				
			||||||
 | 
					            'uploader': 'SRI International'
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }, {
 | 
				
			||||||
 | 
					        'url': 'https://archive.org/details/Cops1922',
 | 
				
			||||||
 | 
					        'md5': '18f2a19e6d89af8425671da1cf3d4e04',
 | 
				
			||||||
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'id': 'Cops1922',
 | 
				
			||||||
 | 
					            'ext': 'ogv',
 | 
				
			||||||
 | 
					            'title': 'Buster Keaton\'s "Cops" (1922)',
 | 
				
			||||||
 | 
					            'description': 'md5:70f72ee70882f713d4578725461ffcc3',
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _real_extract(self, url):
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
        mobj = re.match(self._VALID_URL, url)
 | 
					        video_id = self._match_id(url)
 | 
				
			||||||
        video_id = mobj.group('id')
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        json_url = url + ('?' if '?' in url else '&') + 'output=json'
 | 
					        json_url = url + ('?' if '?' in url else '&') + 'output=json'
 | 
				
			||||||
        json_data = self._download_webpage(json_url, video_id)
 | 
					        data = self._download_json(json_url, video_id)
 | 
				
			||||||
        data = json.loads(json_data)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        title = data['metadata']['title'][0]
 | 
					        def get_optional(data_dict, field):
 | 
				
			||||||
        description = data['metadata']['description'][0]
 | 
					            return data_dict['metadata'].get(field, [None])[0]
 | 
				
			||||||
        uploader = data['metadata']['creator'][0]
 | 
					
 | 
				
			||||||
        upload_date = unified_strdate(data['metadata']['date'][0])
 | 
					        title = get_optional(data, 'title')
 | 
				
			||||||
 | 
					        description = get_optional(data, 'description')
 | 
				
			||||||
 | 
					        uploader = get_optional(data, 'creator')
 | 
				
			||||||
 | 
					        upload_date = unified_strdate(get_optional(data, 'date'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        formats = [
 | 
					        formats = [
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,13 +23,7 @@ class ARDMediathekIE(InfoExtractor):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    _TESTS = [{
 | 
					    _TESTS = [{
 | 
				
			||||||
        'url': 'http://mediathek.daserste.de/sendungen_a-z/328454_anne-will/22429276_vertrauen-ist-gut-spionieren-ist-besser-geht',
 | 
					        'url': 'http://mediathek.daserste.de/sendungen_a-z/328454_anne-will/22429276_vertrauen-ist-gut-spionieren-ist-besser-geht',
 | 
				
			||||||
        'file': '22429276.mp4',
 | 
					        'only_matching': True,
 | 
				
			||||||
        'md5': '469751912f1de0816a9fc9df8336476c',
 | 
					 | 
				
			||||||
        'info_dict': {
 | 
					 | 
				
			||||||
            'title': 'Vertrauen ist gut, Spionieren ist besser - Geht so deutsch-amerikanische Freundschaft?',
 | 
					 | 
				
			||||||
            'description': 'Das Erste Mediathek [ARD]: Vertrauen ist gut, Spionieren ist besser - Geht so deutsch-amerikanische Freundschaft?, Anne Will, Über die Spionage-Affäre diskutieren Clemens Binninger, Katrin Göring-Eckardt, Georg Mascolo, Andrew B. Denison und Constanze Kurz.. Das Video zur Sendung Anne Will am Mittwoch, 16.07.2014',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        'skip': 'Blocked outside of Germany',
 | 
					 | 
				
			||||||
    }, {
 | 
					    }, {
 | 
				
			||||||
        'url': 'http://www.ardmediathek.de/tv/Tatort/Das-Wunder-von-Wolbeck-Video-tgl-ab-20/Das-Erste/Video?documentId=22490580&bcastId=602916',
 | 
					        'url': 'http://www.ardmediathek.de/tv/Tatort/Das-Wunder-von-Wolbeck-Video-tgl-ab-20/Das-Erste/Video?documentId=22490580&bcastId=602916',
 | 
				
			||||||
        'info_dict': {
 | 
					        'info_dict': {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -37,7 +37,7 @@ class ArteTvIE(InfoExtractor):
 | 
				
			|||||||
            config_xml_url, video_id, note='Downloading configuration')
 | 
					            config_xml_url, video_id, note='Downloading configuration')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        formats = [{
 | 
					        formats = [{
 | 
				
			||||||
            'forma_id': q.attrib['quality'],
 | 
					            'format_id': q.attrib['quality'],
 | 
				
			||||||
            # The playpath starts at 'mp4:', if we don't manually
 | 
					            # The playpath starts at 'mp4:', if we don't manually
 | 
				
			||||||
            # split the url, rtmpdump will incorrectly parse them
 | 
					            # split the url, rtmpdump will incorrectly parse them
 | 
				
			||||||
            'url': q.text.split('mp4:', 1)[0],
 | 
					            'url': q.text.split('mp4:', 1)[0],
 | 
				
			||||||
@@ -133,7 +133,7 @@ class ArteTVPlus7IE(InfoExtractor):
 | 
				
			|||||||
                'width': int_or_none(f.get('width')),
 | 
					                'width': int_or_none(f.get('width')),
 | 
				
			||||||
                'height': int_or_none(f.get('height')),
 | 
					                'height': int_or_none(f.get('height')),
 | 
				
			||||||
                'tbr': int_or_none(f.get('bitrate')),
 | 
					                'tbr': int_or_none(f.get('bitrate')),
 | 
				
			||||||
                'quality': qfunc(f['quality']),
 | 
					                'quality': qfunc(f.get('quality')),
 | 
				
			||||||
                'source_preference': source_pref,
 | 
					                'source_preference': source_pref,
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										162
									
								
								youtube_dl/extractor/atresplayer.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								youtube_dl/extractor/atresplayer.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,162 @@
 | 
				
			|||||||
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					import hmac
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .common import InfoExtractor
 | 
				
			||||||
 | 
					from ..compat import (
 | 
				
			||||||
 | 
					    compat_str,
 | 
				
			||||||
 | 
					    compat_urllib_parse,
 | 
				
			||||||
 | 
					    compat_urllib_request,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					from ..utils import (
 | 
				
			||||||
 | 
					    int_or_none,
 | 
				
			||||||
 | 
					    float_or_none,
 | 
				
			||||||
 | 
					    xpath_text,
 | 
				
			||||||
 | 
					    ExtractorError,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AtresPlayerIE(InfoExtractor):
 | 
				
			||||||
 | 
					    _VALID_URL = r'https?://(?:www\.)?atresplayer\.com/television/[^/]+/[^/]+/[^/]+/(?P<id>.+?)_\d+\.html'
 | 
				
			||||||
 | 
					    _TESTS = [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            'url': 'http://www.atresplayer.com/television/programas/el-club-de-la-comedia/temporada-4/capitulo-10-especial-solidario-nochebuena_2014122100174.html',
 | 
				
			||||||
 | 
					            'md5': 'efd56753cda1bb64df52a3074f62e38a',
 | 
				
			||||||
 | 
					            'info_dict': {
 | 
				
			||||||
 | 
					                'id': 'capitulo-10-especial-solidario-nochebuena',
 | 
				
			||||||
 | 
					                'ext': 'mp4',
 | 
				
			||||||
 | 
					                'title': 'Especial Solidario de Nochebuena',
 | 
				
			||||||
 | 
					                'description': 'md5:e2d52ff12214fa937107d21064075bf1',
 | 
				
			||||||
 | 
					                'duration': 5527.6,
 | 
				
			||||||
 | 
					                'thumbnail': 're:^https?://.*\.jpg$',
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            'url': 'http://www.atresplayer.com/television/series/el-secreto-de-puente-viejo/el-chico-de-los-tres-lunares/capitulo-977-29-12-14_2014122400174.html',
 | 
				
			||||||
 | 
					            'only_matching': True,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    _USER_AGENT = 'Dalvik/1.6.0 (Linux; U; Android 4.3; GT-I9300 Build/JSS15J'
 | 
				
			||||||
 | 
					    _MAGIC = 'QWtMLXs414Yo+c#_+Q#K@NN)'
 | 
				
			||||||
 | 
					    _TIMESTAMP_SHIFT = 30000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    _TIME_API_URL = 'http://servicios.atresplayer.com/api/admin/time.json'
 | 
				
			||||||
 | 
					    _URL_VIDEO_TEMPLATE = 'https://servicios.atresplayer.com/api/urlVideo/{1}/{0}/{1}|{2}|{3}.json'
 | 
				
			||||||
 | 
					    _PLAYER_URL_TEMPLATE = 'https://servicios.atresplayer.com/episode/getplayer.json?episodePk=%s'
 | 
				
			||||||
 | 
					    _EPISODE_URL_TEMPLATE = 'http://www.atresplayer.com/episodexml/%s'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    _LOGIN_URL = 'https://servicios.atresplayer.com/j_spring_security_check'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _real_initialize(self):
 | 
				
			||||||
 | 
					        self._login()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _login(self):
 | 
				
			||||||
 | 
					        (username, password) = self._get_login_info()
 | 
				
			||||||
 | 
					        if username is None:
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        login_form = {
 | 
				
			||||||
 | 
					            'j_username': username,
 | 
				
			||||||
 | 
					            'j_password': password,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        request = compat_urllib_request.Request(
 | 
				
			||||||
 | 
					            self._LOGIN_URL, compat_urllib_parse.urlencode(login_form).encode('utf-8'))
 | 
				
			||||||
 | 
					        request.add_header('Content-Type', 'application/x-www-form-urlencoded')
 | 
				
			||||||
 | 
					        response = self._download_webpage(
 | 
				
			||||||
 | 
					            request, None, 'Logging in as %s' % username)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        error = self._html_search_regex(
 | 
				
			||||||
 | 
					            r'(?s)<ul class="list_error">(.+?)</ul>', response, 'error', default=None)
 | 
				
			||||||
 | 
					        if error:
 | 
				
			||||||
 | 
					            raise ExtractorError(
 | 
				
			||||||
 | 
					                'Unable to login: %s' % error, expected=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
 | 
					        video_id = self._match_id(url)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        webpage = self._download_webpage(url, video_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        episode_id = self._search_regex(
 | 
				
			||||||
 | 
					            r'episode="([^"]+)"', webpage, 'episode id')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        timestamp = int_or_none(self._download_webpage(
 | 
				
			||||||
 | 
					            self._TIME_API_URL,
 | 
				
			||||||
 | 
					            video_id, 'Downloading timestamp', fatal=False), 1000, time.time())
 | 
				
			||||||
 | 
					        timestamp_shifted = compat_str(timestamp + self._TIMESTAMP_SHIFT)
 | 
				
			||||||
 | 
					        token = hmac.new(
 | 
				
			||||||
 | 
					            self._MAGIC.encode('ascii'),
 | 
				
			||||||
 | 
					            (episode_id + timestamp_shifted).encode('utf-8')
 | 
				
			||||||
 | 
					        ).hexdigest()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        formats = []
 | 
				
			||||||
 | 
					        for fmt in ['windows', 'android_tablet']:
 | 
				
			||||||
 | 
					            request = compat_urllib_request.Request(
 | 
				
			||||||
 | 
					                self._URL_VIDEO_TEMPLATE.format(fmt, episode_id, timestamp_shifted, token))
 | 
				
			||||||
 | 
					            request.add_header('User-Agent', self._USER_AGENT)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            fmt_json = self._download_json(
 | 
				
			||||||
 | 
					                request, video_id, 'Downloading %s video JSON' % fmt)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            result = fmt_json.get('resultDes')
 | 
				
			||||||
 | 
					            if result.lower() != 'ok':
 | 
				
			||||||
 | 
					                raise ExtractorError(
 | 
				
			||||||
 | 
					                    '%s returned error: %s' % (self.IE_NAME, result), expected=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for format_id, video_url in fmt_json['resultObject'].items():
 | 
				
			||||||
 | 
					                if format_id == 'token' or not video_url.startswith('http'):
 | 
				
			||||||
 | 
					                    continue
 | 
				
			||||||
 | 
					                if video_url.endswith('/Manifest'):
 | 
				
			||||||
 | 
					                    if 'geodeswowsmpra3player' in video_url:
 | 
				
			||||||
 | 
					                        f4m_path = video_url.split('smil:', 1)[-1].split('free_', 1)[0]
 | 
				
			||||||
 | 
					                        f4m_url = 'http://drg.antena3.com/{0}hds/es/sd.f4m'.format(f4m_path)
 | 
				
			||||||
 | 
					                        # this videos are protected by DRM, the f4m downloader doesn't support them
 | 
				
			||||||
 | 
					                        continue
 | 
				
			||||||
 | 
					                    else:
 | 
				
			||||||
 | 
					                        f4m_url = video_url[:-9] + '/manifest.f4m'
 | 
				
			||||||
 | 
					                    formats.extend(self._extract_f4m_formats(f4m_url, video_id))
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    formats.append({
 | 
				
			||||||
 | 
					                        'url': video_url,
 | 
				
			||||||
 | 
					                        'format_id': 'android-%s' % format_id,
 | 
				
			||||||
 | 
					                        'preference': 1,
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					        self._sort_formats(formats)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        player = self._download_json(
 | 
				
			||||||
 | 
					            self._PLAYER_URL_TEMPLATE % episode_id,
 | 
				
			||||||
 | 
					            episode_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        path_data = player.get('pathData')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        episode = self._download_xml(
 | 
				
			||||||
 | 
					            self._EPISODE_URL_TEMPLATE % path_data,
 | 
				
			||||||
 | 
					            video_id, 'Downloading episode XML')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        duration = float_or_none(xpath_text(
 | 
				
			||||||
 | 
					            episode, './media/asset/info/technical/contentDuration', 'duration'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        art = episode.find('./media/asset/info/art')
 | 
				
			||||||
 | 
					        title = xpath_text(art, './name', 'title')
 | 
				
			||||||
 | 
					        description = xpath_text(art, './description', 'description')
 | 
				
			||||||
 | 
					        thumbnail = xpath_text(episode, './media/asset/files/background', 'thumbnail')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        subtitles = {}
 | 
				
			||||||
 | 
					        subtitle_url = xpath_text(episode, './media/asset/files/subtitle', 'subtitle')
 | 
				
			||||||
 | 
					        if subtitle_url:
 | 
				
			||||||
 | 
					            subtitles['es'] = [{
 | 
				
			||||||
 | 
					                'ext': 'srt',
 | 
				
			||||||
 | 
					                'url': subtitle_url,
 | 
				
			||||||
 | 
					            }]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            'id': video_id,
 | 
				
			||||||
 | 
					            'title': title,
 | 
				
			||||||
 | 
					            'description': description,
 | 
				
			||||||
 | 
					            'thumbnail': thumbnail,
 | 
				
			||||||
 | 
					            'duration': duration,
 | 
				
			||||||
 | 
					            'formats': formats,
 | 
				
			||||||
 | 
					            'subtitles': subtitles,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
							
								
								
									
										55
									
								
								youtube_dl/extractor/atttechchannel.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								youtube_dl/extractor/atttechchannel.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
				
			|||||||
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .common import InfoExtractor
 | 
				
			||||||
 | 
					from ..utils import unified_strdate
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ATTTechChannelIE(InfoExtractor):
 | 
				
			||||||
 | 
					    _VALID_URL = r'https?://techchannel\.att\.com/play-video\.cfm/([^/]+/)*(?P<id>.+)'
 | 
				
			||||||
 | 
					    _TEST = {
 | 
				
			||||||
 | 
					        'url': 'http://techchannel.att.com/play-video.cfm/2014/1/27/ATT-Archives-The-UNIX-System-Making-Computers-Easier-to-Use',
 | 
				
			||||||
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'id': '11316',
 | 
				
			||||||
 | 
					            'display_id': 'ATT-Archives-The-UNIX-System-Making-Computers-Easier-to-Use',
 | 
				
			||||||
 | 
					            'ext': 'flv',
 | 
				
			||||||
 | 
					            'title': 'AT&T Archives : The UNIX System: Making Computers Easier to Use',
 | 
				
			||||||
 | 
					            'description': 'A 1982 film about UNIX is the foundation for software in use around Bell Labs and AT&T.',
 | 
				
			||||||
 | 
					            'thumbnail': 're:^https?://.*\.jpg$',
 | 
				
			||||||
 | 
					            'upload_date': '20140127',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        'params': {
 | 
				
			||||||
 | 
					            # rtmp download
 | 
				
			||||||
 | 
					            'skip_download': True,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
 | 
					        display_id = self._match_id(url)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        webpage = self._download_webpage(url, display_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        video_url = self._search_regex(
 | 
				
			||||||
 | 
					            r"url\s*:\s*'(rtmp://[^']+)'",
 | 
				
			||||||
 | 
					            webpage, 'video URL')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        video_id = self._search_regex(
 | 
				
			||||||
 | 
					            r'mediaid\s*=\s*(\d+)',
 | 
				
			||||||
 | 
					            webpage, 'video id', fatal=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        title = self._og_search_title(webpage)
 | 
				
			||||||
 | 
					        description = self._og_search_description(webpage)
 | 
				
			||||||
 | 
					        thumbnail = self._og_search_thumbnail(webpage)
 | 
				
			||||||
 | 
					        upload_date = unified_strdate(self._search_regex(
 | 
				
			||||||
 | 
					            r'[Rr]elease\s+date:\s*(\d{1,2}/\d{1,2}/\d{4})',
 | 
				
			||||||
 | 
					            webpage, 'upload date', fatal=False), False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            'id': video_id,
 | 
				
			||||||
 | 
					            'display_id': display_id,
 | 
				
			||||||
 | 
					            'url': video_url,
 | 
				
			||||||
 | 
					            'ext': 'flv',
 | 
				
			||||||
 | 
					            'title': title,
 | 
				
			||||||
 | 
					            'description': description,
 | 
				
			||||||
 | 
					            'thumbnail': thumbnail,
 | 
				
			||||||
 | 
					            'upload_date': upload_date,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
@@ -1,11 +1,15 @@
 | 
				
			|||||||
# coding: utf-8
 | 
					# coding: utf-8
 | 
				
			||||||
from __future__ import unicode_literals
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import itertools
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .common import InfoExtractor
 | 
					from .common import InfoExtractor
 | 
				
			||||||
from .soundcloud import SoundcloudIE
 | 
					from .soundcloud import SoundcloudIE
 | 
				
			||||||
from ..utils import ExtractorError
 | 
					from ..utils import (
 | 
				
			||||||
 | 
					    ExtractorError,
 | 
				
			||||||
import time
 | 
					    url_basename,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AudiomackIE(InfoExtractor):
 | 
					class AudiomackIE(InfoExtractor):
 | 
				
			||||||
@@ -17,12 +21,13 @@ class AudiomackIE(InfoExtractor):
 | 
				
			|||||||
            'url': 'http://www.audiomack.com/song/roosh-williams/extraordinary',
 | 
					            'url': 'http://www.audiomack.com/song/roosh-williams/extraordinary',
 | 
				
			||||||
            'info_dict':
 | 
					            'info_dict':
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                'id': 'roosh-williams/extraordinary',
 | 
					                'id': '310086',
 | 
				
			||||||
                'ext': 'mp3',
 | 
					                'ext': 'mp3',
 | 
				
			||||||
                'title': 'Roosh Williams - Extraordinary'
 | 
					                'uploader': 'Roosh Williams',
 | 
				
			||||||
 | 
					                'title': 'Extraordinary'
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        # hosted on soundcloud via audiomack
 | 
					        # audiomack wrapper around soundcloud song
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            'add_ie': ['Soundcloud'],
 | 
					            'add_ie': ['Soundcloud'],
 | 
				
			||||||
            'url': 'http://www.audiomack.com/song/xclusiveszone/take-kare',
 | 
					            'url': 'http://www.audiomack.com/song/xclusiveszone/take-kare',
 | 
				
			||||||
@@ -38,32 +43,102 @@ class AudiomackIE(InfoExtractor):
 | 
				
			|||||||
    ]
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _real_extract(self, url):
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
        video_id = self._match_id(url)
 | 
					        # URLs end with [uploader name]/[uploader title]
 | 
				
			||||||
 | 
					        # this title is whatever the user types in, and is rarely
 | 
				
			||||||
 | 
					        # the proper song title.  Real metadata is in the api response
 | 
				
			||||||
 | 
					        album_url_tag = self._match_id(url)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Request the extended version of the api for extra fields like artist and title
 | 
				
			||||||
        api_response = self._download_json(
 | 
					        api_response = self._download_json(
 | 
				
			||||||
            "http://www.audiomack.com/api/music/url/song/%s?_=%d" % (
 | 
					            'http://www.audiomack.com/api/music/url/song/%s?extended=1&_=%d' % (
 | 
				
			||||||
                video_id, time.time()),
 | 
					                album_url_tag, time.time()),
 | 
				
			||||||
            video_id)
 | 
					            album_url_tag)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if "url" not in api_response:
 | 
					        # API is inconsistent with errors
 | 
				
			||||||
            raise ExtractorError("Unable to deduce api url of song")
 | 
					        if 'url' not in api_response or not api_response['url'] or 'error' in api_response:
 | 
				
			||||||
        realurl = api_response["url"]
 | 
					            raise ExtractorError('Invalid url %s', url)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Audiomack wraps a lot of soundcloud tracks in their branded wrapper
 | 
					        # Audiomack wraps a lot of soundcloud tracks in their branded wrapper
 | 
				
			||||||
        # - if so, pass the work off to the soundcloud extractor
 | 
					        # if so, pass the work off to the soundcloud extractor
 | 
				
			||||||
        if SoundcloudIE.suitable(realurl):
 | 
					        if SoundcloudIE.suitable(api_response['url']):
 | 
				
			||||||
            return {'_type': 'url', 'url': realurl, 'ie_key': 'Soundcloud'}
 | 
					            return {'_type': 'url', 'url': api_response['url'], 'ie_key': 'Soundcloud'}
 | 
				
			||||||
 | 
					 | 
				
			||||||
        webpage = self._download_webpage(url, video_id)
 | 
					 | 
				
			||||||
        artist = self._html_search_regex(
 | 
					 | 
				
			||||||
            r'<span class="artist">(.*?)</span>', webpage, "artist")
 | 
					 | 
				
			||||||
        songtitle = self._html_search_regex(
 | 
					 | 
				
			||||||
            r'<h1 class="profile-title song-title"><span class="artist">.*?</span>(.*?)</h1>',
 | 
					 | 
				
			||||||
            webpage, "title")
 | 
					 | 
				
			||||||
        title = artist + " - " + songtitle
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            'id': video_id,
 | 
					            'id': api_response.get('id', album_url_tag),
 | 
				
			||||||
            'title': title,
 | 
					            'uploader': api_response.get('artist'),
 | 
				
			||||||
            'url': realurl,
 | 
					            'title': api_response.get('title'),
 | 
				
			||||||
 | 
					            'url': api_response['url'],
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AudiomackAlbumIE(InfoExtractor):
 | 
				
			||||||
 | 
					    _VALID_URL = r'https?://(?:www\.)?audiomack\.com/album/(?P<id>[\w/-]+)'
 | 
				
			||||||
 | 
					    IE_NAME = 'audiomack:album'
 | 
				
			||||||
 | 
					    _TESTS = [
 | 
				
			||||||
 | 
					        # Standard album playlist
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            'url': 'http://www.audiomack.com/album/flytunezcom/tha-tour-part-2-mixtape',
 | 
				
			||||||
 | 
					            'playlist_count': 15,
 | 
				
			||||||
 | 
					            'info_dict':
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                'id': '812251',
 | 
				
			||||||
 | 
					                'title': 'Tha Tour: Part 2 (Official Mixtape)'
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        # Album playlist ripped from fakeshoredrive with no metadata
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            'url': 'http://www.audiomack.com/album/fakeshoredrive/ppp-pistol-p-project',
 | 
				
			||||||
 | 
					            'info_dict': {
 | 
				
			||||||
 | 
					                'title': 'PPP (Pistol P Project)',
 | 
				
			||||||
 | 
					                'id': '837572',
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            'playlist': [{
 | 
				
			||||||
 | 
					                'info_dict': {
 | 
				
			||||||
 | 
					                    'title': 'PPP (Pistol P Project) - 9. Heaven or Hell (CHIMACA) ft Zuse (prod by DJ FU)',
 | 
				
			||||||
 | 
					                    'id': '837577',
 | 
				
			||||||
 | 
					                    'ext': 'mp3',
 | 
				
			||||||
 | 
					                    'uploader': 'Lil Herb a.k.a. G Herbo',
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }],
 | 
				
			||||||
 | 
					            'params': {
 | 
				
			||||||
 | 
					                'playliststart': 9,
 | 
				
			||||||
 | 
					                'playlistend': 9,
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
 | 
					        # URLs end with [uploader name]/[uploader title]
 | 
				
			||||||
 | 
					        # this title is whatever the user types in, and is rarely
 | 
				
			||||||
 | 
					        # the proper song title.  Real metadata is in the api response
 | 
				
			||||||
 | 
					        album_url_tag = self._match_id(url)
 | 
				
			||||||
 | 
					        result = {'_type': 'playlist', 'entries': []}
 | 
				
			||||||
 | 
					        # There is no one endpoint for album metadata - instead it is included/repeated in each song's metadata
 | 
				
			||||||
 | 
					        # Therefore we don't know how many songs the album has and must infi-loop until failure
 | 
				
			||||||
 | 
					        for track_no in itertools.count():
 | 
				
			||||||
 | 
					            # Get song's metadata
 | 
				
			||||||
 | 
					            api_response = self._download_json(
 | 
				
			||||||
 | 
					                'http://www.audiomack.com/api/music/url/album/%s/%d?extended=1&_=%d'
 | 
				
			||||||
 | 
					                % (album_url_tag, track_no, time.time()), album_url_tag,
 | 
				
			||||||
 | 
					                note='Querying song information (%d)' % (track_no + 1))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Total failure, only occurs when url is totally wrong
 | 
				
			||||||
 | 
					            # Won't happen in middle of valid playlist (next case)
 | 
				
			||||||
 | 
					            if 'url' not in api_response or 'error' in api_response:
 | 
				
			||||||
 | 
					                raise ExtractorError('Invalid url for track %d of album url %s' % (track_no, url))
 | 
				
			||||||
 | 
					            # URL is good but song id doesn't exist - usually means end of playlist
 | 
				
			||||||
 | 
					            elif not api_response['url']:
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                # Pull out the album metadata and add to result (if it exists)
 | 
				
			||||||
 | 
					                for resultkey, apikey in [('id', 'album_id'), ('title', 'album_title')]:
 | 
				
			||||||
 | 
					                    if apikey in api_response and resultkey not in result:
 | 
				
			||||||
 | 
					                        result[resultkey] = api_response[apikey]
 | 
				
			||||||
 | 
					                song_id = url_basename(api_response['url']).rpartition('.')[0]
 | 
				
			||||||
 | 
					                result['entries'].append({
 | 
				
			||||||
 | 
					                    'id': api_response.get('id', song_id),
 | 
				
			||||||
 | 
					                    'uploader': api_response.get('artist'),
 | 
				
			||||||
 | 
					                    'title': api_response.get('title', song_id),
 | 
				
			||||||
 | 
					                    'url': api_response['url'],
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					        return result
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,54 +0,0 @@
 | 
				
			|||||||
from __future__ import unicode_literals
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import re
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from .common import InfoExtractor
 | 
					 | 
				
			||||||
from ..compat import compat_urllib_parse
 | 
					 | 
				
			||||||
from ..utils import (
 | 
					 | 
				
			||||||
    determine_ext,
 | 
					 | 
				
			||||||
    ExtractorError,
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class AUEngineIE(InfoExtractor):
 | 
					 | 
				
			||||||
    _VALID_URL = r'http://(?:www\.)?auengine\.com/embed\.php\?.*?file=(?P<id>[^&]+).*?'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    _TEST = {
 | 
					 | 
				
			||||||
        'url': 'http://auengine.com/embed.php?file=lfvlytY6&w=650&h=370',
 | 
					 | 
				
			||||||
        'md5': '48972bdbcf1a3a2f5533e62425b41d4f',
 | 
					 | 
				
			||||||
        'info_dict': {
 | 
					 | 
				
			||||||
            'id': 'lfvlytY6',
 | 
					 | 
				
			||||||
            'ext': 'mp4',
 | 
					 | 
				
			||||||
            'title': '[Commie]The Legend of the Legendary Heroes - 03 - Replication Eye (Alpha Stigma)[F9410F5A]'
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _real_extract(self, url):
 | 
					 | 
				
			||||||
        video_id = self._match_id(url)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        webpage = self._download_webpage(url, video_id)
 | 
					 | 
				
			||||||
        title = self._html_search_regex(r'<title>(?P<title>.+?)</title>', webpage, 'title')
 | 
					 | 
				
			||||||
        title = title.strip()
 | 
					 | 
				
			||||||
        links = re.findall(r'\s(?:file|url):\s*["\']([^\'"]+)["\']', webpage)
 | 
					 | 
				
			||||||
        links = map(compat_urllib_parse.unquote, links)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        thumbnail = None
 | 
					 | 
				
			||||||
        video_url = None
 | 
					 | 
				
			||||||
        for link in links:
 | 
					 | 
				
			||||||
            if link.endswith('.png'):
 | 
					 | 
				
			||||||
                thumbnail = link
 | 
					 | 
				
			||||||
            elif '/videos/' in link:
 | 
					 | 
				
			||||||
                video_url = link
 | 
					 | 
				
			||||||
        if not video_url:
 | 
					 | 
				
			||||||
            raise ExtractorError('Could not find video URL')
 | 
					 | 
				
			||||||
        ext = '.' + determine_ext(video_url)
 | 
					 | 
				
			||||||
        if ext == title[-len(ext):]:
 | 
					 | 
				
			||||||
            title = title[:-len(ext)]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return {
 | 
					 | 
				
			||||||
            'id': video_id,
 | 
					 | 
				
			||||||
            'url': video_url,
 | 
					 | 
				
			||||||
            'title': title,
 | 
					 | 
				
			||||||
            'thumbnail': thumbnail,
 | 
					 | 
				
			||||||
            'http_referer': 'http://www.auengine.com/flowplayer/flowplayer.commercial-3.2.14.swf',
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
@@ -50,7 +50,7 @@ class BambuserIE(InfoExtractor):
 | 
				
			|||||||
            'duration': int(info['length']),
 | 
					            'duration': int(info['length']),
 | 
				
			||||||
            'view_count': int(info['views_total']),
 | 
					            'view_count': int(info['views_total']),
 | 
				
			||||||
            'uploader': info['username'],
 | 
					            'uploader': info['username'],
 | 
				
			||||||
            'uploader_id': info['uid'],
 | 
					            'uploader_id': info['owner']['uid'],
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -72,26 +72,29 @@ class BandcampIE(InfoExtractor):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        download_link = m_download.group(1)
 | 
					        download_link = m_download.group(1)
 | 
				
			||||||
        video_id = self._search_regex(
 | 
					        video_id = self._search_regex(
 | 
				
			||||||
            r'var TralbumData = {.*?id: (?P<id>\d+),?$',
 | 
					            r'(?ms)var TralbumData = {.*?id: (?P<id>\d+),?$',
 | 
				
			||||||
            webpage, 'video id', flags=re.MULTILINE | re.DOTALL)
 | 
					            webpage, 'video id')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        download_webpage = self._download_webpage(download_link, video_id, 'Downloading free downloads page')
 | 
					        download_webpage = self._download_webpage(download_link, video_id, 'Downloading free downloads page')
 | 
				
			||||||
        # We get the dictionary of the track from some javascript code
 | 
					        # We get the dictionary of the track from some javascript code
 | 
				
			||||||
        info = re.search(r'items: (.*?),$', download_webpage, re.MULTILINE).group(1)
 | 
					        all_info = self._parse_json(self._search_regex(
 | 
				
			||||||
        info = json.loads(info)[0]
 | 
					            r'(?sm)items: (.*?),$', download_webpage, 'items'), video_id)
 | 
				
			||||||
 | 
					        info = all_info[0]
 | 
				
			||||||
        # We pick mp3-320 for now, until format selection can be easily implemented.
 | 
					        # We pick mp3-320 for now, until format selection can be easily implemented.
 | 
				
			||||||
        mp3_info = info['downloads']['mp3-320']
 | 
					        mp3_info = info['downloads']['mp3-320']
 | 
				
			||||||
        # If we try to use this url it says the link has expired
 | 
					        # If we try to use this url it says the link has expired
 | 
				
			||||||
        initial_url = mp3_info['url']
 | 
					        initial_url = mp3_info['url']
 | 
				
			||||||
        re_url = r'(?P<server>http://(.*?)\.bandcamp\.com)/download/track\?enc=mp3-320&fsig=(?P<fsig>.*?)&id=(?P<id>.*?)&ts=(?P<ts>.*)$'
 | 
					        m_url = re.match(
 | 
				
			||||||
        m_url = re.match(re_url, initial_url)
 | 
					            r'(?P<server>http://(.*?)\.bandcamp\.com)/download/track\?enc=mp3-320&fsig=(?P<fsig>.*?)&id=(?P<id>.*?)&ts=(?P<ts>.*)$',
 | 
				
			||||||
 | 
					            initial_url)
 | 
				
			||||||
        # We build the url we will use to get the final track url
 | 
					        # We build the url we will use to get the final track url
 | 
				
			||||||
        # This url is build in Bandcamp in the script download_bunde_*.js
 | 
					        # This url is build in Bandcamp in the script download_bunde_*.js
 | 
				
			||||||
        request_url = '%s/statdownload/track?enc=mp3-320&fsig=%s&id=%s&ts=%s&.rand=665028774616&.vrs=1' % (m_url.group('server'), m_url.group('fsig'), video_id, m_url.group('ts'))
 | 
					        request_url = '%s/statdownload/track?enc=mp3-320&fsig=%s&id=%s&ts=%s&.rand=665028774616&.vrs=1' % (m_url.group('server'), m_url.group('fsig'), video_id, m_url.group('ts'))
 | 
				
			||||||
        final_url_webpage = self._download_webpage(request_url, video_id, 'Requesting download url')
 | 
					        final_url_webpage = self._download_webpage(request_url, video_id, 'Requesting download url')
 | 
				
			||||||
        # If we could correctly generate the .rand field the url would be
 | 
					        # If we could correctly generate the .rand field the url would be
 | 
				
			||||||
        # in the "download_url" key
 | 
					        # in the "download_url" key
 | 
				
			||||||
        final_url = re.search(r'"retry_url":"(.*?)"', final_url_webpage).group(1)
 | 
					        final_url = self._search_regex(
 | 
				
			||||||
 | 
					            r'"retry_url":"(.*?)"', final_url_webpage, 'final video URL')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            'id': video_id,
 | 
					            'id': video_id,
 | 
				
			||||||
@@ -106,7 +109,7 @@ class BandcampIE(InfoExtractor):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class BandcampAlbumIE(InfoExtractor):
 | 
					class BandcampAlbumIE(InfoExtractor):
 | 
				
			||||||
    IE_NAME = 'Bandcamp:album'
 | 
					    IE_NAME = 'Bandcamp:album'
 | 
				
			||||||
    _VALID_URL = r'https?://(?:(?P<subdomain>[^.]+)\.)?bandcamp\.com(?:/album/(?P<title>[^?#]+)|/?(?:$|[?#]))'
 | 
					    _VALID_URL = r'https?://(?:(?P<subdomain>[^.]+)\.)?bandcamp\.com(?:/album/(?P<album_id>[^?#]+)|/?(?:$|[?#]))'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    _TESTS = [{
 | 
					    _TESTS = [{
 | 
				
			||||||
        'url': 'http://blazo.bandcamp.com/album/jazz-format-mixtape-vol-1',
 | 
					        'url': 'http://blazo.bandcamp.com/album/jazz-format-mixtape-vol-1',
 | 
				
			||||||
@@ -130,42 +133,49 @@ class BandcampAlbumIE(InfoExtractor):
 | 
				
			|||||||
        ],
 | 
					        ],
 | 
				
			||||||
        'info_dict': {
 | 
					        'info_dict': {
 | 
				
			||||||
            'title': 'Jazz Format Mixtape vol.1',
 | 
					            'title': 'Jazz Format Mixtape vol.1',
 | 
				
			||||||
 | 
					            'id': 'jazz-format-mixtape-vol-1',
 | 
				
			||||||
 | 
					            'uploader_id': 'blazo',
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        'params': {
 | 
					        'params': {
 | 
				
			||||||
            'playlistend': 2
 | 
					            'playlistend': 2
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        'skip': 'Bandcamp imposes download limits. See test_playlists:test_bandcamp_album for the playlist test'
 | 
					        'skip': 'Bandcamp imposes download limits.'
 | 
				
			||||||
    }, {
 | 
					    }, {
 | 
				
			||||||
        'url': 'http://nightbringer.bandcamp.com/album/hierophany-of-the-open-grave',
 | 
					        'url': 'http://nightbringer.bandcamp.com/album/hierophany-of-the-open-grave',
 | 
				
			||||||
        'info_dict': {
 | 
					        'info_dict': {
 | 
				
			||||||
            'title': 'Hierophany of the Open Grave',
 | 
					            'title': 'Hierophany of the Open Grave',
 | 
				
			||||||
 | 
					            'uploader_id': 'nightbringer',
 | 
				
			||||||
 | 
					            'id': 'hierophany-of-the-open-grave',
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        'playlist_mincount': 9,
 | 
					        'playlist_mincount': 9,
 | 
				
			||||||
    }, {
 | 
					    }, {
 | 
				
			||||||
        'url': 'http://dotscale.bandcamp.com',
 | 
					        'url': 'http://dotscale.bandcamp.com',
 | 
				
			||||||
        'info_dict': {
 | 
					        'info_dict': {
 | 
				
			||||||
            'title': 'Loom',
 | 
					            'title': 'Loom',
 | 
				
			||||||
 | 
					            'id': 'dotscale',
 | 
				
			||||||
 | 
					            'uploader_id': 'dotscale',
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        'playlist_mincount': 7,
 | 
					        'playlist_mincount': 7,
 | 
				
			||||||
    }]
 | 
					    }]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _real_extract(self, url):
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
        mobj = re.match(self._VALID_URL, url)
 | 
					        mobj = re.match(self._VALID_URL, url)
 | 
				
			||||||
        playlist_id = mobj.group('subdomain')
 | 
					        uploader_id = mobj.group('subdomain')
 | 
				
			||||||
        title = mobj.group('title')
 | 
					        album_id = mobj.group('album_id')
 | 
				
			||||||
        display_id = title or playlist_id
 | 
					        playlist_id = album_id or uploader_id
 | 
				
			||||||
        webpage = self._download_webpage(url, display_id)
 | 
					        webpage = self._download_webpage(url, playlist_id)
 | 
				
			||||||
        tracks_paths = re.findall(r'<a href="(.*?)" itemprop="url">', webpage)
 | 
					        tracks_paths = re.findall(r'<a href="(.*?)" itemprop="url">', webpage)
 | 
				
			||||||
        if not tracks_paths:
 | 
					        if not tracks_paths:
 | 
				
			||||||
            raise ExtractorError('The page doesn\'t contain any tracks')
 | 
					            raise ExtractorError('The page doesn\'t contain any tracks')
 | 
				
			||||||
        entries = [
 | 
					        entries = [
 | 
				
			||||||
            self.url_result(compat_urlparse.urljoin(url, t_path), ie=BandcampIE.ie_key())
 | 
					            self.url_result(compat_urlparse.urljoin(url, t_path), ie=BandcampIE.ie_key())
 | 
				
			||||||
            for t_path in tracks_paths]
 | 
					            for t_path in tracks_paths]
 | 
				
			||||||
        title = self._search_regex(r'album_title : "(.*?)"', webpage, 'title')
 | 
					        title = self._search_regex(
 | 
				
			||||||
 | 
					            r'album_title\s*:\s*"(.*?)"', webpage, 'title', fatal=False)
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            '_type': 'playlist',
 | 
					            '_type': 'playlist',
 | 
				
			||||||
 | 
					            'uploader_id': uploader_id,
 | 
				
			||||||
            'id': playlist_id,
 | 
					            'id': playlist_id,
 | 
				
			||||||
            'display_id': display_id,
 | 
					 | 
				
			||||||
            'title': title,
 | 
					            'title': title,
 | 
				
			||||||
            'entries': entries,
 | 
					            'entries': entries,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,15 +2,15 @@ from __future__ import unicode_literals
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import xml.etree.ElementTree
 | 
					import xml.etree.ElementTree
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .subtitles import SubtitlesInfoExtractor
 | 
					from .common import InfoExtractor
 | 
				
			||||||
from ..utils import ExtractorError
 | 
					from ..utils import ExtractorError
 | 
				
			||||||
from ..compat import compat_HTTPError
 | 
					from ..compat import compat_HTTPError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BBCCoUkIE(SubtitlesInfoExtractor):
 | 
					class BBCCoUkIE(InfoExtractor):
 | 
				
			||||||
    IE_NAME = 'bbc.co.uk'
 | 
					    IE_NAME = 'bbc.co.uk'
 | 
				
			||||||
    IE_DESC = 'BBC iPlayer'
 | 
					    IE_DESC = 'BBC iPlayer'
 | 
				
			||||||
    _VALID_URL = r'https?://(?:www\.)?bbc\.co\.uk/(?:programmes|iplayer/episode)/(?P<id>[\da-z]{8})'
 | 
					    _VALID_URL = r'https?://(?:www\.)?bbc\.co\.uk/(?:(?:(?:programmes|iplayer(?:/[^/]+)?/(?:episode|playlist))/)|music/clips[/#])(?P<id>[\da-z]{8})'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    _TESTS = [
 | 
					    _TESTS = [
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@@ -18,8 +18,8 @@ class BBCCoUkIE(SubtitlesInfoExtractor):
 | 
				
			|||||||
            'info_dict': {
 | 
					            'info_dict': {
 | 
				
			||||||
                'id': 'b039d07m',
 | 
					                'id': 'b039d07m',
 | 
				
			||||||
                'ext': 'flv',
 | 
					                'ext': 'flv',
 | 
				
			||||||
                'title': 'Kaleidoscope: Leonard Cohen',
 | 
					                'title': 'Kaleidoscope, Leonard Cohen',
 | 
				
			||||||
                'description': 'md5:db4755d7a665ae72343779f7dacb402c',
 | 
					                'description': 'The Canadian poet and songwriter reflects on his musical career.',
 | 
				
			||||||
                'duration': 1740,
 | 
					                'duration': 1740,
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            'params': {
 | 
					            'params': {
 | 
				
			||||||
@@ -71,7 +71,57 @@ class BBCCoUkIE(SubtitlesInfoExtractor):
 | 
				
			|||||||
                'skip_download': True,
 | 
					                'skip_download': True,
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            'skip': 'Currently BBC iPlayer TV programmes are available to play in the UK only',
 | 
					            'skip': 'Currently BBC iPlayer TV programmes are available to play in the UK only',
 | 
				
			||||||
        },
 | 
					        }, {
 | 
				
			||||||
 | 
					            'url': 'http://www.bbc.co.uk/programmes/b04v20dw',
 | 
				
			||||||
 | 
					            'info_dict': {
 | 
				
			||||||
 | 
					                'id': 'b04v209v',
 | 
				
			||||||
 | 
					                'ext': 'flv',
 | 
				
			||||||
 | 
					                'title': 'Pete Tong, The Essential New Tune Special',
 | 
				
			||||||
 | 
					                'description': "Pete has a very special mix - all of 2014's Essential New Tunes!",
 | 
				
			||||||
 | 
					                'duration': 10800,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            'params': {
 | 
				
			||||||
 | 
					                # rtmp download
 | 
				
			||||||
 | 
					                'skip_download': True,
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }, {
 | 
				
			||||||
 | 
					            'url': 'http://www.bbc.co.uk/music/clips/p02frcc3',
 | 
				
			||||||
 | 
					            'note': 'Audio',
 | 
				
			||||||
 | 
					            'info_dict': {
 | 
				
			||||||
 | 
					                'id': 'p02frcch',
 | 
				
			||||||
 | 
					                'ext': 'flv',
 | 
				
			||||||
 | 
					                'title': 'Pete Tong, Past, Present and Future Special, Madeon - After Hours mix',
 | 
				
			||||||
 | 
					                'description': 'French house superstar Madeon takes us out of the club and onto the after party.',
 | 
				
			||||||
 | 
					                'duration': 3507,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            'params': {
 | 
				
			||||||
 | 
					                # rtmp download
 | 
				
			||||||
 | 
					                'skip_download': True,
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }, {
 | 
				
			||||||
 | 
					            'url': 'http://www.bbc.co.uk/music/clips/p025c0zz',
 | 
				
			||||||
 | 
					            'note': 'Video',
 | 
				
			||||||
 | 
					            'info_dict': {
 | 
				
			||||||
 | 
					                'id': 'p025c103',
 | 
				
			||||||
 | 
					                'ext': 'flv',
 | 
				
			||||||
 | 
					                'title': 'Reading and Leeds Festival, 2014, Rae Morris - Closer (Live on BBC Three)',
 | 
				
			||||||
 | 
					                'description': 'Rae Morris performs Closer for BBC Three at Reading 2014',
 | 
				
			||||||
 | 
					                'duration': 226,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            'params': {
 | 
				
			||||||
 | 
					                # rtmp download
 | 
				
			||||||
 | 
					                'skip_download': True,
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }, {
 | 
				
			||||||
 | 
					            'url': 'http://www.bbc.co.uk/iplayer/playlist/p01dvks4',
 | 
				
			||||||
 | 
					            'only_matching': True,
 | 
				
			||||||
 | 
					        }, {
 | 
				
			||||||
 | 
					            'url': 'http://www.bbc.co.uk/music/clips#p02frcc3',
 | 
				
			||||||
 | 
					            'only_matching': True,
 | 
				
			||||||
 | 
					        }, {
 | 
				
			||||||
 | 
					            'url': 'http://www.bbc.co.uk/iplayer/cbeebies/episode/b0480276/bing-14-atchoo',
 | 
				
			||||||
 | 
					            'only_matching': True,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _extract_asx_playlist(self, connection, programme_id):
 | 
					    def _extract_asx_playlist(self, connection, programme_id):
 | 
				
			||||||
@@ -165,17 +215,32 @@ class BBCCoUkIE(SubtitlesInfoExtractor):
 | 
				
			|||||||
            formats.extend(conn_formats)
 | 
					            formats.extend(conn_formats)
 | 
				
			||||||
        return formats
 | 
					        return formats
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _extract_captions(self, media, programme_id):
 | 
					    def _get_subtitles(self, media, programme_id):
 | 
				
			||||||
        subtitles = {}
 | 
					        subtitles = {}
 | 
				
			||||||
        for connection in self._extract_connections(media):
 | 
					        for connection in self._extract_connections(media):
 | 
				
			||||||
            captions = self._download_xml(connection.get('href'), programme_id, 'Downloading captions')
 | 
					            captions = self._download_xml(connection.get('href'), programme_id, 'Downloading captions')
 | 
				
			||||||
            lang = captions.get('{http://www.w3.org/XML/1998/namespace}lang', 'en')
 | 
					            lang = captions.get('{http://www.w3.org/XML/1998/namespace}lang', 'en')
 | 
				
			||||||
            ps = captions.findall('./{0}body/{0}div/{0}p'.format('{http://www.w3.org/2006/10/ttaf1}'))
 | 
					            ps = captions.findall('./{0}body/{0}div/{0}p'.format('{http://www.w3.org/2006/10/ttaf1}'))
 | 
				
			||||||
            srt = ''
 | 
					            srt = ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            def _extract_text(p):
 | 
				
			||||||
 | 
					                if p.text is not None:
 | 
				
			||||||
 | 
					                    stripped_text = p.text.strip()
 | 
				
			||||||
 | 
					                    if stripped_text:
 | 
				
			||||||
 | 
					                        return stripped_text
 | 
				
			||||||
 | 
					                return ' '.join(span.text.strip() for span in p.findall('{http://www.w3.org/2006/10/ttaf1}span'))
 | 
				
			||||||
            for pos, p in enumerate(ps):
 | 
					            for pos, p in enumerate(ps):
 | 
				
			||||||
                srt += '%s\r\n%s --> %s\r\n%s\r\n\r\n' % (str(pos), p.get('begin'), p.get('end'),
 | 
					                srt += '%s\r\n%s --> %s\r\n%s\r\n\r\n' % (str(pos), p.get('begin'), p.get('end'), _extract_text(p))
 | 
				
			||||||
                                                          p.text.strip() if p.text is not None else '')
 | 
					            subtitles[lang] = [
 | 
				
			||||||
            subtitles[lang] = srt
 | 
					                {
 | 
				
			||||||
 | 
					                    'url': connection.get('href'),
 | 
				
			||||||
 | 
					                    'ext': 'ttml',
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    'data': srt,
 | 
				
			||||||
 | 
					                    'ext': 'srt',
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
        return subtitles
 | 
					        return subtitles
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _download_media_selector(self, programme_id):
 | 
					    def _download_media_selector(self, programme_id):
 | 
				
			||||||
@@ -199,10 +264,63 @@ class BBCCoUkIE(SubtitlesInfoExtractor):
 | 
				
			|||||||
            elif kind == 'video':
 | 
					            elif kind == 'video':
 | 
				
			||||||
                formats.extend(self._extract_video(media, programme_id))
 | 
					                formats.extend(self._extract_video(media, programme_id))
 | 
				
			||||||
            elif kind == 'captions':
 | 
					            elif kind == 'captions':
 | 
				
			||||||
                subtitles = self._extract_captions(media, programme_id)
 | 
					                subtitles = self.extract_subtitles(media, programme_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return formats, subtitles
 | 
					        return formats, subtitles
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _download_playlist(self, playlist_id):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            playlist = self._download_json(
 | 
				
			||||||
 | 
					                'http://www.bbc.co.uk/programmes/%s/playlist.json' % playlist_id,
 | 
				
			||||||
 | 
					                playlist_id, 'Downloading playlist JSON')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            version = playlist.get('defaultAvailableVersion')
 | 
				
			||||||
 | 
					            if version:
 | 
				
			||||||
 | 
					                smp_config = version['smpConfig']
 | 
				
			||||||
 | 
					                title = smp_config['title']
 | 
				
			||||||
 | 
					                description = smp_config['summary']
 | 
				
			||||||
 | 
					                for item in smp_config['items']:
 | 
				
			||||||
 | 
					                    kind = item['kind']
 | 
				
			||||||
 | 
					                    if kind != 'programme' and kind != 'radioProgramme':
 | 
				
			||||||
 | 
					                        continue
 | 
				
			||||||
 | 
					                    programme_id = item.get('vpid')
 | 
				
			||||||
 | 
					                    duration = int(item.get('duration'))
 | 
				
			||||||
 | 
					                    formats, subtitles = self._download_media_selector(programme_id)
 | 
				
			||||||
 | 
					                return programme_id, title, description, duration, formats, subtitles
 | 
				
			||||||
 | 
					        except ExtractorError as ee:
 | 
				
			||||||
 | 
					            if not (isinstance(ee.cause, compat_HTTPError) and ee.cause.code == 404):
 | 
				
			||||||
 | 
					                raise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # fallback to legacy playlist
 | 
				
			||||||
 | 
					        playlist = self._download_xml(
 | 
				
			||||||
 | 
					            'http://www.bbc.co.uk/iplayer/playlist/%s' % playlist_id,
 | 
				
			||||||
 | 
					            playlist_id, 'Downloading legacy playlist XML')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        no_items = playlist.find('./{http://bbc.co.uk/2008/emp/playlist}noItems')
 | 
				
			||||||
 | 
					        if no_items is not None:
 | 
				
			||||||
 | 
					            reason = no_items.get('reason')
 | 
				
			||||||
 | 
					            if reason == 'preAvailability':
 | 
				
			||||||
 | 
					                msg = 'Episode %s is not yet available' % playlist_id
 | 
				
			||||||
 | 
					            elif reason == 'postAvailability':
 | 
				
			||||||
 | 
					                msg = 'Episode %s is no longer available' % playlist_id
 | 
				
			||||||
 | 
					            elif reason == 'noMedia':
 | 
				
			||||||
 | 
					                msg = 'Episode %s is not currently available' % playlist_id
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                msg = 'Episode %s is not available: %s' % (playlist_id, reason)
 | 
				
			||||||
 | 
					            raise ExtractorError(msg, expected=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for item in self._extract_items(playlist):
 | 
				
			||||||
 | 
					            kind = item.get('kind')
 | 
				
			||||||
 | 
					            if kind != 'programme' and kind != 'radioProgramme':
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            title = playlist.find('./{http://bbc.co.uk/2008/emp/playlist}title').text
 | 
				
			||||||
 | 
					            description = playlist.find('./{http://bbc.co.uk/2008/emp/playlist}summary').text
 | 
				
			||||||
 | 
					            programme_id = item.get('identifier')
 | 
				
			||||||
 | 
					            duration = int(item.get('duration'))
 | 
				
			||||||
 | 
					            formats, subtitles = self._download_media_selector(programme_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return programme_id, title, description, duration, formats, subtitles
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _real_extract(self, url):
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
        group_id = self._match_id(url)
 | 
					        group_id = self._match_id(url)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -219,36 +337,7 @@ class BBCCoUkIE(SubtitlesInfoExtractor):
 | 
				
			|||||||
            duration = player['duration']
 | 
					            duration = player['duration']
 | 
				
			||||||
            formats, subtitles = self._download_media_selector(programme_id)
 | 
					            formats, subtitles = self._download_media_selector(programme_id)
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            playlist = self._download_xml(
 | 
					            programme_id, title, description, duration, formats, subtitles = self._download_playlist(group_id)
 | 
				
			||||||
                'http://www.bbc.co.uk/iplayer/playlist/%s' % group_id,
 | 
					 | 
				
			||||||
                group_id, 'Downloading playlist XML')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            no_items = playlist.find('./{http://bbc.co.uk/2008/emp/playlist}noItems')
 | 
					 | 
				
			||||||
            if no_items is not None:
 | 
					 | 
				
			||||||
                reason = no_items.get('reason')
 | 
					 | 
				
			||||||
                if reason == 'preAvailability':
 | 
					 | 
				
			||||||
                    msg = 'Episode %s is not yet available' % group_id
 | 
					 | 
				
			||||||
                elif reason == 'postAvailability':
 | 
					 | 
				
			||||||
                    msg = 'Episode %s is no longer available' % group_id
 | 
					 | 
				
			||||||
                elif reason == 'noMedia':
 | 
					 | 
				
			||||||
                    msg = 'Episode %s is not currently available' % group_id
 | 
					 | 
				
			||||||
                else:
 | 
					 | 
				
			||||||
                    msg = 'Episode %s is not available: %s' % (group_id, reason)
 | 
					 | 
				
			||||||
                raise ExtractorError(msg, expected=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            for item in self._extract_items(playlist):
 | 
					 | 
				
			||||||
                kind = item.get('kind')
 | 
					 | 
				
			||||||
                if kind != 'programme' and kind != 'radioProgramme':
 | 
					 | 
				
			||||||
                    continue
 | 
					 | 
				
			||||||
                title = playlist.find('./{http://bbc.co.uk/2008/emp/playlist}title').text
 | 
					 | 
				
			||||||
                description = playlist.find('./{http://bbc.co.uk/2008/emp/playlist}summary').text
 | 
					 | 
				
			||||||
                programme_id = item.get('identifier')
 | 
					 | 
				
			||||||
                duration = int(item.get('duration'))
 | 
					 | 
				
			||||||
                formats, subtitles = self._download_media_selector(programme_id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if self._downloader.params.get('listsubtitles', False):
 | 
					 | 
				
			||||||
            self._list_available_subtitles(programme_id, subtitles)
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self._sort_formats(formats)
 | 
					        self._sort_formats(formats)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,7 +9,7 @@ class BeegIE(InfoExtractor):
 | 
				
			|||||||
    _VALID_URL = r'https?://(?:www\.)?beeg\.com/(?P<id>\d+)'
 | 
					    _VALID_URL = r'https?://(?:www\.)?beeg\.com/(?P<id>\d+)'
 | 
				
			||||||
    _TEST = {
 | 
					    _TEST = {
 | 
				
			||||||
        'url': 'http://beeg.com/5416503',
 | 
					        'url': 'http://beeg.com/5416503',
 | 
				
			||||||
        'md5': '634526ae978711f6b748fe0dd6c11f57',
 | 
					        'md5': '1bff67111adb785c51d1b42959ec10e5',
 | 
				
			||||||
        'info_dict': {
 | 
					        'info_dict': {
 | 
				
			||||||
            'id': '5416503',
 | 
					            'id': '5416503',
 | 
				
			||||||
            'ext': 'mp4',
 | 
					            'ext': 'mp4',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,7 +16,7 @@ class BetIE(InfoExtractor):
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            'url': 'http://www.bet.com/news/politics/2014/12/08/in-bet-exclusive-obama-talks-race-and-racism.html',
 | 
					            'url': 'http://www.bet.com/news/politics/2014/12/08/in-bet-exclusive-obama-talks-race-and-racism.html',
 | 
				
			||||||
            'info_dict': {
 | 
					            'info_dict': {
 | 
				
			||||||
                'id': '417cd61c-c793-4e8e-b006-e445ecc45add',
 | 
					                'id': '740ab250-bb94-4a8a-8787-fe0de7c74471',
 | 
				
			||||||
                'display_id': 'in-bet-exclusive-obama-talks-race-and-racism',
 | 
					                'display_id': 'in-bet-exclusive-obama-talks-race-and-racism',
 | 
				
			||||||
                'ext': 'flv',
 | 
					                'ext': 'flv',
 | 
				
			||||||
                'title': 'BET News Presents: A Conversation With President Obama',
 | 
					                'title': 'BET News Presents: A Conversation With President Obama',
 | 
				
			||||||
@@ -35,7 +35,7 @@ class BetIE(InfoExtractor):
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            'url': 'http://www.bet.com/video/news/national/2014/justice-for-ferguson-a-community-reacts.html',
 | 
					            'url': 'http://www.bet.com/video/news/national/2014/justice-for-ferguson-a-community-reacts.html',
 | 
				
			||||||
            'info_dict': {
 | 
					            'info_dict': {
 | 
				
			||||||
                'id': '4160e53b-ad41-43b1-980f-8d85f63121f4',
 | 
					                'id': 'bcd1b1df-673a-42cf-8d01-b282db608f2d',
 | 
				
			||||||
                'display_id': 'justice-for-ferguson-a-community-reacts',
 | 
					                'display_id': 'justice-for-ferguson-a-community-reacts',
 | 
				
			||||||
                'ext': 'flv',
 | 
					                'ext': 'flv',
 | 
				
			||||||
                'title': 'Justice for Ferguson: A Community Reacts',
 | 
					                'title': 'Justice for Ferguson: A Community Reacts',
 | 
				
			||||||
@@ -55,7 +55,6 @@ class BetIE(InfoExtractor):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def _real_extract(self, url):
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
        display_id = self._match_id(url)
 | 
					        display_id = self._match_id(url)
 | 
				
			||||||
 | 
					 | 
				
			||||||
        webpage = self._download_webpage(url, display_id)
 | 
					        webpage = self._download_webpage(url, display_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        media_url = compat_urllib_parse.unquote(self._search_regex(
 | 
					        media_url = compat_urllib_parse.unquote(self._search_regex(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,9 +4,7 @@ from __future__ import unicode_literals
 | 
				
			|||||||
import re
 | 
					import re
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .common import InfoExtractor
 | 
					from .common import InfoExtractor
 | 
				
			||||||
from ..compat import compat_parse_qs
 | 
					 | 
				
			||||||
from ..utils import (
 | 
					from ..utils import (
 | 
				
			||||||
    ExtractorError,
 | 
					 | 
				
			||||||
    int_or_none,
 | 
					    int_or_none,
 | 
				
			||||||
    unified_strdate,
 | 
					    unified_strdate,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@@ -54,45 +52,38 @@ class BiliBiliIE(InfoExtractor):
 | 
				
			|||||||
        thumbnail = self._html_search_meta(
 | 
					        thumbnail = self._html_search_meta(
 | 
				
			||||||
            'thumbnailUrl', video_code, 'thumbnail', fatal=False)
 | 
					            'thumbnailUrl', video_code, 'thumbnail', fatal=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        player_params = compat_parse_qs(self._html_search_regex(
 | 
					        cid = self._search_regex(r'cid=(\d+)', webpage, 'cid')
 | 
				
			||||||
            r'<iframe .*?class="player" src="https://secure\.bilibili\.(?:tv|com)/secure,([^"]+)"',
 | 
					 | 
				
			||||||
            webpage, 'player params'))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if 'cid' in player_params:
 | 
					        lq_doc = self._download_xml(
 | 
				
			||||||
            cid = player_params['cid'][0]
 | 
					            'http://interface.bilibili.com/v_cdn_play?appkey=1&cid=%s' % cid,
 | 
				
			||||||
 | 
					            video_id,
 | 
				
			||||||
 | 
					            note='Downloading LQ video info'
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        lq_durl = lq_doc.find('./durl')
 | 
				
			||||||
 | 
					        formats = [{
 | 
				
			||||||
 | 
					            'format_id': 'lq',
 | 
				
			||||||
 | 
					            'quality': 1,
 | 
				
			||||||
 | 
					            'url': lq_durl.find('./url').text,
 | 
				
			||||||
 | 
					            'filesize': int_or_none(
 | 
				
			||||||
 | 
					                lq_durl.find('./size'), get_attr='text'),
 | 
				
			||||||
 | 
					        }]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            lq_doc = self._download_xml(
 | 
					        hq_doc = self._download_xml(
 | 
				
			||||||
                'http://interface.bilibili.cn/v_cdn_play?cid=%s' % cid,
 | 
					            'http://interface.bilibili.com/playurl?appkey=1&cid=%s' % cid,
 | 
				
			||||||
                video_id,
 | 
					            video_id,
 | 
				
			||||||
                note='Downloading LQ video info'
 | 
					            note='Downloading HQ video info',
 | 
				
			||||||
            )
 | 
					            fatal=False,
 | 
				
			||||||
            lq_durl = lq_doc.find('.//durl')
 | 
					        )
 | 
				
			||||||
            formats = [{
 | 
					        if hq_doc is not False:
 | 
				
			||||||
                'format_id': 'lq',
 | 
					            hq_durl = hq_doc.find('./durl')
 | 
				
			||||||
                'quality': 1,
 | 
					            formats.append({
 | 
				
			||||||
                'url': lq_durl.find('./url').text,
 | 
					                'format_id': 'hq',
 | 
				
			||||||
 | 
					                'quality': 2,
 | 
				
			||||||
 | 
					                'ext': 'flv',
 | 
				
			||||||
 | 
					                'url': hq_durl.find('./url').text,
 | 
				
			||||||
                'filesize': int_or_none(
 | 
					                'filesize': int_or_none(
 | 
				
			||||||
                    lq_durl.find('./size'), get_attr='text'),
 | 
					                    hq_durl.find('./size'), get_attr='text'),
 | 
				
			||||||
            }]
 | 
					            })
 | 
				
			||||||
 | 
					 | 
				
			||||||
            hq_doc = self._download_xml(
 | 
					 | 
				
			||||||
                'http://interface.bilibili.cn/playurl?cid=%s' % cid,
 | 
					 | 
				
			||||||
                video_id,
 | 
					 | 
				
			||||||
                note='Downloading HQ video info',
 | 
					 | 
				
			||||||
                fatal=False,
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            if hq_doc is not False:
 | 
					 | 
				
			||||||
                hq_durl = hq_doc.find('.//durl')
 | 
					 | 
				
			||||||
                formats.append({
 | 
					 | 
				
			||||||
                    'format_id': 'hq',
 | 
					 | 
				
			||||||
                    'quality': 2,
 | 
					 | 
				
			||||||
                    'ext': 'flv',
 | 
					 | 
				
			||||||
                    'url': hq_durl.find('./url').text,
 | 
					 | 
				
			||||||
                    'filesize': int_or_none(
 | 
					 | 
				
			||||||
                        hq_durl.find('./size'), get_attr='text'),
 | 
					 | 
				
			||||||
                })
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            raise ExtractorError('Unsupported player parameters: %r' % (player_params,))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self._sort_formats(formats)
 | 
					        self._sort_formats(formats)
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,40 +1,35 @@
 | 
				
			|||||||
from __future__ import unicode_literals
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import json
 | 
					import json
 | 
				
			||||||
import re
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .common import InfoExtractor
 | 
					from .common import InfoExtractor
 | 
				
			||||||
from ..utils import remove_start
 | 
					from ..utils import (
 | 
				
			||||||
 | 
					    remove_start,
 | 
				
			||||||
 | 
					    int_or_none,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BlinkxIE(InfoExtractor):
 | 
					class BlinkxIE(InfoExtractor):
 | 
				
			||||||
    _VALID_URL = r'^(?:https?://(?:www\.)blinkx\.com/#?ce/|blinkx:)(?P<id>[^?]+)'
 | 
					    _VALID_URL = r'(?:https?://(?:www\.)blinkx\.com/#?ce/|blinkx:)(?P<id>[^?]+)'
 | 
				
			||||||
    IE_NAME = 'blinkx'
 | 
					    IE_NAME = 'blinkx'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    _TEST = {
 | 
					    _TEST = {
 | 
				
			||||||
        'url': 'http://www.blinkx.com/ce/8aQUy7GVFYgFzpKhT0oqsilwOGFRVXk3R1ZGWWdGenBLaFQwb3FzaWx3OGFRVXk3R1ZGWWdGenB',
 | 
					        'url': 'http://www.blinkx.com/ce/Da0Gw3xc5ucpNduzLuDDlv4WC9PuI4fDi1-t6Y3LyfdY2SZS5Urbvn-UPJvrvbo8LTKTc67Wu2rPKSQDJyZeeORCR8bYkhs8lI7eqddznH2ofh5WEEdjYXnoRtj7ByQwt7atMErmXIeYKPsSDuMAAqJDlQZ-3Ff4HJVeH_s3Gh8oQ',
 | 
				
			||||||
        'md5': '2e9a07364af40163a908edbf10bb2492',
 | 
					        'md5': '337cf7a344663ec79bf93a526a2e06c7',
 | 
				
			||||||
        'info_dict': {
 | 
					        'info_dict': {
 | 
				
			||||||
            'id': '8aQUy7GV',
 | 
					            'id': 'Da0Gw3xc',
 | 
				
			||||||
            'ext': 'mp4',
 | 
					            'ext': 'mp4',
 | 
				
			||||||
            'title': 'Police Car Rolls Away',
 | 
					            'title': 'No Daily Show for John Oliver; HBO Show Renewed - IGN News',
 | 
				
			||||||
            'uploader': 'stupidvideos.com',
 | 
					            'uploader': 'IGN News',
 | 
				
			||||||
            'upload_date': '20131215',
 | 
					            'upload_date': '20150217',
 | 
				
			||||||
            'timestamp': 1387068000,
 | 
					            'timestamp': 1424215740,
 | 
				
			||||||
            'description': 'A police car gently rolls away from a fight. Maybe it felt weird being around a confrontation and just had to get out of there!',
 | 
					            'description': 'HBO has renewed Last Week Tonight With John Oliver for two more seasons.',
 | 
				
			||||||
            'duration': 14.886,
 | 
					            'duration': 47.743333,
 | 
				
			||||||
            'thumbnails': [{
 | 
					 | 
				
			||||||
                'width': 100,
 | 
					 | 
				
			||||||
                'height': 76,
 | 
					 | 
				
			||||||
                'resolution': '100x76',
 | 
					 | 
				
			||||||
                'url': 'http://cdn.blinkx.com/stream/b/41/StupidVideos/20131215/1873969261/1873969261_tn_0.jpg',
 | 
					 | 
				
			||||||
            }],
 | 
					 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _real_extract(self, rl):
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
        m = re.match(self._VALID_URL, rl)
 | 
					        video_id = self._match_id(url)
 | 
				
			||||||
        video_id = m.group('id')
 | 
					 | 
				
			||||||
        display_id = video_id[:8]
 | 
					        display_id = video_id[:8]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        api_url = ('https://apib4.blinkx.com/api.php?action=play_video&' +
 | 
					        api_url = ('https://apib4.blinkx.com/api.php?action=play_video&' +
 | 
				
			||||||
@@ -60,18 +55,20 @@ class BlinkxIE(InfoExtractor):
 | 
				
			|||||||
            elif m['type'] in ('flv', 'mp4'):
 | 
					            elif m['type'] in ('flv', 'mp4'):
 | 
				
			||||||
                vcodec = remove_start(m['vcodec'], 'ff')
 | 
					                vcodec = remove_start(m['vcodec'], 'ff')
 | 
				
			||||||
                acodec = remove_start(m['acodec'], 'ff')
 | 
					                acodec = remove_start(m['acodec'], 'ff')
 | 
				
			||||||
                tbr = (int(m['vbr']) + int(m['abr'])) // 1000
 | 
					                vbr = int_or_none(m.get('vbr') or m.get('vbitrate'), 1000)
 | 
				
			||||||
 | 
					                abr = int_or_none(m.get('abr') or m.get('abitrate'), 1000)
 | 
				
			||||||
 | 
					                tbr = vbr + abr if vbr and abr else None
 | 
				
			||||||
                format_id = '%s-%sk-%s' % (vcodec, tbr, m['w'])
 | 
					                format_id = '%s-%sk-%s' % (vcodec, tbr, m['w'])
 | 
				
			||||||
                formats.append({
 | 
					                formats.append({
 | 
				
			||||||
                    'format_id': format_id,
 | 
					                    'format_id': format_id,
 | 
				
			||||||
                    'url': m['link'],
 | 
					                    'url': m['link'],
 | 
				
			||||||
                    'vcodec': vcodec,
 | 
					                    'vcodec': vcodec,
 | 
				
			||||||
                    'acodec': acodec,
 | 
					                    'acodec': acodec,
 | 
				
			||||||
                    'abr': int(m['abr']) // 1000,
 | 
					                    'abr': abr,
 | 
				
			||||||
                    'vbr': int(m['vbr']) // 1000,
 | 
					                    'vbr': vbr,
 | 
				
			||||||
                    'tbr': tbr,
 | 
					                    'tbr': tbr,
 | 
				
			||||||
                    'width': int(m['w']),
 | 
					                    'width': int_or_none(m.get('w')),
 | 
				
			||||||
                    'height': int(m['h']),
 | 
					                    'height': int_or_none(m.get('h')),
 | 
				
			||||||
                })
 | 
					                })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self._sort_formats(formats)
 | 
					        self._sort_formats(formats)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,7 +3,6 @@ from __future__ import unicode_literals
 | 
				
			|||||||
import re
 | 
					import re
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .common import InfoExtractor
 | 
					from .common import InfoExtractor
 | 
				
			||||||
from .subtitles import SubtitlesInfoExtractor
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
from ..compat import (
 | 
					from ..compat import (
 | 
				
			||||||
    compat_str,
 | 
					    compat_str,
 | 
				
			||||||
@@ -18,7 +17,7 @@ from ..utils import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BlipTVIE(SubtitlesInfoExtractor):
 | 
					class BlipTVIE(InfoExtractor):
 | 
				
			||||||
    _VALID_URL = r'https?://(?:\w+\.)?blip\.tv/(?:(?:.+-|rss/flash/)(?P<id>\d+)|((?:play/|api\.swf#)(?P<lookup_id>[\da-zA-Z+_]+)))'
 | 
					    _VALID_URL = r'https?://(?:\w+\.)?blip\.tv/(?:(?:.+-|rss/flash/)(?P<id>\d+)|((?:play/|api\.swf#)(?P<lookup_id>[\da-zA-Z+_]+)))'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    _TESTS = [
 | 
					    _TESTS = [
 | 
				
			||||||
@@ -143,7 +142,7 @@ class BlipTVIE(SubtitlesInfoExtractor):
 | 
				
			|||||||
        categories = [category.text for category in item.findall('category')]
 | 
					        categories = [category.text for category in item.findall('category')]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        formats = []
 | 
					        formats = []
 | 
				
			||||||
        subtitles = {}
 | 
					        subtitles_urls = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        media_group = item.find(media('group'))
 | 
					        media_group = item.find(media('group'))
 | 
				
			||||||
        for media_content in media_group.findall(media('content')):
 | 
					        for media_content in media_group.findall(media('content')):
 | 
				
			||||||
@@ -161,7 +160,7 @@ class BlipTVIE(SubtitlesInfoExtractor):
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
                lang = role.rpartition('-')[-1].strip().lower()
 | 
					                lang = role.rpartition('-')[-1].strip().lower()
 | 
				
			||||||
                langcode = LANGS.get(lang, lang)
 | 
					                langcode = LANGS.get(lang, lang)
 | 
				
			||||||
                subtitles[langcode] = url
 | 
					                subtitles_urls[langcode] = url
 | 
				
			||||||
            elif media_type.startswith('video/'):
 | 
					            elif media_type.startswith('video/'):
 | 
				
			||||||
                formats.append({
 | 
					                formats.append({
 | 
				
			||||||
                    'url': real_url,
 | 
					                    'url': real_url,
 | 
				
			||||||
@@ -175,11 +174,7 @@ class BlipTVIE(SubtitlesInfoExtractor):
 | 
				
			|||||||
                })
 | 
					                })
 | 
				
			||||||
        self._sort_formats(formats)
 | 
					        self._sort_formats(formats)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # subtitles
 | 
					        subtitles = self.extract_subtitles(video_id, subtitles_urls)
 | 
				
			||||||
        video_subtitles = self.extract_subtitles(video_id, subtitles)
 | 
					 | 
				
			||||||
        if self._downloader.params.get('listsubtitles', False):
 | 
					 | 
				
			||||||
            self._list_available_subtitles(video_id, subtitles)
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            'id': video_id,
 | 
					            'id': video_id,
 | 
				
			||||||
@@ -192,15 +187,22 @@ class BlipTVIE(SubtitlesInfoExtractor):
 | 
				
			|||||||
            'thumbnail': thumbnail,
 | 
					            'thumbnail': thumbnail,
 | 
				
			||||||
            'categories': categories,
 | 
					            'categories': categories,
 | 
				
			||||||
            'formats': formats,
 | 
					            'formats': formats,
 | 
				
			||||||
            'subtitles': video_subtitles,
 | 
					            'subtitles': subtitles,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _download_subtitle_url(self, sub_lang, url):
 | 
					    def _get_subtitles(self, video_id, subtitles_urls):
 | 
				
			||||||
        # For some weird reason, blip.tv serves a video instead of subtitles
 | 
					        subtitles = {}
 | 
				
			||||||
        # when we request with a common UA
 | 
					        for lang, url in subtitles_urls.items():
 | 
				
			||||||
        req = compat_urllib_request.Request(url)
 | 
					            # For some weird reason, blip.tv serves a video instead of subtitles
 | 
				
			||||||
        req.add_header('Youtubedl-user-agent', 'youtube-dl')
 | 
					            # when we request with a common UA
 | 
				
			||||||
        return self._download_webpage(req, None, note=False)
 | 
					            req = compat_urllib_request.Request(url)
 | 
				
			||||||
 | 
					            req.add_header('User-Agent', 'youtube-dl')
 | 
				
			||||||
 | 
					            subtitles[lang] = [{
 | 
				
			||||||
 | 
					                # The extension is 'srt' but it's actually an 'ass' file
 | 
				
			||||||
 | 
					                'ext': 'ass',
 | 
				
			||||||
 | 
					                'data': self._download_webpage(req, None, note=False),
 | 
				
			||||||
 | 
					            }]
 | 
				
			||||||
 | 
					        return subtitles
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BlipTVUserIE(InfoExtractor):
 | 
					class BlipTVUserIE(InfoExtractor):
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,7 +6,7 @@ from .common import InfoExtractor
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BloombergIE(InfoExtractor):
 | 
					class BloombergIE(InfoExtractor):
 | 
				
			||||||
    _VALID_URL = r'https?://www\.bloomberg\.com/video/(?P<name>.+?)\.html'
 | 
					    _VALID_URL = r'https?://www\.bloomberg\.com/video/(?P<id>.+?)\.html'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    _TEST = {
 | 
					    _TEST = {
 | 
				
			||||||
        'url': 'http://www.bloomberg.com/video/shah-s-presentation-on-foreign-exchange-strategies-qurhIVlJSB6hzkVi229d8g.html',
 | 
					        'url': 'http://www.bloomberg.com/video/shah-s-presentation-on-foreign-exchange-strategies-qurhIVlJSB6hzkVi229d8g.html',
 | 
				
			||||||
@@ -20,9 +20,9 @@ class BloombergIE(InfoExtractor):
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _real_extract(self, url):
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
        mobj = re.match(self._VALID_URL, url)
 | 
					        name = self._match_id(url)
 | 
				
			||||||
        name = mobj.group('name')
 | 
					 | 
				
			||||||
        webpage = self._download_webpage(url, name)
 | 
					        webpage = self._download_webpage(url, name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        f4m_url = self._search_regex(
 | 
					        f4m_url = self._search_regex(
 | 
				
			||||||
            r'<source src="(https?://[^"]+\.f4m.*?)"', webpage,
 | 
					            r'<source src="(https?://[^"]+\.f4m.*?)"', webpage,
 | 
				
			||||||
            'f4m url')
 | 
					            'f4m url')
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,7 +25,7 @@ from ..utils import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BrightcoveIE(InfoExtractor):
 | 
					class BrightcoveIE(InfoExtractor):
 | 
				
			||||||
    _VALID_URL = r'https?://.*brightcove\.com/(services|viewer).*?\?(?P<query>.*)'
 | 
					    _VALID_URL = r'(?:https?://.*brightcove\.com/(services|viewer).*?\?|brightcove:)(?P<query>.*)'
 | 
				
			||||||
    _FEDERATED_URL_TEMPLATE = 'http://c.brightcove.com/services/viewer/htmlFederated?%s'
 | 
					    _FEDERATED_URL_TEMPLATE = 'http://c.brightcove.com/services/viewer/htmlFederated?%s'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    _TESTS = [
 | 
					    _TESTS = [
 | 
				
			||||||
@@ -95,6 +95,7 @@ class BrightcoveIE(InfoExtractor):
 | 
				
			|||||||
            'url': 'http://c.brightcove.com/services/viewer/htmlFederated?playerID=3550052898001&playerKey=AQ%7E%7E%2CAAABmA9XpXk%7E%2C-Kp7jNgisre1fG5OdqpAFUTcs0lP_ZoL',
 | 
					            'url': 'http://c.brightcove.com/services/viewer/htmlFederated?playerID=3550052898001&playerKey=AQ%7E%7E%2CAAABmA9XpXk%7E%2C-Kp7jNgisre1fG5OdqpAFUTcs0lP_ZoL',
 | 
				
			||||||
            'info_dict': {
 | 
					            'info_dict': {
 | 
				
			||||||
                'title': 'Sealife',
 | 
					                'title': 'Sealife',
 | 
				
			||||||
 | 
					                'id': '3550319591001',
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            'playlist_mincount': 7,
 | 
					            'playlist_mincount': 7,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
@@ -108,7 +109,7 @@ class BrightcoveIE(InfoExtractor):
 | 
				
			|||||||
        """
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Fix up some stupid HTML, see https://github.com/rg3/youtube-dl/issues/1553
 | 
					        # Fix up some stupid HTML, see https://github.com/rg3/youtube-dl/issues/1553
 | 
				
			||||||
        object_str = re.sub(r'(<param name="[^"]+" value="[^"]+")>',
 | 
					        object_str = re.sub(r'(<param(?:\s+[a-zA-Z0-9_]+="[^"]*")*)>',
 | 
				
			||||||
                            lambda m: m.group(1) + '/>', object_str)
 | 
					                            lambda m: m.group(1) + '/>', object_str)
 | 
				
			||||||
        # Fix up some stupid XML, see https://github.com/rg3/youtube-dl/issues/1608
 | 
					        # Fix up some stupid XML, see https://github.com/rg3/youtube-dl/issues/1608
 | 
				
			||||||
        object_str = object_str.replace('<--', '<!--')
 | 
					        object_str = object_str.replace('<--', '<!--')
 | 
				
			||||||
@@ -247,7 +248,7 @@ class BrightcoveIE(InfoExtractor):
 | 
				
			|||||||
        playlist_info = json_data['videoList']
 | 
					        playlist_info = json_data['videoList']
 | 
				
			||||||
        videos = [self._extract_video_info(video_info) for video_info in playlist_info['mediaCollectionDTO']['videoDTOs']]
 | 
					        videos = [self._extract_video_info(video_info) for video_info in playlist_info['mediaCollectionDTO']['videoDTOs']]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return self.playlist_result(videos, playlist_id=playlist_info['id'],
 | 
					        return self.playlist_result(videos, playlist_id='%s' % playlist_info['id'],
 | 
				
			||||||
                                    playlist_title=playlist_info['mediaCollectionDTO']['displayName'])
 | 
					                                    playlist_title=playlist_info['mediaCollectionDTO']['displayName'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _extract_video_info(self, video_info):
 | 
					    def _extract_video_info(self, video_info):
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -33,7 +33,8 @@ class BuzzFeedIE(InfoExtractor):
 | 
				
			|||||||
            'skip_download': True,  # Got enough YouTube download tests
 | 
					            'skip_download': True,  # Got enough YouTube download tests
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        'info_dict': {
 | 
					        'info_dict': {
 | 
				
			||||||
            'description': 'Munchkin the Teddy Bear is back !',
 | 
					            'id': 'look-at-this-cute-dog-omg',
 | 
				
			||||||
 | 
					            'description': 're:Munchkin the Teddy Bear is back ?!',
 | 
				
			||||||
            'title': 'You Need To Stop What You\'re Doing And Watching This Dog Walk On A Treadmill',
 | 
					            'title': 'You Need To Stop What You\'re Doing And Watching This Dog Walk On A Treadmill',
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        'playlist': [{
 | 
					        'playlist': [{
 | 
				
			||||||
@@ -42,9 +43,9 @@ class BuzzFeedIE(InfoExtractor):
 | 
				
			|||||||
                'ext': 'mp4',
 | 
					                'ext': 'mp4',
 | 
				
			||||||
                'upload_date': '20141124',
 | 
					                'upload_date': '20141124',
 | 
				
			||||||
                'uploader_id': 'CindysMunchkin',
 | 
					                'uploader_id': 'CindysMunchkin',
 | 
				
			||||||
                'description': '© 2014 Munchkin the Shih Tzu\nAll rights reserved\nFacebook: http://facebook.com/MunchkintheShihTzu',
 | 
					                'description': 're:© 2014 Munchkin the',
 | 
				
			||||||
                'uploader': 'Munchkin the Shih Tzu',
 | 
					                'uploader': 're:^Munchkin the',
 | 
				
			||||||
                'title': 'Munchkin the Teddy Bear gets her exercise',
 | 
					                'title': 're:Munchkin the Teddy Bear gets her exercise',
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
        }]
 | 
					        }]
 | 
				
			||||||
    }]
 | 
					    }]
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										153
									
								
								youtube_dl/extractor/camdemy.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								youtube_dl/extractor/camdemy.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,153 @@
 | 
				
			|||||||
 | 
					# coding: utf-8
 | 
				
			||||||
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import datetime
 | 
				
			||||||
 | 
					import re
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .common import InfoExtractor
 | 
				
			||||||
 | 
					from ..compat import (
 | 
				
			||||||
 | 
					    compat_urllib_parse,
 | 
				
			||||||
 | 
					    compat_urlparse,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					from ..utils import (
 | 
				
			||||||
 | 
					    parse_iso8601,
 | 
				
			||||||
 | 
					    str_to_int,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CamdemyIE(InfoExtractor):
 | 
				
			||||||
 | 
					    _VALID_URL = r'http://(?:www\.)?camdemy\.com/media/(?P<id>\d+)'
 | 
				
			||||||
 | 
					    _TESTS = [{
 | 
				
			||||||
 | 
					        # single file
 | 
				
			||||||
 | 
					        'url': 'http://www.camdemy.com/media/5181/',
 | 
				
			||||||
 | 
					        'md5': '5a5562b6a98b37873119102e052e311b',
 | 
				
			||||||
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'id': '5181',
 | 
				
			||||||
 | 
					            'ext': 'mp4',
 | 
				
			||||||
 | 
					            'title': 'Ch1-1 Introduction, Signals (02-23-2012)',
 | 
				
			||||||
 | 
					            'thumbnail': 're:^https?://.*\.jpg$',
 | 
				
			||||||
 | 
					            'description': '',
 | 
				
			||||||
 | 
					            'creator': 'ss11spring',
 | 
				
			||||||
 | 
					            'upload_date': '20130114',
 | 
				
			||||||
 | 
					            'timestamp': 1358154556,
 | 
				
			||||||
 | 
					            'view_count': int,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }, {
 | 
				
			||||||
 | 
					        # With non-empty description
 | 
				
			||||||
 | 
					        'url': 'http://www.camdemy.com/media/13885',
 | 
				
			||||||
 | 
					        'md5': '4576a3bb2581f86c61044822adbd1249',
 | 
				
			||||||
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'id': '13885',
 | 
				
			||||||
 | 
					            'ext': 'mp4',
 | 
				
			||||||
 | 
					            'title': 'EverCam + Camdemy QuickStart',
 | 
				
			||||||
 | 
					            'thumbnail': 're:^https?://.*\.jpg$',
 | 
				
			||||||
 | 
					            'description': 'md5:050b62f71ed62928f8a35f1a41e186c9',
 | 
				
			||||||
 | 
					            'creator': 'evercam',
 | 
				
			||||||
 | 
					            'upload_date': '20140620',
 | 
				
			||||||
 | 
					            'timestamp': 1403271569,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }, {
 | 
				
			||||||
 | 
					        # External source
 | 
				
			||||||
 | 
					        'url': 'http://www.camdemy.com/media/14842',
 | 
				
			||||||
 | 
					        'md5': '50e1c3c3aa233d3d7b7daa2fa10b1cf7',
 | 
				
			||||||
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'id': '2vsYQzNIsJo',
 | 
				
			||||||
 | 
					            'ext': 'mp4',
 | 
				
			||||||
 | 
					            'upload_date': '20130211',
 | 
				
			||||||
 | 
					            'uploader': 'Hun Kim',
 | 
				
			||||||
 | 
					            'description': 'Excel 2013 Tutorial for Beginners - How to add Password Protection',
 | 
				
			||||||
 | 
					            'uploader_id': 'hunkimtutorials',
 | 
				
			||||||
 | 
					            'title': 'Excel 2013 Tutorial - How to add Password Protection',
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
 | 
					        video_id = self._match_id(url)
 | 
				
			||||||
 | 
					        page = self._download_webpage(url, video_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        src_from = self._html_search_regex(
 | 
				
			||||||
 | 
					            r"<div class='srcFrom'>Source: <a title='([^']+)'", page,
 | 
				
			||||||
 | 
					            'external source', default=None)
 | 
				
			||||||
 | 
					        if src_from:
 | 
				
			||||||
 | 
					            return self.url_result(src_from)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        oembed_obj = self._download_json(
 | 
				
			||||||
 | 
					            'http://www.camdemy.com/oembed/?format=json&url=' + url, video_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        thumb_url = oembed_obj['thumbnail_url']
 | 
				
			||||||
 | 
					        video_folder = compat_urlparse.urljoin(thumb_url, 'video/')
 | 
				
			||||||
 | 
					        file_list_doc = self._download_xml(
 | 
				
			||||||
 | 
					            compat_urlparse.urljoin(video_folder, 'fileList.xml'),
 | 
				
			||||||
 | 
					            video_id, 'Filelist XML')
 | 
				
			||||||
 | 
					        file_name = file_list_doc.find('./video/item/fileName').text
 | 
				
			||||||
 | 
					        video_url = compat_urlparse.urljoin(video_folder, file_name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        timestamp = parse_iso8601(self._html_search_regex(
 | 
				
			||||||
 | 
					            r"<div class='title'>Posted\s*:</div>\s*<div class='value'>([^<>]+)<",
 | 
				
			||||||
 | 
					            page, 'creation time', fatal=False),
 | 
				
			||||||
 | 
					            delimiter=' ', timezone=datetime.timedelta(hours=8))
 | 
				
			||||||
 | 
					        view_count = str_to_int(self._html_search_regex(
 | 
				
			||||||
 | 
					            r"<div class='title'>Views\s*:</div>\s*<div class='value'>([^<>]+)<",
 | 
				
			||||||
 | 
					            page, 'view count', fatal=False))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            'id': video_id,
 | 
				
			||||||
 | 
					            'url': video_url,
 | 
				
			||||||
 | 
					            'title': oembed_obj['title'],
 | 
				
			||||||
 | 
					            'thumbnail': thumb_url,
 | 
				
			||||||
 | 
					            'description': self._html_search_meta('description', page),
 | 
				
			||||||
 | 
					            'creator': oembed_obj['author_name'],
 | 
				
			||||||
 | 
					            'duration': oembed_obj['duration'],
 | 
				
			||||||
 | 
					            'timestamp': timestamp,
 | 
				
			||||||
 | 
					            'view_count': view_count,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CamdemyFolderIE(InfoExtractor):
 | 
				
			||||||
 | 
					    _VALID_URL = r'http://www.camdemy.com/folder/(?P<id>\d+)'
 | 
				
			||||||
 | 
					    _TESTS = [{
 | 
				
			||||||
 | 
					        # links with trailing slash
 | 
				
			||||||
 | 
					        'url': 'http://www.camdemy.com/folder/450',
 | 
				
			||||||
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'id': '450',
 | 
				
			||||||
 | 
					            'title': '信號與系統 2012 & 2011 (Signals and Systems)',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        'playlist_mincount': 145
 | 
				
			||||||
 | 
					    }, {
 | 
				
			||||||
 | 
					        # links without trailing slash
 | 
				
			||||||
 | 
					        # and multi-page
 | 
				
			||||||
 | 
					        'url': 'http://www.camdemy.com/folder/853',
 | 
				
			||||||
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'id': '853',
 | 
				
			||||||
 | 
					            'title': '科學計算 - 使用 Matlab'
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        'playlist_mincount': 20
 | 
				
			||||||
 | 
					    }, {
 | 
				
			||||||
 | 
					        # with displayMode parameter. For testing the codes to add parameters
 | 
				
			||||||
 | 
					        'url': 'http://www.camdemy.com/folder/853/?displayMode=defaultOrderByOrg',
 | 
				
			||||||
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'id': '853',
 | 
				
			||||||
 | 
					            'title': '科學計算 - 使用 Matlab'
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        'playlist_mincount': 20
 | 
				
			||||||
 | 
					    }]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
 | 
					        folder_id = self._match_id(url)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Add displayMode=list so that all links are displayed in a single page
 | 
				
			||||||
 | 
					        parsed_url = list(compat_urlparse.urlparse(url))
 | 
				
			||||||
 | 
					        query = dict(compat_urlparse.parse_qsl(parsed_url[4]))
 | 
				
			||||||
 | 
					        query.update({'displayMode': 'list'})
 | 
				
			||||||
 | 
					        parsed_url[4] = compat_urllib_parse.urlencode(query)
 | 
				
			||||||
 | 
					        final_url = compat_urlparse.urlunparse(parsed_url)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        page = self._download_webpage(final_url, folder_id)
 | 
				
			||||||
 | 
					        matches = re.findall(r"href='(/media/\d+/?)'", page)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        entries = [self.url_result('http://www.camdemy.com' + media_path)
 | 
				
			||||||
 | 
					                   for media_path in matches]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        folder_title = self._html_search_meta('keywords', page)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return self.playlist_result(entries, folder_id, folder_title)
 | 
				
			||||||
@@ -5,6 +5,8 @@ import re
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from .common import InfoExtractor
 | 
					from .common import InfoExtractor
 | 
				
			||||||
from ..utils import (
 | 
					from ..utils import (
 | 
				
			||||||
 | 
					    ExtractorError,
 | 
				
			||||||
 | 
					    HEADRequest,
 | 
				
			||||||
    unified_strdate,
 | 
					    unified_strdate,
 | 
				
			||||||
    url_basename,
 | 
					    url_basename,
 | 
				
			||||||
    qualities,
 | 
					    qualities,
 | 
				
			||||||
@@ -13,12 +15,13 @@ from ..utils import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class CanalplusIE(InfoExtractor):
 | 
					class CanalplusIE(InfoExtractor):
 | 
				
			||||||
    IE_DESC = 'canalplus.fr, piwiplus.fr and d8.tv'
 | 
					    IE_DESC = 'canalplus.fr, piwiplus.fr and d8.tv'
 | 
				
			||||||
    _VALID_URL = r'https?://(?:www\.(?P<site>canalplus\.fr|piwiplus\.fr|d8\.tv)/.*?/(?P<path>.*)|player\.canalplus\.fr/#/(?P<id>[0-9]+))'
 | 
					    _VALID_URL = r'https?://(?:www\.(?P<site>canalplus\.fr|piwiplus\.fr|d8\.tv|itele\.fr)/.*?/(?P<path>.*)|player\.canalplus\.fr/#/(?P<id>[0-9]+))'
 | 
				
			||||||
    _VIDEO_INFO_TEMPLATE = 'http://service.canal-plus.com/video/rest/getVideosLiees/%s/%s'
 | 
					    _VIDEO_INFO_TEMPLATE = 'http://service.canal-plus.com/video/rest/getVideosLiees/%s/%s'
 | 
				
			||||||
    _SITE_ID_MAP = {
 | 
					    _SITE_ID_MAP = {
 | 
				
			||||||
        'canalplus.fr': 'cplus',
 | 
					        'canalplus.fr': 'cplus',
 | 
				
			||||||
        'piwiplus.fr': 'teletoon',
 | 
					        'piwiplus.fr': 'teletoon',
 | 
				
			||||||
        'd8.tv': 'd8',
 | 
					        'd8.tv': 'd8',
 | 
				
			||||||
 | 
					        'itele.fr': 'itele',
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    _TESTS = [{
 | 
					    _TESTS = [{
 | 
				
			||||||
@@ -51,6 +54,16 @@ class CanalplusIE(InfoExtractor):
 | 
				
			|||||||
            'upload_date': '20131108',
 | 
					            'upload_date': '20131108',
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        'skip': 'videos get deleted after a while',
 | 
					        'skip': 'videos get deleted after a while',
 | 
				
			||||||
 | 
					    }, {
 | 
				
			||||||
 | 
					        'url': 'http://www.itele.fr/france/video/aubervilliers-un-lycee-en-colere-111559',
 | 
				
			||||||
 | 
					        'md5': '65aa83ad62fe107ce29e564bb8712580',
 | 
				
			||||||
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'id': '1213714',
 | 
				
			||||||
 | 
					            'ext': 'flv',
 | 
				
			||||||
 | 
					            'title': 'Aubervilliers : un lycée en colère - Le 11/02/2015 à 06h45',
 | 
				
			||||||
 | 
					            'description': 'md5:8216206ec53426ea6321321f3b3c16db',
 | 
				
			||||||
 | 
					            'upload_date': '20150211',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
    }]
 | 
					    }]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _real_extract(self, url):
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
@@ -76,6 +89,16 @@ class CanalplusIE(InfoExtractor):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        preference = qualities(['MOBILE', 'BAS_DEBIT', 'HAUT_DEBIT', 'HD', 'HLS', 'HDS'])
 | 
					        preference = qualities(['MOBILE', 'BAS_DEBIT', 'HAUT_DEBIT', 'HD', 'HLS', 'HDS'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fmt_url = next(iter(media.find('VIDEOS'))).text
 | 
				
			||||||
 | 
					        if '/geo' in fmt_url.lower():
 | 
				
			||||||
 | 
					            response = self._request_webpage(
 | 
				
			||||||
 | 
					                HEADRequest(fmt_url), video_id,
 | 
				
			||||||
 | 
					                'Checking if the video is georestricted')
 | 
				
			||||||
 | 
					            if '/blocage' in response.geturl():
 | 
				
			||||||
 | 
					                raise ExtractorError(
 | 
				
			||||||
 | 
					                    'The video is not available in your country',
 | 
				
			||||||
 | 
					                    expected=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        formats = []
 | 
					        formats = []
 | 
				
			||||||
        for fmt in media.find('VIDEOS'):
 | 
					        for fmt in media.find('VIDEOS'):
 | 
				
			||||||
            format_url = fmt.text
 | 
					            format_url = fmt.text
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,5 @@
 | 
				
			|||||||
from __future__ import unicode_literals
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import re
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from .common import InfoExtractor
 | 
					from .common import InfoExtractor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -39,8 +37,7 @@ class CBSIE(InfoExtractor):
 | 
				
			|||||||
    }]
 | 
					    }]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _real_extract(self, url):
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
        mobj = re.match(self._VALID_URL, url)
 | 
					        video_id = self._match_id(url)
 | 
				
			||||||
        video_id = mobj.group('id')
 | 
					 | 
				
			||||||
        webpage = self._download_webpage(url, video_id)
 | 
					        webpage = self._download_webpage(url, video_id)
 | 
				
			||||||
        real_id = self._search_regex(
 | 
					        real_id = self._search_regex(
 | 
				
			||||||
            r"video\.settings\.pid\s*=\s*'([^']+)';",
 | 
					            r"video\.settings\.pid\s*=\s*'([^']+)';",
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										30
									
								
								youtube_dl/extractor/cbssports.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								youtube_dl/extractor/cbssports.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import re
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .common import InfoExtractor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CBSSportsIE(InfoExtractor):
 | 
				
			||||||
 | 
					    _VALID_URL = r'http://www\.cbssports\.com/video/player/(?P<section>[^/]+)/(?P<id>[^/]+)'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    _TEST = {
 | 
				
			||||||
 | 
					        'url': 'http://www.cbssports.com/video/player/tennis/318462531970/0/us-open-flashbacks-1990s',
 | 
				
			||||||
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'id': '_d5_GbO8p1sT',
 | 
				
			||||||
 | 
					            'ext': 'flv',
 | 
				
			||||||
 | 
					            'title': 'US Open flashbacks: 1990s',
 | 
				
			||||||
 | 
					            'description': 'Bill Macatee relives the best moments in US Open history from the 1990s.',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
 | 
					        mobj = re.match(self._VALID_URL, url)
 | 
				
			||||||
 | 
					        section = mobj.group('section')
 | 
				
			||||||
 | 
					        video_id = mobj.group('id')
 | 
				
			||||||
 | 
					        all_videos = self._download_json(
 | 
				
			||||||
 | 
					            'http://www.cbssports.com/data/video/player/getVideos/%s?as=json' % section,
 | 
				
			||||||
 | 
					            video_id)
 | 
				
			||||||
 | 
					        # The json file contains the info of all the videos in the section
 | 
				
			||||||
 | 
					        video_info = next(v for v in all_videos if v['pcid'] == video_id)
 | 
				
			||||||
 | 
					        return self.url_result('theplatform:%s' % video_info['pid'], 'ThePlatform')
 | 
				
			||||||
							
								
								
									
										99
									
								
								youtube_dl/extractor/ccc.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								youtube_dl/extractor/ccc.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,99 @@
 | 
				
			|||||||
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import re
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .common import InfoExtractor
 | 
				
			||||||
 | 
					from ..utils import (
 | 
				
			||||||
 | 
					    int_or_none,
 | 
				
			||||||
 | 
					    qualities,
 | 
				
			||||||
 | 
					    unified_strdate,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CCCIE(InfoExtractor):
 | 
				
			||||||
 | 
					    IE_NAME = 'media.ccc.de'
 | 
				
			||||||
 | 
					    _VALID_URL = r'https?://(?:www\.)?media\.ccc\.de/[^?#]+/[^?#/]*?_(?P<id>[0-9]{8,})._[^?#/]*\.html'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    _TEST = {
 | 
				
			||||||
 | 
					        'url': 'http://media.ccc.de/browse/congress/2013/30C3_-_5443_-_en_-_saal_g_-_201312281830_-_introduction_to_processor_design_-_byterazor.html#video',
 | 
				
			||||||
 | 
					        'md5': '205a365d0d57c0b1e43a12c9ffe8f9be',
 | 
				
			||||||
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'id': '20131228183',
 | 
				
			||||||
 | 
					            'ext': 'mp4',
 | 
				
			||||||
 | 
					            'title': 'Introduction to Processor Design',
 | 
				
			||||||
 | 
					            'description': 'md5:5ddbf8c734800267f2cee4eab187bc1b',
 | 
				
			||||||
 | 
					            'thumbnail': 're:^https?://.*\.jpg$',
 | 
				
			||||||
 | 
					            'view_count': int,
 | 
				
			||||||
 | 
					            'upload_date': '20131229',
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
 | 
					        video_id = self._match_id(url)
 | 
				
			||||||
 | 
					        webpage = self._download_webpage(url, video_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self._downloader.params.get('prefer_free_formats'):
 | 
				
			||||||
 | 
					            preference = qualities(['mp3', 'opus', 'mp4-lq', 'webm-lq', 'h264-sd', 'mp4-sd', 'webm-sd', 'mp4', 'webm', 'mp4-hd', 'h264-hd', 'webm-hd'])
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            preference = qualities(['opus', 'mp3', 'webm-lq', 'mp4-lq', 'webm-sd', 'h264-sd', 'mp4-sd', 'webm', 'mp4', 'webm-hd', 'mp4-hd', 'h264-hd'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        title = self._html_search_regex(
 | 
				
			||||||
 | 
					            r'(?s)<h1>(.*?)</h1>', webpage, 'title')
 | 
				
			||||||
 | 
					        description = self._html_search_regex(
 | 
				
			||||||
 | 
					            r"(?s)<p class='description'>(.*?)</p>",
 | 
				
			||||||
 | 
					            webpage, 'description', fatal=False)
 | 
				
			||||||
 | 
					        upload_date = unified_strdate(self._html_search_regex(
 | 
				
			||||||
 | 
					            r"(?s)<span class='[^']*fa-calendar-o'></span>(.*?)</li>",
 | 
				
			||||||
 | 
					            webpage, 'upload date', fatal=False))
 | 
				
			||||||
 | 
					        view_count = int_or_none(self._html_search_regex(
 | 
				
			||||||
 | 
					            r"(?s)<span class='[^']*fa-eye'></span>(.*?)</li>",
 | 
				
			||||||
 | 
					            webpage, 'view count', fatal=False))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        matches = re.finditer(r'''(?xs)
 | 
				
			||||||
 | 
					            <(?:span|div)\s+class='label\s+filetype'>(?P<format>.*?)</(?:span|div)>\s*
 | 
				
			||||||
 | 
					            <a\s+href='(?P<http_url>[^']+)'>\s*
 | 
				
			||||||
 | 
					            (?:
 | 
				
			||||||
 | 
					                .*?
 | 
				
			||||||
 | 
					                <a\s+href='(?P<torrent_url>[^']+\.torrent)'
 | 
				
			||||||
 | 
					            )?''', webpage)
 | 
				
			||||||
 | 
					        formats = []
 | 
				
			||||||
 | 
					        for m in matches:
 | 
				
			||||||
 | 
					            format = m.group('format')
 | 
				
			||||||
 | 
					            format_id = self._search_regex(
 | 
				
			||||||
 | 
					                r'.*/([a-z0-9_-]+)/[^/]*$',
 | 
				
			||||||
 | 
					                m.group('http_url'), 'format id', default=None)
 | 
				
			||||||
 | 
					            vcodec = 'h264' if 'h264' in format_id else (
 | 
				
			||||||
 | 
					                'none' if format_id in ('mp3', 'opus') else None
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            formats.append({
 | 
				
			||||||
 | 
					                'format_id': format_id,
 | 
				
			||||||
 | 
					                'format': format,
 | 
				
			||||||
 | 
					                'url': m.group('http_url'),
 | 
				
			||||||
 | 
					                'vcodec': vcodec,
 | 
				
			||||||
 | 
					                'preference': preference(format_id),
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if m.group('torrent_url'):
 | 
				
			||||||
 | 
					                formats.append({
 | 
				
			||||||
 | 
					                    'format_id': 'torrent-%s' % (format if format_id is None else format_id),
 | 
				
			||||||
 | 
					                    'format': '%s (torrent)' % format,
 | 
				
			||||||
 | 
					                    'proto': 'torrent',
 | 
				
			||||||
 | 
					                    'format_note': '(unsupported; will just download the .torrent file)',
 | 
				
			||||||
 | 
					                    'vcodec': vcodec,
 | 
				
			||||||
 | 
					                    'preference': -100 + preference(format_id),
 | 
				
			||||||
 | 
					                    'url': m.group('torrent_url'),
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					        self._sort_formats(formats)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        thumbnail = self._html_search_regex(
 | 
				
			||||||
 | 
					            r"<video.*?poster='([^']+)'", webpage, 'thumbnail', fatal=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            'id': video_id,
 | 
				
			||||||
 | 
					            'title': title,
 | 
				
			||||||
 | 
					            'description': description,
 | 
				
			||||||
 | 
					            'thumbnail': thumbnail,
 | 
				
			||||||
 | 
					            'view_count': view_count,
 | 
				
			||||||
 | 
					            'upload_date': upload_date,
 | 
				
			||||||
 | 
					            'formats': formats,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
@@ -11,6 +11,7 @@ from ..compat import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
from ..utils import (
 | 
					from ..utils import (
 | 
				
			||||||
    ExtractorError,
 | 
					    ExtractorError,
 | 
				
			||||||
 | 
					    float_or_none,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -19,41 +20,33 @@ class CeskaTelevizeIE(InfoExtractor):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    _TESTS = [
 | 
					    _TESTS = [
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            'url': 'http://www.ceskatelevize.cz/ivysilani/10532695142-prvni-republika/213512120230004-spanelska-chripka',
 | 
					            'url': 'http://www.ceskatelevize.cz/ivysilani/ivysilani/10441294653-hyde-park-civilizace/214411058091220',
 | 
				
			||||||
            'info_dict': {
 | 
					            'info_dict': {
 | 
				
			||||||
                'id': '213512120230004',
 | 
					                'id': '214411058091220',
 | 
				
			||||||
                'ext': 'flv',
 | 
					                'ext': 'mp4',
 | 
				
			||||||
                'title': 'První republika: Španělská chřipka',
 | 
					                'title': 'Hyde Park Civilizace',
 | 
				
			||||||
                'duration': 3107.4,
 | 
					                'description': 'Věda a současná civilizace. Interaktivní pořad - prostor pro vaše otázky a komentáře',
 | 
				
			||||||
 | 
					                'thumbnail': 're:^https?://.*\.jpg',
 | 
				
			||||||
 | 
					                'duration': 3350,
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            'params': {
 | 
					            'params': {
 | 
				
			||||||
                'skip_download': True,  # requires rtmpdump
 | 
					                # m3u8 download
 | 
				
			||||||
 | 
					                'skip_download': True,
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            'skip': 'Works only from Czech Republic.',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            'url': 'http://www.ceskatelevize.cz/ivysilani/1030584952-tsatsiki-maminka-a-policajt',
 | 
					 | 
				
			||||||
            'info_dict': {
 | 
					 | 
				
			||||||
                'id': '20138143440',
 | 
					 | 
				
			||||||
                'ext': 'flv',
 | 
					 | 
				
			||||||
                'title': 'Tsatsiki, maminka a policajt',
 | 
					 | 
				
			||||||
                'duration': 6754.1,
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            'params': {
 | 
					 | 
				
			||||||
                'skip_download': True,  # requires rtmpdump
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            'skip': 'Works only from Czech Republic.',
 | 
					 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            'url': 'http://www.ceskatelevize.cz/ivysilani/10532695142-prvni-republika/bonus/14716-zpevacka-z-duparny-bobina',
 | 
					            'url': 'http://www.ceskatelevize.cz/ivysilani/10532695142-prvni-republika/bonus/14716-zpevacka-z-duparny-bobina',
 | 
				
			||||||
            'info_dict': {
 | 
					            'info_dict': {
 | 
				
			||||||
                'id': '14716',
 | 
					                'id': '14716',
 | 
				
			||||||
                'ext': 'flv',
 | 
					                'ext': 'mp4',
 | 
				
			||||||
                'title': 'První republika: Zpěvačka z Dupárny Bobina',
 | 
					                'title': 'První republika: Zpěvačka z Dupárny Bobina',
 | 
				
			||||||
                'duration': 90,
 | 
					                'description': 'Sága mapující atmosféru první republiky od r. 1918 do r. 1945.',
 | 
				
			||||||
 | 
					                'thumbnail': 're:^https?://.*\.jpg',
 | 
				
			||||||
 | 
					                'duration': 88.4,
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            'params': {
 | 
					            'params': {
 | 
				
			||||||
                'skip_download': True,  # requires rtmpdump
 | 
					                # m3u8 download
 | 
				
			||||||
 | 
					                'skip_download': True,
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
@@ -80,8 +73,9 @@ class CeskaTelevizeIE(InfoExtractor):
 | 
				
			|||||||
            'requestSource': 'iVysilani',
 | 
					            'requestSource': 'iVysilani',
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        req = compat_urllib_request.Request('http://www.ceskatelevize.cz/ivysilani/ajax/get-playlist-url',
 | 
					        req = compat_urllib_request.Request(
 | 
				
			||||||
                                            data=compat_urllib_parse.urlencode(data))
 | 
					            'http://www.ceskatelevize.cz/ivysilani/ajax/get-client-playlist',
 | 
				
			||||||
 | 
					            data=compat_urllib_parse.urlencode(data))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        req.add_header('Content-type', 'application/x-www-form-urlencoded')
 | 
					        req.add_header('Content-type', 'application/x-www-form-urlencoded')
 | 
				
			||||||
        req.add_header('x-addr', '127.0.0.1')
 | 
					        req.add_header('x-addr', '127.0.0.1')
 | 
				
			||||||
@@ -90,39 +84,72 @@ class CeskaTelevizeIE(InfoExtractor):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        playlistpage = self._download_json(req, video_id)
 | 
					        playlistpage = self._download_json(req, video_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        req = compat_urllib_request.Request(compat_urllib_parse.unquote(playlistpage['url']))
 | 
					        playlist_url = playlistpage['url']
 | 
				
			||||||
 | 
					        if playlist_url == 'error_region':
 | 
				
			||||||
 | 
					            raise ExtractorError(NOT_AVAILABLE_STRING, expected=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        req = compat_urllib_request.Request(compat_urllib_parse.unquote(playlist_url))
 | 
				
			||||||
        req.add_header('Referer', url)
 | 
					        req.add_header('Referer', url)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        playlist = self._download_xml(req, video_id)
 | 
					        playlist = self._download_json(req, video_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        item = playlist['playlist'][0]
 | 
				
			||||||
        formats = []
 | 
					        formats = []
 | 
				
			||||||
        for i in playlist.find('smilRoot/body'):
 | 
					        for format_id, stream_url in item['streamUrls'].items():
 | 
				
			||||||
            if 'AD' not in i.attrib['id']:
 | 
					            formats.extend(self._extract_m3u8_formats(stream_url, video_id, 'mp4'))
 | 
				
			||||||
                base_url = i.attrib['base']
 | 
					 | 
				
			||||||
                parsedurl = compat_urllib_parse_urlparse(base_url)
 | 
					 | 
				
			||||||
                duration = i.attrib['duration']
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                for video in i.findall('video'):
 | 
					 | 
				
			||||||
                    if video.attrib['label'] != 'AD':
 | 
					 | 
				
			||||||
                        format_id = video.attrib['label']
 | 
					 | 
				
			||||||
                        play_path = video.attrib['src']
 | 
					 | 
				
			||||||
                        vbr = int(video.attrib['system-bitrate'])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        formats.append({
 | 
					 | 
				
			||||||
                            'format_id': format_id,
 | 
					 | 
				
			||||||
                            'url': base_url,
 | 
					 | 
				
			||||||
                            'vbr': vbr,
 | 
					 | 
				
			||||||
                            'play_path': play_path,
 | 
					 | 
				
			||||||
                            'app': parsedurl.path[1:] + '?' + parsedurl.query,
 | 
					 | 
				
			||||||
                            'rtmp_live': True,
 | 
					 | 
				
			||||||
                            'ext': 'flv',
 | 
					 | 
				
			||||||
                        })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self._sort_formats(formats)
 | 
					        self._sort_formats(formats)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        title = self._og_search_title(webpage)
 | 
				
			||||||
 | 
					        description = self._og_search_description(webpage)
 | 
				
			||||||
 | 
					        duration = float_or_none(item.get('duration'))
 | 
				
			||||||
 | 
					        thumbnail = item.get('previewImageUrl')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        subtitles = {}
 | 
				
			||||||
 | 
					        subs = item.get('subtitles')
 | 
				
			||||||
 | 
					        if subs:
 | 
				
			||||||
 | 
					            subtitles = self.extract_subtitles(episode_id, subs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            'id': episode_id,
 | 
					            'id': episode_id,
 | 
				
			||||||
            'title': self._html_search_regex(r'<title>(.+?) — iVysílání — Česká televize</title>', webpage, 'title'),
 | 
					            'title': title,
 | 
				
			||||||
            'duration': float(duration),
 | 
					            'description': description,
 | 
				
			||||||
 | 
					            'thumbnail': thumbnail,
 | 
				
			||||||
 | 
					            'duration': duration,
 | 
				
			||||||
            'formats': formats,
 | 
					            'formats': formats,
 | 
				
			||||||
 | 
					            'subtitles': subtitles,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _get_subtitles(self, episode_id, subs):
 | 
				
			||||||
 | 
					        original_subtitles = self._download_webpage(
 | 
				
			||||||
 | 
					            subs[0]['url'], episode_id, 'Downloading subtitles')
 | 
				
			||||||
 | 
					        srt_subs = self._fix_subtitles(original_subtitles)
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            'cs': [{
 | 
				
			||||||
 | 
					                'ext': 'srt',
 | 
				
			||||||
 | 
					                'data': srt_subs,
 | 
				
			||||||
 | 
					            }]
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @staticmethod
 | 
				
			||||||
 | 
					    def _fix_subtitles(subtitles):
 | 
				
			||||||
 | 
					        """ Convert millisecond-based subtitles to SRT """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def _msectotimecode(msec):
 | 
				
			||||||
 | 
					            """ Helper utility to convert milliseconds to timecode """
 | 
				
			||||||
 | 
					            components = []
 | 
				
			||||||
 | 
					            for divider in [1000, 60, 60, 100]:
 | 
				
			||||||
 | 
					                components.append(msec % divider)
 | 
				
			||||||
 | 
					                msec //= divider
 | 
				
			||||||
 | 
					            return "{3:02}:{2:02}:{1:02},{0:03}".format(*components)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def _fix_subtitle(subtitle):
 | 
				
			||||||
 | 
					            for line in subtitle.splitlines():
 | 
				
			||||||
 | 
					                m = re.match(r"^\s*([0-9]+);\s*([0-9]+)\s+([0-9]+)\s*$", line)
 | 
				
			||||||
 | 
					                if m:
 | 
				
			||||||
 | 
					                    yield m.group(1)
 | 
				
			||||||
 | 
					                    start, stop = (_msectotimecode(int(t)) for t in m.groups()[1:])
 | 
				
			||||||
 | 
					                    yield "{0} --> {1}".format(start, stop)
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    yield line
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return "\r\n".join(_fix_subtitle(subtitles))
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										84
									
								
								youtube_dl/extractor/chirbit.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								youtube_dl/extractor/chirbit.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,84 @@
 | 
				
			|||||||
 | 
					# coding: utf-8
 | 
				
			||||||
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .common import InfoExtractor
 | 
				
			||||||
 | 
					from ..utils import (
 | 
				
			||||||
 | 
					    parse_duration,
 | 
				
			||||||
 | 
					    int_or_none,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ChirbitIE(InfoExtractor):
 | 
				
			||||||
 | 
					    IE_NAME = 'chirbit'
 | 
				
			||||||
 | 
					    _VALID_URL = r'https?://(?:www\.)?chirb\.it/(?:(?:wp|pl)/|fb_chirbit_player\.swf\?key=)?(?P<id>[\da-zA-Z]+)'
 | 
				
			||||||
 | 
					    _TESTS = [{
 | 
				
			||||||
 | 
					        'url': 'http://chirb.it/PrIPv5',
 | 
				
			||||||
 | 
					        'md5': '9847b0dad6ac3e074568bf2cfb197de8',
 | 
				
			||||||
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'id': 'PrIPv5',
 | 
				
			||||||
 | 
					            'ext': 'mp3',
 | 
				
			||||||
 | 
					            'title': 'Фасадстрой',
 | 
				
			||||||
 | 
					            'duration': 52,
 | 
				
			||||||
 | 
					            'view_count': int,
 | 
				
			||||||
 | 
					            'comment_count': int,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }, {
 | 
				
			||||||
 | 
					        'url': 'https://chirb.it/fb_chirbit_player.swf?key=PrIPv5',
 | 
				
			||||||
 | 
					        'only_matching': True,
 | 
				
			||||||
 | 
					    }]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
 | 
					        audio_id = self._match_id(url)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        webpage = self._download_webpage(
 | 
				
			||||||
 | 
					            'http://chirb.it/%s' % audio_id, audio_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        audio_url = self._search_regex(
 | 
				
			||||||
 | 
					            r'"setFile"\s*,\s*"([^"]+)"', webpage, 'audio url')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        title = self._search_regex(
 | 
				
			||||||
 | 
					            r'itemprop="name">([^<]+)', webpage, 'title')
 | 
				
			||||||
 | 
					        duration = parse_duration(self._html_search_meta(
 | 
				
			||||||
 | 
					            'duration', webpage, 'duration', fatal=False))
 | 
				
			||||||
 | 
					        view_count = int_or_none(self._search_regex(
 | 
				
			||||||
 | 
					            r'itemprop="playCount"\s*>(\d+)', webpage,
 | 
				
			||||||
 | 
					            'listen count', fatal=False))
 | 
				
			||||||
 | 
					        comment_count = int_or_none(self._search_regex(
 | 
				
			||||||
 | 
					            r'>(\d+) Comments?:', webpage,
 | 
				
			||||||
 | 
					            'comment count', fatal=False))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            'id': audio_id,
 | 
				
			||||||
 | 
					            'url': audio_url,
 | 
				
			||||||
 | 
					            'title': title,
 | 
				
			||||||
 | 
					            'duration': duration,
 | 
				
			||||||
 | 
					            'view_count': view_count,
 | 
				
			||||||
 | 
					            'comment_count': comment_count,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ChirbitProfileIE(InfoExtractor):
 | 
				
			||||||
 | 
					    IE_NAME = 'chirbit:profile'
 | 
				
			||||||
 | 
					    _VALID_URL = r'https?://(?:www\.)?chirbit.com/(?:rss/)?(?P<id>[^/]+)'
 | 
				
			||||||
 | 
					    _TEST = {
 | 
				
			||||||
 | 
					        'url': 'http://chirbit.com/ScarletBeauty',
 | 
				
			||||||
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'id': 'ScarletBeauty',
 | 
				
			||||||
 | 
					            'title': 'Chirbits by ScarletBeauty',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        'playlist_mincount': 3,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
 | 
					        profile_id = self._match_id(url)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        rss = self._download_xml(
 | 
				
			||||||
 | 
					            'http://chirbit.com/rss/%s' % profile_id, profile_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        entries = [
 | 
				
			||||||
 | 
					            self.url_result(audio_url.text, 'Chirbit')
 | 
				
			||||||
 | 
					            for audio_url in rss.findall('./channel/item/link')]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        title = rss.find('./channel/title').text
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return self.playlist_result(entries, profile_id, title)
 | 
				
			||||||
@@ -28,12 +28,10 @@ class CinchcastIE(InfoExtractor):
 | 
				
			|||||||
            item, './{http://developer.longtailvideo.com/trac/}date')
 | 
					            item, './{http://developer.longtailvideo.com/trac/}date')
 | 
				
			||||||
        upload_date = unified_strdate(date_str, day_first=False)
 | 
					        upload_date = unified_strdate(date_str, day_first=False)
 | 
				
			||||||
        # duration is present but wrong
 | 
					        # duration is present but wrong
 | 
				
			||||||
        formats = []
 | 
					        formats = [{
 | 
				
			||||||
        formats.append({
 | 
					 | 
				
			||||||
            'format_id': 'main',
 | 
					            'format_id': 'main',
 | 
				
			||||||
            'url': item.find(
 | 
					            'url': item.find('./{http://search.yahoo.com/mrss/}content').attrib['url'],
 | 
				
			||||||
                './{http://search.yahoo.com/mrss/}content').attrib['url'],
 | 
					        }]
 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
        backup_url = xpath_text(
 | 
					        backup_url = xpath_text(
 | 
				
			||||||
            item, './{http://developer.longtailvideo.com/trac/}backupContent')
 | 
					            item, './{http://developer.longtailvideo.com/trac/}backupContent')
 | 
				
			||||||
        if backup_url:
 | 
					        if backup_url:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,7 @@
 | 
				
			|||||||
from __future__ import unicode_literals
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import json
 | 
					 | 
				
			||||||
import re
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from .common import InfoExtractor
 | 
					from .common import InfoExtractor
 | 
				
			||||||
 | 
					from ..utils import determine_ext
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
_translation_table = {
 | 
					_translation_table = {
 | 
				
			||||||
@@ -27,10 +25,10 @@ class CliphunterIE(InfoExtractor):
 | 
				
			|||||||
    '''
 | 
					    '''
 | 
				
			||||||
    _TEST = {
 | 
					    _TEST = {
 | 
				
			||||||
        'url': 'http://www.cliphunter.com/w/1012420/Fun_Jynx_Maze_solo',
 | 
					        'url': 'http://www.cliphunter.com/w/1012420/Fun_Jynx_Maze_solo',
 | 
				
			||||||
        'md5': 'a2ba71eebf523859fe527a61018f723e',
 | 
					        'md5': 'b7c9bbd4eb3a226ab91093714dcaa480',
 | 
				
			||||||
        'info_dict': {
 | 
					        'info_dict': {
 | 
				
			||||||
            'id': '1012420',
 | 
					            'id': '1012420',
 | 
				
			||||||
            'ext': 'mp4',
 | 
					            'ext': 'flv',
 | 
				
			||||||
            'title': 'Fun Jynx Maze solo',
 | 
					            'title': 'Fun Jynx Maze solo',
 | 
				
			||||||
            'thumbnail': 're:^https?://.*\.jpg$',
 | 
					            'thumbnail': 're:^https?://.*\.jpg$',
 | 
				
			||||||
            'age_limit': 18,
 | 
					            'age_limit': 18,
 | 
				
			||||||
@@ -44,39 +42,31 @@ class CliphunterIE(InfoExtractor):
 | 
				
			|||||||
        video_title = self._search_regex(
 | 
					        video_title = self._search_regex(
 | 
				
			||||||
            r'mediaTitle = "([^"]+)"', webpage, 'title')
 | 
					            r'mediaTitle = "([^"]+)"', webpage, 'title')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        pl_fiji = self._search_regex(
 | 
					        fmts = {}
 | 
				
			||||||
            r'pl_fiji = \'([^\']+)\'', webpage, 'video data')
 | 
					        for fmt in ('mp4', 'flv'):
 | 
				
			||||||
        pl_c_qual = self._search_regex(
 | 
					            fmt_list = self._parse_json(self._search_regex(
 | 
				
			||||||
            r'pl_c_qual = "(.)"', webpage, 'video quality')
 | 
					                r'var %sjson\s*=\s*(\[.*?\]);' % fmt, webpage, '%s formats' % fmt), video_id)
 | 
				
			||||||
        video_url = _decode(pl_fiji)
 | 
					            for f in fmt_list:
 | 
				
			||||||
        formats = [{
 | 
					                fmts[f['fname']] = _decode(f['sUrl'])
 | 
				
			||||||
            'url': video_url,
 | 
					 | 
				
			||||||
            'format_id': 'default-%s' % pl_c_qual,
 | 
					 | 
				
			||||||
        }]
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        qualities_json = self._search_regex(
 | 
					        qualities = self._parse_json(self._search_regex(
 | 
				
			||||||
            r'var pl_qualities\s*=\s*(.*?);\n', webpage, 'quality info')
 | 
					            r'var player_btns\s*=\s*(.*?);\n', webpage, 'quality info'), video_id)
 | 
				
			||||||
        qualities_data = json.loads(qualities_json)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for i, t in enumerate(
 | 
					        formats = []
 | 
				
			||||||
                re.findall(r"pl_fiji_([a-z0-9]+)\s*=\s*'([^']+')", webpage)):
 | 
					        for fname, url in fmts.items():
 | 
				
			||||||
            quality_id, crypted_url = t
 | 
					 | 
				
			||||||
            video_url = _decode(crypted_url)
 | 
					 | 
				
			||||||
            f = {
 | 
					            f = {
 | 
				
			||||||
                'format_id': quality_id,
 | 
					                'url': url,
 | 
				
			||||||
                'url': video_url,
 | 
					 | 
				
			||||||
                'quality': i,
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if quality_id in qualities_data:
 | 
					            if fname in qualities:
 | 
				
			||||||
                qd = qualities_data[quality_id]
 | 
					                qual = qualities[fname]
 | 
				
			||||||
                m = re.match(
 | 
					                f.update({
 | 
				
			||||||
                    r'''(?x)<b>(?P<width>[0-9]+)x(?P<height>[0-9]+)<\\/b>
 | 
					                    'format_id': '%s_%sp' % (determine_ext(url), qual['h']),
 | 
				
			||||||
                        \s*\(\s*(?P<tbr>[0-9]+)\s*kb\\/s''', qd)
 | 
					                    'width': qual['w'],
 | 
				
			||||||
                if m:
 | 
					                    'height': qual['h'],
 | 
				
			||||||
                    f['width'] = int(m.group('width'))
 | 
					                    'tbr': qual['br'],
 | 
				
			||||||
                    f['height'] = int(m.group('height'))
 | 
					                })
 | 
				
			||||||
                    f['tbr'] = int(m.group('tbr'))
 | 
					 | 
				
			||||||
            formats.append(f)
 | 
					            formats.append(f)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self._sort_formats(formats)
 | 
					        self._sort_formats(formats)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        thumbnail = self._search_regex(
 | 
					        thumbnail = self._search_regex(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,14 +11,14 @@ from ..utils import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CNNIE(InfoExtractor):
 | 
					class CNNIE(InfoExtractor):
 | 
				
			||||||
    _VALID_URL = r'''(?x)https?://((edition|www)\.)?cnn\.com/video/(data/.+?|\?)/
 | 
					    _VALID_URL = r'''(?x)https?://(?:(?:edition|www)\.)?cnn\.com/video/(?:data/.+?|\?)/
 | 
				
			||||||
        (?P<path>.+?/(?P<title>[^/]+?)(?:\.cnn(-ap)?|(?=&)))'''
 | 
					        (?P<path>.+?/(?P<title>[^/]+?)(?:\.(?:cnn|hln)(?:-ap)?|(?=&)))'''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    _TESTS = [{
 | 
					    _TESTS = [{
 | 
				
			||||||
        'url': 'http://edition.cnn.com/video/?/video/sports/2013/06/09/nadal-1-on-1.cnn',
 | 
					        'url': 'http://edition.cnn.com/video/?/video/sports/2013/06/09/nadal-1-on-1.cnn',
 | 
				
			||||||
        'md5': '3e6121ea48df7e2259fe73a0628605c4',
 | 
					        'md5': '3e6121ea48df7e2259fe73a0628605c4',
 | 
				
			||||||
        'info_dict': {
 | 
					        'info_dict': {
 | 
				
			||||||
            'id': 'sports_2013_06_09_nadal-1-on-1.cnn',
 | 
					            'id': 'sports/2013/06/09/nadal-1-on-1.cnn',
 | 
				
			||||||
            'ext': 'mp4',
 | 
					            'ext': 'mp4',
 | 
				
			||||||
            'title': 'Nadal wins 8th French Open title',
 | 
					            'title': 'Nadal wins 8th French Open title',
 | 
				
			||||||
            'description': 'World Sport\'s Amanda Davies chats with 2013 French Open champion Rafael Nadal.',
 | 
					            'description': 'World Sport\'s Amanda Davies chats with 2013 French Open champion Rafael Nadal.',
 | 
				
			||||||
@@ -35,13 +35,23 @@ class CNNIE(InfoExtractor):
 | 
				
			|||||||
            "description": "A Georgia Tech student welcomes the incoming freshmen with an epic speech backed by music from \"2001: A Space Odyssey.\"",
 | 
					            "description": "A Georgia Tech student welcomes the incoming freshmen with an epic speech backed by music from \"2001: A Space Odyssey.\"",
 | 
				
			||||||
            "upload_date": "20130821",
 | 
					            "upload_date": "20130821",
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					    }, {
 | 
				
			||||||
 | 
					        'url': 'http://www.cnn.com/video/data/2.0/video/living/2014/12/22/growing-america-nashville-salemtown-board-episode-1.hln.html',
 | 
				
			||||||
 | 
					        'md5': 'f14d02ebd264df951feb2400e2c25a1b',
 | 
				
			||||||
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'id': 'living/2014/12/22/growing-america-nashville-salemtown-board-episode-1.hln',
 | 
				
			||||||
 | 
					            'ext': 'mp4',
 | 
				
			||||||
 | 
					            'title': 'Nashville Ep. 1: Hand crafted skateboards',
 | 
				
			||||||
 | 
					            'description': 'md5:e7223a503315c9f150acac52e76de086',
 | 
				
			||||||
 | 
					            'upload_date': '20141222',
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }]
 | 
					    }]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _real_extract(self, url):
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
        mobj = re.match(self._VALID_URL, url)
 | 
					        mobj = re.match(self._VALID_URL, url)
 | 
				
			||||||
        path = mobj.group('path')
 | 
					        path = mobj.group('path')
 | 
				
			||||||
        page_title = mobj.group('title')
 | 
					        page_title = mobj.group('title')
 | 
				
			||||||
        info_url = 'http://cnn.com/video/data/3.0/%s/index.xml' % path
 | 
					        info_url = 'http://edition.cnn.com/video/data/3.0/%s/index.xml' % path
 | 
				
			||||||
        info = self._download_xml(info_url, page_title)
 | 
					        info = self._download_xml(info_url, page_title)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        formats = []
 | 
					        formats = []
 | 
				
			||||||
@@ -127,3 +137,28 @@ class CNNBlogsIE(InfoExtractor):
 | 
				
			|||||||
            'url': cnn_url,
 | 
					            'url': cnn_url,
 | 
				
			||||||
            'ie_key': CNNIE.ie_key(),
 | 
					            'ie_key': CNNIE.ie_key(),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CNNArticleIE(InfoExtractor):
 | 
				
			||||||
 | 
					    _VALID_URL = r'https?://(?:(?:edition|www)\.)?cnn\.com/(?!video/)'
 | 
				
			||||||
 | 
					    _TEST = {
 | 
				
			||||||
 | 
					        'url': 'http://www.cnn.com/2014/12/21/politics/obama-north-koreas-hack-not-war-but-cyber-vandalism/',
 | 
				
			||||||
 | 
					        'md5': '689034c2a3d9c6dc4aa72d65a81efd01',
 | 
				
			||||||
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'id': 'bestoftv/2014/12/21/ip-north-korea-obama.cnn',
 | 
				
			||||||
 | 
					            'ext': 'mp4',
 | 
				
			||||||
 | 
					            'title': 'Obama: Cyberattack not an act of war',
 | 
				
			||||||
 | 
					            'description': 'md5:51ce6750450603795cad0cdfbd7d05c5',
 | 
				
			||||||
 | 
					            'upload_date': '20141221',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        'add_ie': ['CNN'],
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
 | 
					        webpage = self._download_webpage(url, url_basename(url))
 | 
				
			||||||
 | 
					        cnn_url = self._html_search_regex(r"video:\s*'([^']+)'", webpage, 'cnn url')
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            '_type': 'url',
 | 
				
			||||||
 | 
					            'url': 'http://cnn.com/video/?/video/' + cnn_url,
 | 
				
			||||||
 | 
					            'ie_key': CNNIE.ie_key(),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										92
									
								
								youtube_dl/extractor/collegerama.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								youtube_dl/extractor/collegerama.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,92 @@
 | 
				
			|||||||
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .common import InfoExtractor
 | 
				
			||||||
 | 
					from ..compat import compat_urllib_request
 | 
				
			||||||
 | 
					from ..utils import (
 | 
				
			||||||
 | 
					    float_or_none,
 | 
				
			||||||
 | 
					    int_or_none,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CollegeRamaIE(InfoExtractor):
 | 
				
			||||||
 | 
					    _VALID_URL = r'https?://collegerama\.tudelft\.nl/Mediasite/Play/(?P<id>[\da-f]+)'
 | 
				
			||||||
 | 
					    _TESTS = [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            'url': 'https://collegerama.tudelft.nl/Mediasite/Play/585a43626e544bdd97aeb71a0ec907a01d',
 | 
				
			||||||
 | 
					            'md5': '481fda1c11f67588c0d9d8fbdced4e39',
 | 
				
			||||||
 | 
					            'info_dict': {
 | 
				
			||||||
 | 
					                'id': '585a43626e544bdd97aeb71a0ec907a01d',
 | 
				
			||||||
 | 
					                'ext': 'mp4',
 | 
				
			||||||
 | 
					                'title': 'Een nieuwe wereld: waarden, bewustzijn en techniek van de mensheid 2.0.',
 | 
				
			||||||
 | 
					                'description': '',
 | 
				
			||||||
 | 
					                'thumbnail': 're:^https?://.*\.jpg$',
 | 
				
			||||||
 | 
					                'duration': 7713.088,
 | 
				
			||||||
 | 
					                'timestamp': 1413309600,
 | 
				
			||||||
 | 
					                'upload_date': '20141014',
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            'url': 'https://collegerama.tudelft.nl/Mediasite/Play/86a9ea9f53e149079fbdb4202b521ed21d?catalog=fd32fd35-6c99-466c-89d4-cd3c431bc8a4',
 | 
				
			||||||
 | 
					            'md5': 'ef1fdded95bdf19b12c5999949419c92',
 | 
				
			||||||
 | 
					            'info_dict': {
 | 
				
			||||||
 | 
					                'id': '86a9ea9f53e149079fbdb4202b521ed21d',
 | 
				
			||||||
 | 
					                'ext': 'wmv',
 | 
				
			||||||
 | 
					                'title': '64ste Vakantiecursus: Afvalwater',
 | 
				
			||||||
 | 
					                'description': 'md5:7fd774865cc69d972f542b157c328305',
 | 
				
			||||||
 | 
					                'duration': 10853,
 | 
				
			||||||
 | 
					                'timestamp': 1326446400,
 | 
				
			||||||
 | 
					                'upload_date': '20120113',
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
 | 
					        video_id = self._match_id(url)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        player_options_request = {
 | 
				
			||||||
 | 
					            "getPlayerOptionsRequest": {
 | 
				
			||||||
 | 
					                "ResourceId": video_id,
 | 
				
			||||||
 | 
					                "QueryString": "",
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        request = compat_urllib_request.Request(
 | 
				
			||||||
 | 
					            'http://collegerama.tudelft.nl/Mediasite/PlayerService/PlayerService.svc/json/GetPlayerOptions',
 | 
				
			||||||
 | 
					            json.dumps(player_options_request))
 | 
				
			||||||
 | 
					        request.add_header('Content-Type', 'application/json')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        player_options = self._download_json(request, video_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        presentation = player_options['d']['Presentation']
 | 
				
			||||||
 | 
					        title = presentation['Title']
 | 
				
			||||||
 | 
					        description = presentation.get('Description')
 | 
				
			||||||
 | 
					        thumbnail = None
 | 
				
			||||||
 | 
					        duration = float_or_none(presentation.get('Duration'), 1000)
 | 
				
			||||||
 | 
					        timestamp = int_or_none(presentation.get('UnixTime'), 1000)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        formats = []
 | 
				
			||||||
 | 
					        for stream in presentation['Streams']:
 | 
				
			||||||
 | 
					            for video in stream['VideoUrls']:
 | 
				
			||||||
 | 
					                thumbnail_url = stream.get('ThumbnailUrl')
 | 
				
			||||||
 | 
					                if thumbnail_url:
 | 
				
			||||||
 | 
					                    thumbnail = 'http://collegerama.tudelft.nl' + thumbnail_url
 | 
				
			||||||
 | 
					                format_id = video['MediaType']
 | 
				
			||||||
 | 
					                if format_id == 'SS':
 | 
				
			||||||
 | 
					                    continue
 | 
				
			||||||
 | 
					                formats.append({
 | 
				
			||||||
 | 
					                    'url': video['Location'],
 | 
				
			||||||
 | 
					                    'format_id': format_id,
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					        self._sort_formats(formats)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            'id': video_id,
 | 
				
			||||||
 | 
					            'title': title,
 | 
				
			||||||
 | 
					            'description': description,
 | 
				
			||||||
 | 
					            'thumbnail': thumbnail,
 | 
				
			||||||
 | 
					            'duration': duration,
 | 
				
			||||||
 | 
					            'timestamp': timestamp,
 | 
				
			||||||
 | 
					            'formats': formats,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
@@ -34,12 +34,12 @@ class ComedyCentralIE(MTVServicesInfoExtractor):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class ComedyCentralShowsIE(MTVServicesInfoExtractor):
 | 
					class ComedyCentralShowsIE(MTVServicesInfoExtractor):
 | 
				
			||||||
    IE_DESC = 'The Daily Show / The Colbert Report'
 | 
					    IE_DESC = 'The Daily Show / The Colbert Report'
 | 
				
			||||||
    # urls can be abbreviations like :thedailyshow or :colbert
 | 
					    # urls can be abbreviations like :thedailyshow
 | 
				
			||||||
    # urls for episodes like:
 | 
					    # urls for episodes like:
 | 
				
			||||||
    # or urls for clips like: http://www.thedailyshow.com/watch/mon-december-10-2012/any-given-gun-day
 | 
					    # or urls for clips like: http://www.thedailyshow.com/watch/mon-december-10-2012/any-given-gun-day
 | 
				
			||||||
    #                     or: http://www.colbertnation.com/the-colbert-report-videos/421667/november-29-2012/moon-shattering-news
 | 
					    #                     or: http://www.colbertnation.com/the-colbert-report-videos/421667/november-29-2012/moon-shattering-news
 | 
				
			||||||
    #                     or: http://www.colbertnation.com/the-colbert-report-collections/422008/festival-of-lights/79524
 | 
					    #                     or: http://www.colbertnation.com/the-colbert-report-collections/422008/festival-of-lights/79524
 | 
				
			||||||
    _VALID_URL = r'''(?x)^(:(?P<shortname>tds|thedailyshow|cr|colbert|colbertnation|colbertreport)
 | 
					    _VALID_URL = r'''(?x)^(:(?P<shortname>tds|thedailyshow)
 | 
				
			||||||
                      |https?://(:www\.)?
 | 
					                      |https?://(:www\.)?
 | 
				
			||||||
                          (?P<showname>thedailyshow|thecolbertreport)\.(?:cc\.)?com/
 | 
					                          (?P<showname>thedailyshow|thecolbertreport)\.(?:cc\.)?com/
 | 
				
			||||||
                         ((?:full-)?episodes/(?:[0-9a-z]{6}/)?(?P<episode>.*)|
 | 
					                         ((?:full-)?episodes/(?:[0-9a-z]{6}/)?(?P<episode>.*)|
 | 
				
			||||||
@@ -49,8 +49,10 @@ class ComedyCentralShowsIE(MTVServicesInfoExtractor):
 | 
				
			|||||||
                              |(watch/(?P<date>[^/]*)/(?P<tdstitle>.*))
 | 
					                              |(watch/(?P<date>[^/]*)/(?P<tdstitle>.*))
 | 
				
			||||||
                          )|
 | 
					                          )|
 | 
				
			||||||
                          (?P<interview>
 | 
					                          (?P<interview>
 | 
				
			||||||
                              extended-interviews/(?P<interID>[0-9a-z]+)/(?:playlist_tds_extended_)?(?P<interview_title>.*?)(/.*?)?)))
 | 
					                              extended-interviews/(?P<interID>[0-9a-z]+)/
 | 
				
			||||||
                     (?:[?#].*|$)'''
 | 
					                              (?:playlist_tds_extended_)?(?P<interview_title>[^/?#]*?)
 | 
				
			||||||
 | 
					                              (?:/[^/?#]?|[?#]|$))))
 | 
				
			||||||
 | 
					                     '''
 | 
				
			||||||
    _TESTS = [{
 | 
					    _TESTS = [{
 | 
				
			||||||
        'url': 'http://thedailyshow.cc.com/watch/thu-december-13-2012/kristen-stewart',
 | 
					        'url': 'http://thedailyshow.cc.com/watch/thu-december-13-2012/kristen-stewart',
 | 
				
			||||||
        'md5': '4e2f5cb088a83cd8cdb7756132f9739d',
 | 
					        'md5': '4e2f5cb088a83cd8cdb7756132f9739d',
 | 
				
			||||||
@@ -62,6 +64,38 @@ class ComedyCentralShowsIE(MTVServicesInfoExtractor):
 | 
				
			|||||||
            'uploader': 'thedailyshow',
 | 
					            'uploader': 'thedailyshow',
 | 
				
			||||||
            'title': 'thedailyshow kristen-stewart part 1',
 | 
					            'title': 'thedailyshow kristen-stewart part 1',
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					    }, {
 | 
				
			||||||
 | 
					        'url': 'http://thedailyshow.cc.com/extended-interviews/b6364d/sarah-chayes-extended-interview',
 | 
				
			||||||
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'id': 'sarah-chayes-extended-interview',
 | 
				
			||||||
 | 
					            'description': 'Carnegie Endowment Senior Associate Sarah Chayes discusses how corrupt institutions function throughout the world in her book "Thieves of State: Why Corruption Threatens Global Security."',
 | 
				
			||||||
 | 
					            'title': 'thedailyshow Sarah Chayes Extended Interview',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        'playlist': [
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                'info_dict': {
 | 
				
			||||||
 | 
					                    'id': '0baad492-cbec-4ec1-9e50-ad91c291127f',
 | 
				
			||||||
 | 
					                    'ext': 'mp4',
 | 
				
			||||||
 | 
					                    'upload_date': '20150129',
 | 
				
			||||||
 | 
					                    'description': 'Carnegie Endowment Senior Associate Sarah Chayes discusses how corrupt institutions function throughout the world in her book "Thieves of State: Why Corruption Threatens Global Security."',
 | 
				
			||||||
 | 
					                    'uploader': 'thedailyshow',
 | 
				
			||||||
 | 
					                    'title': 'thedailyshow sarah-chayes-extended-interview part 1',
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                'info_dict': {
 | 
				
			||||||
 | 
					                    'id': '1e4fb91b-8ce7-4277-bd7c-98c9f1bbd283',
 | 
				
			||||||
 | 
					                    'ext': 'mp4',
 | 
				
			||||||
 | 
					                    'upload_date': '20150129',
 | 
				
			||||||
 | 
					                    'description': 'Carnegie Endowment Senior Associate Sarah Chayes discusses how corrupt institutions function throughout the world in her book "Thieves of State: Why Corruption Threatens Global Security."',
 | 
				
			||||||
 | 
					                    'uploader': 'thedailyshow',
 | 
				
			||||||
 | 
					                    'title': 'thedailyshow sarah-chayes-extended-interview part 2',
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        'params': {
 | 
				
			||||||
 | 
					            'skip_download': True,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
    }, {
 | 
					    }, {
 | 
				
			||||||
        'url': 'http://thedailyshow.cc.com/extended-interviews/xm3fnq/andrew-napolitano-extended-interview',
 | 
					        'url': 'http://thedailyshow.cc.com/extended-interviews/xm3fnq/andrew-napolitano-extended-interview',
 | 
				
			||||||
        'only_matching': True,
 | 
					        'only_matching': True,
 | 
				
			||||||
@@ -83,6 +117,9 @@ class ComedyCentralShowsIE(MTVServicesInfoExtractor):
 | 
				
			|||||||
    }, {
 | 
					    }, {
 | 
				
			||||||
        'url': 'http://thedailyshow.cc.com/video-playlists/npde3s/the-daily-show-19088-highlights',
 | 
					        'url': 'http://thedailyshow.cc.com/video-playlists/npde3s/the-daily-show-19088-highlights',
 | 
				
			||||||
        'only_matching': True,
 | 
					        'only_matching': True,
 | 
				
			||||||
 | 
					    }, {
 | 
				
			||||||
 | 
					        'url': 'http://thedailyshow.cc.com/video-playlists/t6d9sg/the-daily-show-20038-highlights/be3cwo',
 | 
				
			||||||
 | 
					        'only_matching': True,
 | 
				
			||||||
    }, {
 | 
					    }, {
 | 
				
			||||||
        'url': 'http://thedailyshow.cc.com/special-editions/2l8fdb/special-edition---a-look-back-at-food',
 | 
					        'url': 'http://thedailyshow.cc.com/special-editions/2l8fdb/special-edition---a-look-back-at-food',
 | 
				
			||||||
        'only_matching': True,
 | 
					        'only_matching': True,
 | 
				
			||||||
@@ -213,6 +250,8 @@ class ComedyCentralShowsIE(MTVServicesInfoExtractor):
 | 
				
			|||||||
                })
 | 
					                })
 | 
				
			||||||
                self._sort_formats(formats)
 | 
					                self._sort_formats(formats)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            subtitles = self._extract_subtitles(cdoc, guid)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            virtual_id = show_name + ' ' + epTitle + ' part ' + compat_str(part_num + 1)
 | 
					            virtual_id = show_name + ' ' + epTitle + ' part ' + compat_str(part_num + 1)
 | 
				
			||||||
            entries.append({
 | 
					            entries.append({
 | 
				
			||||||
                'id': guid,
 | 
					                'id': guid,
 | 
				
			||||||
@@ -223,10 +262,12 @@ class ComedyCentralShowsIE(MTVServicesInfoExtractor):
 | 
				
			|||||||
                'duration': duration,
 | 
					                'duration': duration,
 | 
				
			||||||
                'thumbnail': thumbnail,
 | 
					                'thumbnail': thumbnail,
 | 
				
			||||||
                'description': description,
 | 
					                'description': description,
 | 
				
			||||||
 | 
					                'subtitles': subtitles,
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            '_type': 'playlist',
 | 
					            '_type': 'playlist',
 | 
				
			||||||
 | 
					            'id': epTitle,
 | 
				
			||||||
            'entries': entries,
 | 
					            'entries': entries,
 | 
				
			||||||
            'title': show_name + ' ' + title,
 | 
					            'title': show_name + ' ' + title,
 | 
				
			||||||
            'description': description,
 | 
					            'description': description,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,6 +14,7 @@ import xml.etree.ElementTree
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from ..compat import (
 | 
					from ..compat import (
 | 
				
			||||||
    compat_cookiejar,
 | 
					    compat_cookiejar,
 | 
				
			||||||
 | 
					    compat_HTTPError,
 | 
				
			||||||
    compat_http_client,
 | 
					    compat_http_client,
 | 
				
			||||||
    compat_urllib_error,
 | 
					    compat_urllib_error,
 | 
				
			||||||
    compat_urllib_parse_urlparse,
 | 
					    compat_urllib_parse_urlparse,
 | 
				
			||||||
@@ -21,6 +22,7 @@ from ..compat import (
 | 
				
			|||||||
    compat_str,
 | 
					    compat_str,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from ..utils import (
 | 
					from ..utils import (
 | 
				
			||||||
 | 
					    age_restricted,
 | 
				
			||||||
    clean_html,
 | 
					    clean_html,
 | 
				
			||||||
    compiled_regex_type,
 | 
					    compiled_regex_type,
 | 
				
			||||||
    ExtractorError,
 | 
					    ExtractorError,
 | 
				
			||||||
@@ -40,7 +42,7 @@ class InfoExtractor(object):
 | 
				
			|||||||
    information about the video (or videos) the URL refers to. This
 | 
					    information about the video (or videos) the URL refers to. This
 | 
				
			||||||
    information includes the real video URL, the video title, author and
 | 
					    information includes the real video URL, the video title, author and
 | 
				
			||||||
    others. The information is stored in a dictionary which is then
 | 
					    others. The information is stored in a dictionary which is then
 | 
				
			||||||
    passed to the FileDownloader. The FileDownloader processes this
 | 
					    passed to the YoutubeDL. The YoutubeDL processes this
 | 
				
			||||||
    information possibly downloading the video to the file system, among
 | 
					    information possibly downloading the video to the file system, among
 | 
				
			||||||
    other possible outcomes.
 | 
					    other possible outcomes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -86,12 +88,15 @@ class InfoExtractor(object):
 | 
				
			|||||||
                    * player_url SWF Player URL (used for rtmpdump).
 | 
					                    * player_url SWF Player URL (used for rtmpdump).
 | 
				
			||||||
                    * protocol   The protocol that will be used for the actual
 | 
					                    * protocol   The protocol that will be used for the actual
 | 
				
			||||||
                                 download, lower-case.
 | 
					                                 download, lower-case.
 | 
				
			||||||
                                 "http", "https", "rtsp", "rtmp", "m3u8" or so.
 | 
					                                 "http", "https", "rtsp", "rtmp", "rtmpe",
 | 
				
			||||||
 | 
					                                 "m3u8", or "m3u8_native".
 | 
				
			||||||
                    * preference Order number of this format. If this field is
 | 
					                    * preference Order number of this format. If this field is
 | 
				
			||||||
                                 present and not None, the formats get sorted
 | 
					                                 present and not None, the formats get sorted
 | 
				
			||||||
                                 by this field, regardless of all other values.
 | 
					                                 by this field, regardless of all other values.
 | 
				
			||||||
                                 -1 for default (order by other properties),
 | 
					                                 -1 for default (order by other properties),
 | 
				
			||||||
                                 -2 or smaller for less than default.
 | 
					                                 -2 or smaller for less than default.
 | 
				
			||||||
 | 
					                                 < -1000 to hide the format (if there is
 | 
				
			||||||
 | 
					                                    another one which is strictly better)
 | 
				
			||||||
                    * language_preference  Is this in the correct requested
 | 
					                    * language_preference  Is this in the correct requested
 | 
				
			||||||
                                 language?
 | 
					                                 language?
 | 
				
			||||||
                                 10 if it's what the URL is about,
 | 
					                                 10 if it's what the URL is about,
 | 
				
			||||||
@@ -105,12 +110,17 @@ class InfoExtractor(object):
 | 
				
			|||||||
                                  (quality takes higher priority)
 | 
					                                  (quality takes higher priority)
 | 
				
			||||||
                                 -1 for default (order by other properties),
 | 
					                                 -1 for default (order by other properties),
 | 
				
			||||||
                                 -2 or smaller for less than default.
 | 
					                                 -2 or smaller for less than default.
 | 
				
			||||||
                    * http_referer  HTTP Referer header value to set.
 | 
					 | 
				
			||||||
                    * http_method  HTTP method to use for the download.
 | 
					                    * http_method  HTTP method to use for the download.
 | 
				
			||||||
                    * http_headers  A dictionary of additional HTTP headers
 | 
					                    * http_headers  A dictionary of additional HTTP headers
 | 
				
			||||||
                                 to add to the request.
 | 
					                                 to add to the request.
 | 
				
			||||||
                    * http_post_data  Additional data to send with a POST
 | 
					                    * http_post_data  Additional data to send with a POST
 | 
				
			||||||
                                 request.
 | 
					                                 request.
 | 
				
			||||||
 | 
					                    * stretched_ratio  If given and not 1, indicates that the
 | 
				
			||||||
 | 
					                                 video's pixels are not square.
 | 
				
			||||||
 | 
					                                 width : height ratio as float.
 | 
				
			||||||
 | 
					                    * no_resume  The server does not support resuming the
 | 
				
			||||||
 | 
					                                 (HTTP or RTMP) download. Boolean.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    url:            Final video URL.
 | 
					    url:            Final video URL.
 | 
				
			||||||
    ext:            Video filename extension.
 | 
					    ext:            Video filename extension.
 | 
				
			||||||
    format:         The video format, defaults to ext (used for --get-format)
 | 
					    format:         The video format, defaults to ext (used for --get-format)
 | 
				
			||||||
@@ -124,7 +134,9 @@ class InfoExtractor(object):
 | 
				
			|||||||
                    something like "4234987", title "Dancing naked mole rats",
 | 
					                    something like "4234987", title "Dancing naked mole rats",
 | 
				
			||||||
                    and display_id "dancing-naked-mole-rats"
 | 
					                    and display_id "dancing-naked-mole-rats"
 | 
				
			||||||
    thumbnails:     A list of dictionaries, with the following entries:
 | 
					    thumbnails:     A list of dictionaries, with the following entries:
 | 
				
			||||||
 | 
					                        * "id" (optional, string) - Thumbnail format ID
 | 
				
			||||||
                        * "url"
 | 
					                        * "url"
 | 
				
			||||||
 | 
					                        * "preference" (optional, int) - quality of the image
 | 
				
			||||||
                        * "width" (optional, int)
 | 
					                        * "width" (optional, int)
 | 
				
			||||||
                        * "height" (optional, int)
 | 
					                        * "height" (optional, int)
 | 
				
			||||||
                        * "resolution" (optional, string "{width}x{height"},
 | 
					                        * "resolution" (optional, string "{width}x{height"},
 | 
				
			||||||
@@ -132,18 +144,37 @@ class InfoExtractor(object):
 | 
				
			|||||||
    thumbnail:      Full URL to a video thumbnail image.
 | 
					    thumbnail:      Full URL to a video thumbnail image.
 | 
				
			||||||
    description:    Full video description.
 | 
					    description:    Full video description.
 | 
				
			||||||
    uploader:       Full name of the video uploader.
 | 
					    uploader:       Full name of the video uploader.
 | 
				
			||||||
 | 
					    creator:        The main artist who created the video.
 | 
				
			||||||
    timestamp:      UNIX timestamp of the moment the video became available.
 | 
					    timestamp:      UNIX timestamp of the moment the video became available.
 | 
				
			||||||
    upload_date:    Video upload date (YYYYMMDD).
 | 
					    upload_date:    Video upload date (YYYYMMDD).
 | 
				
			||||||
                    If not explicitly set, calculated from timestamp.
 | 
					                    If not explicitly set, calculated from timestamp.
 | 
				
			||||||
    uploader_id:    Nickname or id of the video uploader.
 | 
					    uploader_id:    Nickname or id of the video uploader.
 | 
				
			||||||
    location:       Physical location where the video was filmed.
 | 
					    location:       Physical location where the video was filmed.
 | 
				
			||||||
    subtitles:      The subtitle file contents as a dictionary in the format
 | 
					    subtitles:      The available subtitles as a dictionary in the format
 | 
				
			||||||
                    {language: subtitles}.
 | 
					                    {language: subformats}. "subformats" is a list sorted from
 | 
				
			||||||
 | 
					                    lower to higher preference, each element is a dictionary
 | 
				
			||||||
 | 
					                    with the "ext" entry and one of:
 | 
				
			||||||
 | 
					                        * "data": The subtitles file contents
 | 
				
			||||||
 | 
					                        * "url": A url pointing to the subtitles file
 | 
				
			||||||
 | 
					    automatic_captions: Like 'subtitles', used by the YoutubeIE for
 | 
				
			||||||
 | 
					                    automatically generated captions
 | 
				
			||||||
    duration:       Length of the video in seconds, as an integer.
 | 
					    duration:       Length of the video in seconds, as an integer.
 | 
				
			||||||
    view_count:     How many users have watched the video on the platform.
 | 
					    view_count:     How many users have watched the video on the platform.
 | 
				
			||||||
    like_count:     Number of positive ratings of the video
 | 
					    like_count:     Number of positive ratings of the video
 | 
				
			||||||
    dislike_count:  Number of negative ratings of the video
 | 
					    dislike_count:  Number of negative ratings of the video
 | 
				
			||||||
 | 
					    average_rating: Average rating give by users, the scale used depends on the webpage
 | 
				
			||||||
    comment_count:  Number of comments on the video
 | 
					    comment_count:  Number of comments on the video
 | 
				
			||||||
 | 
					    comments:       A list of comments, each with one or more of the following
 | 
				
			||||||
 | 
					                    properties (all but one of text or html optional):
 | 
				
			||||||
 | 
					                        * "author" - human-readable name of the comment author
 | 
				
			||||||
 | 
					                        * "author_id" - user ID of the comment author
 | 
				
			||||||
 | 
					                        * "id" - Comment ID
 | 
				
			||||||
 | 
					                        * "html" - Comment as HTML
 | 
				
			||||||
 | 
					                        * "text" - Plain text of the comment
 | 
				
			||||||
 | 
					                        * "timestamp" - UNIX timestamp of comment
 | 
				
			||||||
 | 
					                        * "parent" - ID of the comment this one is replying to.
 | 
				
			||||||
 | 
					                                     Set to "root" to indicate that this is a
 | 
				
			||||||
 | 
					                                     comment to the original video.
 | 
				
			||||||
    age_limit:      Age restriction for the video, as an integer (years)
 | 
					    age_limit:      Age restriction for the video, as an integer (years)
 | 
				
			||||||
    webpage_url:    The url to the video webpage, if given to youtube-dl it
 | 
					    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
 | 
					                    should allow to get the same result again. (It will be set
 | 
				
			||||||
@@ -239,8 +270,15 @@ class InfoExtractor(object):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def extract(self, url):
 | 
					    def extract(self, url):
 | 
				
			||||||
        """Extracts URL information and returns it in list of dicts."""
 | 
					        """Extracts URL information and returns it in list of dicts."""
 | 
				
			||||||
        self.initialize()
 | 
					        try:
 | 
				
			||||||
        return self._real_extract(url)
 | 
					            self.initialize()
 | 
				
			||||||
 | 
					            return self._real_extract(url)
 | 
				
			||||||
 | 
					        except ExtractorError:
 | 
				
			||||||
 | 
					            raise
 | 
				
			||||||
 | 
					        except compat_http_client.IncompleteRead as e:
 | 
				
			||||||
 | 
					            raise ExtractorError('A network error has occured.', cause=e, expected=True)
 | 
				
			||||||
 | 
					        except (KeyError, StopIteration) as e:
 | 
				
			||||||
 | 
					            raise ExtractorError('An extractor error has occured.', cause=e)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def set_downloader(self, downloader):
 | 
					    def set_downloader(self, downloader):
 | 
				
			||||||
        """Sets the downloader for this IE."""
 | 
					        """Sets the downloader for this IE."""
 | 
				
			||||||
@@ -359,12 +397,32 @@ class InfoExtractor(object):
 | 
				
			|||||||
            if blocked_iframe:
 | 
					            if blocked_iframe:
 | 
				
			||||||
                msg += ' Visit %s for more details' % blocked_iframe
 | 
					                msg += ' Visit %s for more details' % blocked_iframe
 | 
				
			||||||
            raise ExtractorError(msg, expected=True)
 | 
					            raise ExtractorError(msg, expected=True)
 | 
				
			||||||
 | 
					        if '<title>The URL you requested has been blocked</title>' in content[:512]:
 | 
				
			||||||
 | 
					            msg = (
 | 
				
			||||||
 | 
					                'Access to this webpage has been blocked by Indian censorship. '
 | 
				
			||||||
 | 
					                'Use a VPN or proxy server (with --proxy) to route around it.')
 | 
				
			||||||
 | 
					            block_msg = self._html_search_regex(
 | 
				
			||||||
 | 
					                r'</h1><p>(.*?)</p>',
 | 
				
			||||||
 | 
					                content, 'block message', default=None)
 | 
				
			||||||
 | 
					            if block_msg:
 | 
				
			||||||
 | 
					                msg += ' (Message: "%s")' % block_msg.replace('\n', ' ')
 | 
				
			||||||
 | 
					            raise ExtractorError(msg, expected=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return content
 | 
					        return content
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _download_webpage(self, url_or_request, video_id, note=None, errnote=None, fatal=True):
 | 
					    def _download_webpage(self, url_or_request, video_id, note=None, errnote=None, fatal=True, tries=1, timeout=5):
 | 
				
			||||||
        """ Returns the data of the page as a string """
 | 
					        """ Returns the data of the page as a string """
 | 
				
			||||||
        res = self._download_webpage_handle(url_or_request, video_id, note, errnote, fatal)
 | 
					        success = False
 | 
				
			||||||
 | 
					        try_count = 0
 | 
				
			||||||
 | 
					        while success is False:
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                res = self._download_webpage_handle(url_or_request, video_id, note, errnote, fatal)
 | 
				
			||||||
 | 
					                success = True
 | 
				
			||||||
 | 
					            except compat_http_client.IncompleteRead as e:
 | 
				
			||||||
 | 
					                try_count += 1
 | 
				
			||||||
 | 
					                if try_count >= tries:
 | 
				
			||||||
 | 
					                    raise e
 | 
				
			||||||
 | 
					                self._sleep(timeout, video_id)
 | 
				
			||||||
        if res is False:
 | 
					        if res is False:
 | 
				
			||||||
            return res
 | 
					            return res
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
@@ -472,7 +530,7 @@ class InfoExtractor(object):
 | 
				
			|||||||
                if mobj:
 | 
					                if mobj:
 | 
				
			||||||
                    break
 | 
					                    break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if os.name != 'nt' and sys.stderr.isatty():
 | 
					        if not self._downloader.params.get('no_color') and os.name != 'nt' and sys.stderr.isatty():
 | 
				
			||||||
            _name = '\033[0;34m%s\033[0m' % name
 | 
					            _name = '\033[0;34m%s\033[0m' % name
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            _name = name
 | 
					            _name = name
 | 
				
			||||||
@@ -589,9 +647,9 @@ class InfoExtractor(object):
 | 
				
			|||||||
        if display_name is None:
 | 
					        if display_name is None:
 | 
				
			||||||
            display_name = name
 | 
					            display_name = name
 | 
				
			||||||
        return self._html_search_regex(
 | 
					        return self._html_search_regex(
 | 
				
			||||||
            r'''(?ix)<meta
 | 
					            r'''(?isx)<meta
 | 
				
			||||||
                    (?=[^>]+(?:itemprop|name|property)=(["\']?)%s\1)
 | 
					                    (?=[^>]+(?:itemprop|name|property)=(["\']?)%s\1)
 | 
				
			||||||
                    [^>]+content=(["\'])(?P<content>.*?)\1''' % re.escape(name),
 | 
					                    [^>]+?content=(["\'])(?P<content>.*?)\2''' % re.escape(name),
 | 
				
			||||||
            html, display_name, fatal=fatal, group='content', **kwargs)
 | 
					            html, display_name, fatal=fatal, group='content', **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _dc_search_uploader(self, html):
 | 
					    def _dc_search_uploader(self, html):
 | 
				
			||||||
@@ -621,6 +679,21 @@ class InfoExtractor(object):
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        return RATING_TABLE.get(rating.lower(), None)
 | 
					        return RATING_TABLE.get(rating.lower(), None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _family_friendly_search(self, html):
 | 
				
			||||||
 | 
					        # See http://schema.org/VideoObject
 | 
				
			||||||
 | 
					        family_friendly = self._html_search_meta('isFamilyFriendly', html)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not family_friendly:
 | 
				
			||||||
 | 
					            return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        RATING_TABLE = {
 | 
				
			||||||
 | 
					            '1': 0,
 | 
				
			||||||
 | 
					            'true': 0,
 | 
				
			||||||
 | 
					            '0': 18,
 | 
				
			||||||
 | 
					            'false': 18,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return RATING_TABLE.get(family_friendly.lower(), None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _twitter_search_player(self, html):
 | 
					    def _twitter_search_player(self, html):
 | 
				
			||||||
        return self._html_search_meta('twitter:player', html,
 | 
					        return self._html_search_meta('twitter:player', html,
 | 
				
			||||||
                                      'twitter card player')
 | 
					                                      'twitter card player')
 | 
				
			||||||
@@ -670,21 +743,44 @@ class InfoExtractor(object):
 | 
				
			|||||||
                preference,
 | 
					                preference,
 | 
				
			||||||
                f.get('language_preference') if f.get('language_preference') is not None else -1,
 | 
					                f.get('language_preference') if f.get('language_preference') is not None else -1,
 | 
				
			||||||
                f.get('quality') if f.get('quality') is not None else -1,
 | 
					                f.get('quality') if f.get('quality') is not None else -1,
 | 
				
			||||||
 | 
					                f.get('tbr') if f.get('tbr') is not None else -1,
 | 
				
			||||||
 | 
					                f.get('filesize') if f.get('filesize') is not None else -1,
 | 
				
			||||||
 | 
					                f.get('vbr') if f.get('vbr') is not None else -1,
 | 
				
			||||||
                f.get('height') if f.get('height') is not None else -1,
 | 
					                f.get('height') if f.get('height') is not None else -1,
 | 
				
			||||||
                f.get('width') if f.get('width') is not None else -1,
 | 
					                f.get('width') if f.get('width') is not None else -1,
 | 
				
			||||||
                ext_preference,
 | 
					                ext_preference,
 | 
				
			||||||
                f.get('tbr') if f.get('tbr') is not None else -1,
 | 
					 | 
				
			||||||
                f.get('vbr') if f.get('vbr') is not None else -1,
 | 
					 | 
				
			||||||
                f.get('abr') if f.get('abr') is not None else -1,
 | 
					                f.get('abr') if f.get('abr') is not None else -1,
 | 
				
			||||||
                audio_ext_preference,
 | 
					                audio_ext_preference,
 | 
				
			||||||
                f.get('fps') if f.get('fps') is not None else -1,
 | 
					                f.get('fps') if f.get('fps') is not None else -1,
 | 
				
			||||||
                f.get('filesize') if f.get('filesize') is not None else -1,
 | 
					 | 
				
			||||||
                f.get('filesize_approx') if f.get('filesize_approx') is not None else -1,
 | 
					                f.get('filesize_approx') if f.get('filesize_approx') is not None else -1,
 | 
				
			||||||
                f.get('source_preference') if f.get('source_preference') is not None else -1,
 | 
					                f.get('source_preference') if f.get('source_preference') is not None else -1,
 | 
				
			||||||
                f.get('format_id'),
 | 
					                f.get('format_id'),
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        formats.sort(key=_formats_key)
 | 
					        formats.sort(key=_formats_key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _check_formats(self, formats, video_id):
 | 
				
			||||||
 | 
					        if formats:
 | 
				
			||||||
 | 
					            formats[:] = filter(
 | 
				
			||||||
 | 
					                lambda f: self._is_valid_url(
 | 
				
			||||||
 | 
					                    f['url'], video_id,
 | 
				
			||||||
 | 
					                    item='%s video format' % f.get('format_id') if f.get('format_id') else 'video'),
 | 
				
			||||||
 | 
					                formats)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _is_valid_url(self, url, video_id, item='video'):
 | 
				
			||||||
 | 
					        url = self._proto_relative_url(url, scheme='http:')
 | 
				
			||||||
 | 
					        # For now assume non HTTP(S) URLs always valid
 | 
				
			||||||
 | 
					        if not (url.startswith('http://') or url.startswith('https://')):
 | 
				
			||||||
 | 
					            return True
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            self._request_webpage(url, video_id, 'Checking %s URL' % item)
 | 
				
			||||||
 | 
					            return True
 | 
				
			||||||
 | 
					        except ExtractorError as e:
 | 
				
			||||||
 | 
					            if isinstance(e.cause, compat_HTTPError):
 | 
				
			||||||
 | 
					                self.report_warning(
 | 
				
			||||||
 | 
					                    '%s URL is invalid, skipping' % item, video_id)
 | 
				
			||||||
 | 
					                return False
 | 
				
			||||||
 | 
					            raise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def http_scheme(self):
 | 
					    def http_scheme(self):
 | 
				
			||||||
        """ Either "http:" or "https:", depending on the user's preferences """
 | 
					        """ Either "http:" or "https:", depending on the user's preferences """
 | 
				
			||||||
        return (
 | 
					        return (
 | 
				
			||||||
@@ -709,37 +805,45 @@ class InfoExtractor(object):
 | 
				
			|||||||
        self.to_screen(msg)
 | 
					        self.to_screen(msg)
 | 
				
			||||||
        time.sleep(timeout)
 | 
					        time.sleep(timeout)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _extract_f4m_formats(self, manifest_url, video_id):
 | 
					    def _extract_f4m_formats(self, manifest_url, video_id, preference=None, f4m_id=None):
 | 
				
			||||||
        manifest = self._download_xml(
 | 
					        manifest = self._download_xml(
 | 
				
			||||||
            manifest_url, video_id, 'Downloading f4m manifest',
 | 
					            manifest_url, video_id, 'Downloading f4m manifest',
 | 
				
			||||||
            'Unable to download f4m manifest')
 | 
					            'Unable to download f4m manifest')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        formats = []
 | 
					        formats = []
 | 
				
			||||||
 | 
					        manifest_version = '1.0'
 | 
				
			||||||
        media_nodes = manifest.findall('{http://ns.adobe.com/f4m/1.0}media')
 | 
					        media_nodes = manifest.findall('{http://ns.adobe.com/f4m/1.0}media')
 | 
				
			||||||
 | 
					        if not media_nodes:
 | 
				
			||||||
 | 
					            manifest_version = '2.0'
 | 
				
			||||||
 | 
					            media_nodes = manifest.findall('{http://ns.adobe.com/f4m/2.0}media')
 | 
				
			||||||
        for i, media_el in enumerate(media_nodes):
 | 
					        for i, media_el in enumerate(media_nodes):
 | 
				
			||||||
 | 
					            if manifest_version == '2.0':
 | 
				
			||||||
 | 
					                manifest_url = ('/'.join(manifest_url.split('/')[:-1]) + '/' +
 | 
				
			||||||
 | 
					                                (media_el.attrib.get('href') or media_el.attrib.get('url')))
 | 
				
			||||||
            tbr = int_or_none(media_el.attrib.get('bitrate'))
 | 
					            tbr = int_or_none(media_el.attrib.get('bitrate'))
 | 
				
			||||||
            format_id = 'f4m-%d' % (i if tbr is None else tbr)
 | 
					 | 
				
			||||||
            formats.append({
 | 
					            formats.append({
 | 
				
			||||||
                'format_id': format_id,
 | 
					                'format_id': '-'.join(filter(None, [f4m_id, 'f4m-%d' % (i if tbr is None else tbr)])),
 | 
				
			||||||
                'url': manifest_url,
 | 
					                'url': manifest_url,
 | 
				
			||||||
                'ext': 'flv',
 | 
					                'ext': 'flv',
 | 
				
			||||||
                'tbr': tbr,
 | 
					                'tbr': tbr,
 | 
				
			||||||
                'width': int_or_none(media_el.attrib.get('width')),
 | 
					                'width': int_or_none(media_el.attrib.get('width')),
 | 
				
			||||||
                'height': int_or_none(media_el.attrib.get('height')),
 | 
					                'height': int_or_none(media_el.attrib.get('height')),
 | 
				
			||||||
 | 
					                'preference': preference,
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
        self._sort_formats(formats)
 | 
					        self._sort_formats(formats)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return formats
 | 
					        return formats
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _extract_m3u8_formats(self, m3u8_url, video_id, ext=None,
 | 
					    def _extract_m3u8_formats(self, m3u8_url, video_id, ext=None,
 | 
				
			||||||
                              entry_protocol='m3u8', preference=None):
 | 
					                              entry_protocol='m3u8', preference=None,
 | 
				
			||||||
 | 
					                              m3u8_id=None):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        formats = [{
 | 
					        formats = [{
 | 
				
			||||||
            'format_id': 'm3u8-meta',
 | 
					            'format_id': '-'.join(filter(None, [m3u8_id, 'm3u8-meta'])),
 | 
				
			||||||
            'url': m3u8_url,
 | 
					            'url': m3u8_url,
 | 
				
			||||||
            'ext': ext,
 | 
					            'ext': ext,
 | 
				
			||||||
            'protocol': 'm3u8',
 | 
					            'protocol': 'm3u8',
 | 
				
			||||||
            'preference': -1,
 | 
					            'preference': preference - 1 if preference else -1,
 | 
				
			||||||
            'resolution': 'multiple',
 | 
					            'resolution': 'multiple',
 | 
				
			||||||
            'format_note': 'Quality selection URL',
 | 
					            'format_note': 'Quality selection URL',
 | 
				
			||||||
        }]
 | 
					        }]
 | 
				
			||||||
@@ -754,6 +858,7 @@ class InfoExtractor(object):
 | 
				
			|||||||
            note='Downloading m3u8 information',
 | 
					            note='Downloading m3u8 information',
 | 
				
			||||||
            errnote='Failed to download m3u8 information')
 | 
					            errnote='Failed to download m3u8 information')
 | 
				
			||||||
        last_info = None
 | 
					        last_info = None
 | 
				
			||||||
 | 
					        last_media = None
 | 
				
			||||||
        kv_rex = re.compile(
 | 
					        kv_rex = re.compile(
 | 
				
			||||||
            r'(?P<key>[a-zA-Z_-]+)=(?P<val>"[^"]+"|[^",]+)(?:,|$)')
 | 
					            r'(?P<key>[a-zA-Z_-]+)=(?P<val>"[^"]+"|[^",]+)(?:,|$)')
 | 
				
			||||||
        for line in m3u8_doc.splitlines():
 | 
					        for line in m3u8_doc.splitlines():
 | 
				
			||||||
@@ -764,6 +869,13 @@ class InfoExtractor(object):
 | 
				
			|||||||
                    if v.startswith('"'):
 | 
					                    if v.startswith('"'):
 | 
				
			||||||
                        v = v[1:-1]
 | 
					                        v = v[1:-1]
 | 
				
			||||||
                    last_info[m.group('key')] = v
 | 
					                    last_info[m.group('key')] = v
 | 
				
			||||||
 | 
					            elif line.startswith('#EXT-X-MEDIA:'):
 | 
				
			||||||
 | 
					                last_media = {}
 | 
				
			||||||
 | 
					                for m in kv_rex.finditer(line):
 | 
				
			||||||
 | 
					                    v = m.group('val')
 | 
				
			||||||
 | 
					                    if v.startswith('"'):
 | 
				
			||||||
 | 
					                        v = v[1:-1]
 | 
				
			||||||
 | 
					                    last_media[m.group('key')] = v
 | 
				
			||||||
            elif line.startswith('#') or not line.strip():
 | 
					            elif line.startswith('#') or not line.strip():
 | 
				
			||||||
                continue
 | 
					                continue
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
@@ -771,9 +883,8 @@ class InfoExtractor(object):
 | 
				
			|||||||
                    formats.append({'url': format_url(line)})
 | 
					                    formats.append({'url': format_url(line)})
 | 
				
			||||||
                    continue
 | 
					                    continue
 | 
				
			||||||
                tbr = int_or_none(last_info.get('BANDWIDTH'), scale=1000)
 | 
					                tbr = int_or_none(last_info.get('BANDWIDTH'), scale=1000)
 | 
				
			||||||
 | 
					 | 
				
			||||||
                f = {
 | 
					                f = {
 | 
				
			||||||
                    'format_id': 'm3u8-%d' % (tbr if tbr else len(formats)),
 | 
					                    'format_id': '-'.join(filter(None, [m3u8_id, 'm3u8-%d' % (tbr if tbr else len(formats))])),
 | 
				
			||||||
                    'url': format_url(line.strip()),
 | 
					                    'url': format_url(line.strip()),
 | 
				
			||||||
                    'tbr': tbr,
 | 
					                    'tbr': tbr,
 | 
				
			||||||
                    'ext': ext,
 | 
					                    'ext': ext,
 | 
				
			||||||
@@ -793,54 +904,78 @@ class InfoExtractor(object):
 | 
				
			|||||||
                    width_str, height_str = resolution.split('x')
 | 
					                    width_str, height_str = resolution.split('x')
 | 
				
			||||||
                    f['width'] = int(width_str)
 | 
					                    f['width'] = int(width_str)
 | 
				
			||||||
                    f['height'] = int(height_str)
 | 
					                    f['height'] = int(height_str)
 | 
				
			||||||
 | 
					                if last_media is not None:
 | 
				
			||||||
 | 
					                    f['m3u8_media'] = last_media
 | 
				
			||||||
 | 
					                    last_media = None
 | 
				
			||||||
                formats.append(f)
 | 
					                formats.append(f)
 | 
				
			||||||
                last_info = {}
 | 
					                last_info = {}
 | 
				
			||||||
        self._sort_formats(formats)
 | 
					        self._sort_formats(formats)
 | 
				
			||||||
        return formats
 | 
					        return formats
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # TODO: improve extraction
 | 
					    # TODO: improve extraction
 | 
				
			||||||
    def _extract_smil_formats(self, smil_url, video_id):
 | 
					    def _extract_smil_formats(self, smil_url, video_id, fatal=True):
 | 
				
			||||||
        smil = self._download_xml(
 | 
					        smil = self._download_xml(
 | 
				
			||||||
            smil_url, video_id, 'Downloading SMIL file',
 | 
					            smil_url, video_id, 'Downloading SMIL file',
 | 
				
			||||||
            'Unable to download SMIL file')
 | 
					            'Unable to download SMIL file', fatal=fatal)
 | 
				
			||||||
 | 
					        if smil is False:
 | 
				
			||||||
 | 
					            assert not fatal
 | 
				
			||||||
 | 
					            return []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        base = smil.find('./head/meta').get('base')
 | 
					        base = smil.find('./head/meta').get('base')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        formats = []
 | 
					        formats = []
 | 
				
			||||||
        rtmp_count = 0
 | 
					        rtmp_count = 0
 | 
				
			||||||
        for video in smil.findall('./body/switch/video'):
 | 
					        if smil.findall('./body/seq/video'):
 | 
				
			||||||
            src = video.get('src')
 | 
					            video = smil.findall('./body/seq/video')[0]
 | 
				
			||||||
            if not src:
 | 
					            fmts, rtmp_count = self._parse_smil_video(video, video_id, base, rtmp_count)
 | 
				
			||||||
                continue
 | 
					            formats.extend(fmts)
 | 
				
			||||||
            bitrate = int_or_none(video.get('system-bitrate') or video.get('systemBitrate'), 1000)
 | 
					        else:
 | 
				
			||||||
            width = int_or_none(video.get('width'))
 | 
					            for video in smil.findall('./body/switch/video'):
 | 
				
			||||||
            height = int_or_none(video.get('height'))
 | 
					                fmts, rtmp_count = self._parse_smil_video(video, video_id, base, rtmp_count)
 | 
				
			||||||
            proto = video.get('proto')
 | 
					                formats.extend(fmts)
 | 
				
			||||||
            if not proto:
 | 
					
 | 
				
			||||||
                if base:
 | 
					 | 
				
			||||||
                    if base.startswith('rtmp'):
 | 
					 | 
				
			||||||
                        proto = 'rtmp'
 | 
					 | 
				
			||||||
                    elif base.startswith('http'):
 | 
					 | 
				
			||||||
                        proto = 'http'
 | 
					 | 
				
			||||||
            ext = video.get('ext')
 | 
					 | 
				
			||||||
            if proto == 'm3u8':
 | 
					 | 
				
			||||||
                formats.extend(self._extract_m3u8_formats(src, video_id, ext))
 | 
					 | 
				
			||||||
            elif proto == 'rtmp':
 | 
					 | 
				
			||||||
                rtmp_count += 1
 | 
					 | 
				
			||||||
                streamer = video.get('streamer') or base
 | 
					 | 
				
			||||||
                formats.append({
 | 
					 | 
				
			||||||
                    'url': streamer,
 | 
					 | 
				
			||||||
                    'play_path': src,
 | 
					 | 
				
			||||||
                    'ext': 'flv',
 | 
					 | 
				
			||||||
                    'format_id': 'rtmp-%d' % (rtmp_count if bitrate is None else bitrate),
 | 
					 | 
				
			||||||
                    'tbr': bitrate,
 | 
					 | 
				
			||||||
                    'width': width,
 | 
					 | 
				
			||||||
                    'height': height,
 | 
					 | 
				
			||||||
                })
 | 
					 | 
				
			||||||
        self._sort_formats(formats)
 | 
					        self._sort_formats(formats)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return formats
 | 
					        return formats
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _parse_smil_video(self, video, video_id, base, rtmp_count):
 | 
				
			||||||
 | 
					        src = video.get('src')
 | 
				
			||||||
 | 
					        if not src:
 | 
				
			||||||
 | 
					            return ([], rtmp_count)
 | 
				
			||||||
 | 
					        bitrate = int_or_none(video.get('system-bitrate') or video.get('systemBitrate'), 1000)
 | 
				
			||||||
 | 
					        width = int_or_none(video.get('width'))
 | 
				
			||||||
 | 
					        height = int_or_none(video.get('height'))
 | 
				
			||||||
 | 
					        proto = video.get('proto')
 | 
				
			||||||
 | 
					        if not proto:
 | 
				
			||||||
 | 
					            if base:
 | 
				
			||||||
 | 
					                if base.startswith('rtmp'):
 | 
				
			||||||
 | 
					                    proto = 'rtmp'
 | 
				
			||||||
 | 
					                elif base.startswith('http'):
 | 
				
			||||||
 | 
					                    proto = 'http'
 | 
				
			||||||
 | 
					        ext = video.get('ext')
 | 
				
			||||||
 | 
					        if proto == 'm3u8':
 | 
				
			||||||
 | 
					            return (self._extract_m3u8_formats(src, video_id, ext), rtmp_count)
 | 
				
			||||||
 | 
					        elif proto == 'rtmp':
 | 
				
			||||||
 | 
					            rtmp_count += 1
 | 
				
			||||||
 | 
					            streamer = video.get('streamer') or base
 | 
				
			||||||
 | 
					            return ([{
 | 
				
			||||||
 | 
					                'url': streamer,
 | 
				
			||||||
 | 
					                'play_path': src,
 | 
				
			||||||
 | 
					                'ext': 'flv',
 | 
				
			||||||
 | 
					                'format_id': 'rtmp-%d' % (rtmp_count if bitrate is None else bitrate),
 | 
				
			||||||
 | 
					                'tbr': bitrate,
 | 
				
			||||||
 | 
					                'width': width,
 | 
				
			||||||
 | 
					                'height': height,
 | 
				
			||||||
 | 
					            }], rtmp_count)
 | 
				
			||||||
 | 
					        elif proto.startswith('http'):
 | 
				
			||||||
 | 
					            return ([{
 | 
				
			||||||
 | 
					                'url': base + src,
 | 
				
			||||||
 | 
					                'ext': ext or 'flv',
 | 
				
			||||||
 | 
					                'tbr': bitrate,
 | 
				
			||||||
 | 
					                'width': width,
 | 
				
			||||||
 | 
					                'height': height,
 | 
				
			||||||
 | 
					            }], rtmp_count)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _live_title(self, name):
 | 
					    def _live_title(self, name):
 | 
				
			||||||
        """ Generate the title for a live video """
 | 
					        """ Generate the title for a live video """
 | 
				
			||||||
        now = datetime.datetime.now()
 | 
					        now = datetime.datetime.now()
 | 
				
			||||||
@@ -875,6 +1010,53 @@ class InfoExtractor(object):
 | 
				
			|||||||
            None, '/', True, False, expire_time, '', None, None, None)
 | 
					            None, '/', True, False, expire_time, '', None, None, None)
 | 
				
			||||||
        self._downloader.cookiejar.set_cookie(cookie)
 | 
					        self._downloader.cookiejar.set_cookie(cookie)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_testcases(self, include_onlymatching=False):
 | 
				
			||||||
 | 
					        t = getattr(self, '_TEST', None)
 | 
				
			||||||
 | 
					        if t:
 | 
				
			||||||
 | 
					            assert not hasattr(self, '_TESTS'), \
 | 
				
			||||||
 | 
					                '%s has _TEST and _TESTS' % type(self).__name__
 | 
				
			||||||
 | 
					            tests = [t]
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            tests = getattr(self, '_TESTS', [])
 | 
				
			||||||
 | 
					        for t in tests:
 | 
				
			||||||
 | 
					            if not include_onlymatching and t.get('only_matching', False):
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            t['name'] = type(self).__name__[:-len('IE')]
 | 
				
			||||||
 | 
					            yield t
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def is_suitable(self, age_limit):
 | 
				
			||||||
 | 
					        """ Test whether the extractor is generally suitable for the given
 | 
				
			||||||
 | 
					        age limit (i.e. pornographic sites are not, all others usually are) """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        any_restricted = False
 | 
				
			||||||
 | 
					        for tc in self.get_testcases(include_onlymatching=False):
 | 
				
			||||||
 | 
					            if 'playlist' in tc:
 | 
				
			||||||
 | 
					                tc = tc['playlist'][0]
 | 
				
			||||||
 | 
					            is_restricted = age_restricted(
 | 
				
			||||||
 | 
					                tc.get('info_dict', {}).get('age_limit'), age_limit)
 | 
				
			||||||
 | 
					            if not is_restricted:
 | 
				
			||||||
 | 
					                return True
 | 
				
			||||||
 | 
					            any_restricted = any_restricted or is_restricted
 | 
				
			||||||
 | 
					        return not any_restricted
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def extract_subtitles(self, *args, **kwargs):
 | 
				
			||||||
 | 
					        if (self._downloader.params.get('writesubtitles', False) or
 | 
				
			||||||
 | 
					                self._downloader.params.get('listsubtitles')):
 | 
				
			||||||
 | 
					            return self._get_subtitles(*args, **kwargs)
 | 
				
			||||||
 | 
					        return {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _get_subtitles(self, *args, **kwargs):
 | 
				
			||||||
 | 
					        raise NotImplementedError("This method must be implemented by subclasses")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def extract_automatic_captions(self, *args, **kwargs):
 | 
				
			||||||
 | 
					        if (self._downloader.params.get('writeautomaticsub', False) or
 | 
				
			||||||
 | 
					                self._downloader.params.get('listsubtitles')):
 | 
				
			||||||
 | 
					            return self._get_automatic_captions(*args, **kwargs)
 | 
				
			||||||
 | 
					        return {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _get_automatic_captions(self, *args, **kwargs):
 | 
				
			||||||
 | 
					        raise NotImplementedError("This method must be implemented by subclasses")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SearchInfoExtractor(InfoExtractor):
 | 
					class SearchInfoExtractor(InfoExtractor):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										46
									
								
								youtube_dl/extractor/commonmistakes.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								youtube_dl/extractor/commonmistakes.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
				
			|||||||
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .common import InfoExtractor
 | 
				
			||||||
 | 
					from ..utils import ExtractorError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CommonMistakesIE(InfoExtractor):
 | 
				
			||||||
 | 
					    IE_DESC = False  # Do not list
 | 
				
			||||||
 | 
					    _VALID_URL = r'''(?x)
 | 
				
			||||||
 | 
					        (?:url|URL)
 | 
				
			||||||
 | 
					    '''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    _TESTS = [{
 | 
				
			||||||
 | 
					        'url': 'url',
 | 
				
			||||||
 | 
					        'only_matching': True,
 | 
				
			||||||
 | 
					    }, {
 | 
				
			||||||
 | 
					        'url': 'URL',
 | 
				
			||||||
 | 
					        'only_matching': True,
 | 
				
			||||||
 | 
					    }]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
 | 
					        msg = (
 | 
				
			||||||
 | 
					            'You\'ve asked youtube-dl to download the URL "%s". '
 | 
				
			||||||
 | 
					            'That doesn\'t make any sense. '
 | 
				
			||||||
 | 
					            'Simply remove the parameter in your command or configuration.'
 | 
				
			||||||
 | 
					        ) % url
 | 
				
			||||||
 | 
					        if not self._downloader.params.get('verbose'):
 | 
				
			||||||
 | 
					            msg += ' Add -v to the command line to see what arguments and configuration youtube-dl got.'
 | 
				
			||||||
 | 
					        raise ExtractorError(msg, expected=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UnicodeBOMIE(InfoExtractor):
 | 
				
			||||||
 | 
					        IE_DESC = False
 | 
				
			||||||
 | 
					        _VALID_URL = r'(?P<bom>\ufeff)(?P<id>.*)$'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        _TESTS = [{
 | 
				
			||||||
 | 
					            'url': '\ufeffhttp://www.youtube.com/watch?v=BaW_jenozKc',
 | 
				
			||||||
 | 
					            'only_matching': True,
 | 
				
			||||||
 | 
					        }]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def _real_extract(self, url):
 | 
				
			||||||
 | 
					            real_url = self._match_id(url)
 | 
				
			||||||
 | 
					            self.report_warning(
 | 
				
			||||||
 | 
					                'Your URL starts with a Byte Order Mark (BOM). '
 | 
				
			||||||
 | 
					                'Removing the BOM and looking for "%s" ...' % real_url)
 | 
				
			||||||
 | 
					            return self.url_result(real_url)
 | 
				
			||||||
@@ -9,7 +9,7 @@ import xml.etree.ElementTree
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from hashlib import sha1
 | 
					from hashlib import sha1
 | 
				
			||||||
from math import pow, sqrt, floor
 | 
					from math import pow, sqrt, floor
 | 
				
			||||||
from .subtitles import SubtitlesInfoExtractor
 | 
					from .common import InfoExtractor
 | 
				
			||||||
from ..compat import (
 | 
					from ..compat import (
 | 
				
			||||||
    compat_urllib_parse,
 | 
					    compat_urllib_parse,
 | 
				
			||||||
    compat_urllib_request,
 | 
					    compat_urllib_request,
 | 
				
			||||||
@@ -25,14 +25,12 @@ from ..aes import (
 | 
				
			|||||||
    aes_cbc_decrypt,
 | 
					    aes_cbc_decrypt,
 | 
				
			||||||
    inc,
 | 
					    inc,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from .common import InfoExtractor
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CrunchyrollIE(SubtitlesInfoExtractor):
 | 
					class CrunchyrollIE(InfoExtractor):
 | 
				
			||||||
    _VALID_URL = r'https?://(?:(?P<prefix>www|m)\.)?(?P<url>crunchyroll\.com/(?:[^/]*/[^/?&]*?|media/\?id=)(?P<video_id>[0-9]+))(?:[/?&]|$)'
 | 
					    _VALID_URL = r'https?://(?:(?P<prefix>www|m)\.)?(?P<url>crunchyroll\.(?:com|fr)/(?:[^/]*/[^/?&]*?|media/\?id=)(?P<video_id>[0-9]+))(?:[/?&]|$)'
 | 
				
			||||||
    _TEST = {
 | 
					    _TESTS = [{
 | 
				
			||||||
        'url': 'http://www.crunchyroll.com/wanna-be-the-strongest-in-the-world/episode-1-an-idol-wrestler-is-born-645513',
 | 
					        'url': 'http://www.crunchyroll.com/wanna-be-the-strongest-in-the-world/episode-1-an-idol-wrestler-is-born-645513',
 | 
				
			||||||
        #'md5': 'b1639fd6ddfaa43788c85f6d1dddd412',
 | 
					 | 
				
			||||||
        'info_dict': {
 | 
					        'info_dict': {
 | 
				
			||||||
            'id': '645513',
 | 
					            'id': '645513',
 | 
				
			||||||
            'ext': 'flv',
 | 
					            'ext': 'flv',
 | 
				
			||||||
@@ -47,7 +45,10 @@ class CrunchyrollIE(SubtitlesInfoExtractor):
 | 
				
			|||||||
            # rtmp
 | 
					            # rtmp
 | 
				
			||||||
            'skip_download': True,
 | 
					            'skip_download': True,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    }
 | 
					    }, {
 | 
				
			||||||
 | 
					        'url': 'http://www.crunchyroll.fr/girl-friend-beta/episode-11-goodbye-la-mode-661697',
 | 
				
			||||||
 | 
					        'only_matching': True,
 | 
				
			||||||
 | 
					    }]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    _FORMAT_IDS = {
 | 
					    _FORMAT_IDS = {
 | 
				
			||||||
        '360': ('60', '106'),
 | 
					        '360': ('60', '106'),
 | 
				
			||||||
@@ -185,6 +186,38 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return output
 | 
					        return output
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _get_subtitles(self, video_id, webpage):
 | 
				
			||||||
 | 
					        subtitles = {}
 | 
				
			||||||
 | 
					        for sub_id, sub_name in re.findall(r'\?ssid=([0-9]+)" title="([^"]+)', webpage):
 | 
				
			||||||
 | 
					            sub_page = self._download_webpage(
 | 
				
			||||||
 | 
					                'http://www.crunchyroll.com/xml/?req=RpcApiSubtitle_GetXml&subtitle_script_id=' + sub_id,
 | 
				
			||||||
 | 
					                video_id, note='Downloading subtitles for ' + sub_name)
 | 
				
			||||||
 | 
					            id = self._search_regex(r'id=\'([0-9]+)', sub_page, 'subtitle_id', fatal=False)
 | 
				
			||||||
 | 
					            iv = self._search_regex(r'<iv>([^<]+)', sub_page, 'subtitle_iv', fatal=False)
 | 
				
			||||||
 | 
					            data = self._search_regex(r'<data>([^<]+)', sub_page, 'subtitle_data', fatal=False)
 | 
				
			||||||
 | 
					            if not id or not iv or not data:
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            id = int(id)
 | 
				
			||||||
 | 
					            iv = base64.b64decode(iv)
 | 
				
			||||||
 | 
					            data = base64.b64decode(data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            subtitle = self._decrypt_subtitles(data, iv, id).decode('utf-8')
 | 
				
			||||||
 | 
					            lang_code = self._search_regex(r'lang_code=["\']([^"\']+)', subtitle, 'subtitle_lang_code', fatal=False)
 | 
				
			||||||
 | 
					            if not lang_code:
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            sub_root = xml.etree.ElementTree.fromstring(subtitle)
 | 
				
			||||||
 | 
					            subtitles[lang_code] = [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    'ext': 'srt',
 | 
				
			||||||
 | 
					                    'data': self._convert_subtitles_to_srt(sub_root),
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    'ext': 'ass',
 | 
				
			||||||
 | 
					                    'data': self._convert_subtitles_to_ass(sub_root),
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        return subtitles
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _real_extract(self, url):
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
        mobj = re.match(self._VALID_URL, url)
 | 
					        mobj = re.match(self._VALID_URL, url)
 | 
				
			||||||
        video_id = mobj.group('video_id')
 | 
					        video_id = mobj.group('video_id')
 | 
				
			||||||
@@ -226,7 +259,7 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
 | 
				
			|||||||
        video_thumbnail = self._search_regex(r'<episode_image_url>([^<]+)', playerdata, 'thumbnail', fatal=False)
 | 
					        video_thumbnail = self._search_regex(r'<episode_image_url>([^<]+)', playerdata, 'thumbnail', fatal=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        formats = []
 | 
					        formats = []
 | 
				
			||||||
        for fmt in re.findall(r'\?p([0-9]{3,4})=1', webpage):
 | 
					        for fmt in re.findall(r'showmedia\.([0-9]{3,4})p', webpage):
 | 
				
			||||||
            stream_quality, stream_format = self._FORMAT_IDS[fmt]
 | 
					            stream_quality, stream_format = self._FORMAT_IDS[fmt]
 | 
				
			||||||
            video_format = fmt + 'p'
 | 
					            video_format = fmt + 'p'
 | 
				
			||||||
            streamdata_req = compat_urllib_request.Request('http://www.crunchyroll.com/xml/')
 | 
					            streamdata_req = compat_urllib_request.Request('http://www.crunchyroll.com/xml/')
 | 
				
			||||||
@@ -247,34 +280,7 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
 | 
				
			|||||||
                'format_id': video_format,
 | 
					                'format_id': video_format,
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        subtitles = {}
 | 
					        subtitles = self.extract_subtitles(video_id, webpage)
 | 
				
			||||||
        sub_format = self._downloader.params.get('subtitlesformat', 'srt')
 | 
					 | 
				
			||||||
        for sub_id, sub_name in re.findall(r'\?ssid=([0-9]+)" title="([^"]+)', webpage):
 | 
					 | 
				
			||||||
            sub_page = self._download_webpage(
 | 
					 | 
				
			||||||
                'http://www.crunchyroll.com/xml/?req=RpcApiSubtitle_GetXml&subtitle_script_id=' + sub_id,
 | 
					 | 
				
			||||||
                video_id, note='Downloading subtitles for ' + sub_name)
 | 
					 | 
				
			||||||
            id = self._search_regex(r'id=\'([0-9]+)', sub_page, 'subtitle_id', fatal=False)
 | 
					 | 
				
			||||||
            iv = self._search_regex(r'<iv>([^<]+)', sub_page, 'subtitle_iv', fatal=False)
 | 
					 | 
				
			||||||
            data = self._search_regex(r'<data>([^<]+)', sub_page, 'subtitle_data', fatal=False)
 | 
					 | 
				
			||||||
            if not id or not iv or not data:
 | 
					 | 
				
			||||||
                continue
 | 
					 | 
				
			||||||
            id = int(id)
 | 
					 | 
				
			||||||
            iv = base64.b64decode(iv)
 | 
					 | 
				
			||||||
            data = base64.b64decode(data)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            subtitle = self._decrypt_subtitles(data, iv, id).decode('utf-8')
 | 
					 | 
				
			||||||
            lang_code = self._search_regex(r'lang_code=["\']([^"\']+)', subtitle, 'subtitle_lang_code', fatal=False)
 | 
					 | 
				
			||||||
            if not lang_code:
 | 
					 | 
				
			||||||
                continue
 | 
					 | 
				
			||||||
            sub_root = xml.etree.ElementTree.fromstring(subtitle)
 | 
					 | 
				
			||||||
            if sub_format == 'ass':
 | 
					 | 
				
			||||||
                subtitles[lang_code] = self._convert_subtitles_to_ass(sub_root)
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                subtitles[lang_code] = self._convert_subtitles_to_srt(sub_root)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if self._downloader.params.get('listsubtitles', False):
 | 
					 | 
				
			||||||
            self._list_available_subtitles(video_id, subtitles)
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            'id': video_id,
 | 
					            'id': video_id,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,7 +27,6 @@ class CSpanIE(InfoExtractor):
 | 
				
			|||||||
        'url': 'http://www.c-span.org/video/?c4486943/cspan-international-health-care-models',
 | 
					        'url': 'http://www.c-span.org/video/?c4486943/cspan-international-health-care-models',
 | 
				
			||||||
        # For whatever reason, the served video alternates between
 | 
					        # For whatever reason, the served video alternates between
 | 
				
			||||||
        # two different ones
 | 
					        # two different ones
 | 
				
			||||||
        #'md5': 'dbb0f047376d457f2ab8b3929cbb2d0c',
 | 
					 | 
				
			||||||
        'info_dict': {
 | 
					        'info_dict': {
 | 
				
			||||||
            'id': '340723',
 | 
					            'id': '340723',
 | 
				
			||||||
            'ext': 'mp4',
 | 
					            'ext': 'mp4',
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										93
									
								
								youtube_dl/extractor/ctsnews.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								youtube_dl/extractor/ctsnews.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,93 @@
 | 
				
			|||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .common import InfoExtractor
 | 
				
			||||||
 | 
					from ..utils import parse_iso8601, ExtractorError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CtsNewsIE(InfoExtractor):
 | 
				
			||||||
 | 
					    # https connection failed (Connection reset)
 | 
				
			||||||
 | 
					    _VALID_URL = r'http://news\.cts\.com\.tw/[a-z]+/[a-z]+/\d+/(?P<id>\d+)\.html'
 | 
				
			||||||
 | 
					    _TESTS = [{
 | 
				
			||||||
 | 
					        'url': 'http://news.cts.com.tw/cts/international/201501/201501291578109.html',
 | 
				
			||||||
 | 
					        'md5': 'a9875cb790252b08431186d741beaabe',
 | 
				
			||||||
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'id': '201501291578109',
 | 
				
			||||||
 | 
					            'ext': 'mp4',
 | 
				
			||||||
 | 
					            'title': '以色列.真主黨交火 3人死亡',
 | 
				
			||||||
 | 
					            'description': 'md5:95e9b295c898b7ff294f09d450178d7d',
 | 
				
			||||||
 | 
					            'timestamp': 1422528540,
 | 
				
			||||||
 | 
					            'upload_date': '20150129',
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }, {
 | 
				
			||||||
 | 
					        # News count not appear on page but still available in database
 | 
				
			||||||
 | 
					        'url': 'http://news.cts.com.tw/cts/international/201309/201309031304098.html',
 | 
				
			||||||
 | 
					        'md5': '3aee7e0df7cdff94e43581f54c22619e',
 | 
				
			||||||
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'id': '201309031304098',
 | 
				
			||||||
 | 
					            'ext': 'mp4',
 | 
				
			||||||
 | 
					            'title': '韓國31歲童顏男 貌如十多歲小孩',
 | 
				
			||||||
 | 
					            'description': 'md5:f183feeba3752b683827aab71adad584',
 | 
				
			||||||
 | 
					            'thumbnail': 're:^https?://.*\.jpg$',
 | 
				
			||||||
 | 
					            'timestamp': 1378205880,
 | 
				
			||||||
 | 
					            'upload_date': '20130903',
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }, {
 | 
				
			||||||
 | 
					        # With Youtube embedded video
 | 
				
			||||||
 | 
					        'url': 'http://news.cts.com.tw/cts/money/201501/201501291578003.html',
 | 
				
			||||||
 | 
					        'md5': '1d842c771dc94c8c3bca5af2cc1db9c5',
 | 
				
			||||||
 | 
					        'add_ie': ['Youtube'],
 | 
				
			||||||
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'id': 'OVbfO7d0_hQ',
 | 
				
			||||||
 | 
					            'ext': 'mp4',
 | 
				
			||||||
 | 
					            'title': 'iPhone6熱銷 蘋果財報亮眼',
 | 
				
			||||||
 | 
					            'description': 'md5:f395d4f485487bb0f992ed2c4b07aa7d',
 | 
				
			||||||
 | 
					            'thumbnail': 're:^https?://.*\.jpg$',
 | 
				
			||||||
 | 
					            'upload_date': '20150128',
 | 
				
			||||||
 | 
					            'uploader_id': 'TBSCTS',
 | 
				
			||||||
 | 
					            'uploader': '中華電視公司',
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
 | 
					        news_id = self._match_id(url)
 | 
				
			||||||
 | 
					        page = self._download_webpage(url, news_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self._search_regex(r'(CTSPlayer2)', page, 'CTSPlayer2 identifier', default=None):
 | 
				
			||||||
 | 
					            feed_url = self._html_search_regex(
 | 
				
			||||||
 | 
					                r'(http://news\.cts\.com\.tw/action/mp4feed\.php\?news_id=\d+)',
 | 
				
			||||||
 | 
					                page, 'feed url')
 | 
				
			||||||
 | 
					            video_url = self._download_webpage(
 | 
				
			||||||
 | 
					                feed_url, news_id, note='Fetching feed')
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.to_screen('Not CTSPlayer video, trying Youtube...')
 | 
				
			||||||
 | 
					            youtube_url = self._search_regex(
 | 
				
			||||||
 | 
					                r'src="(//www\.youtube\.com/embed/[^"]+)"', page, 'youtube url',
 | 
				
			||||||
 | 
					                default=None)
 | 
				
			||||||
 | 
					            if not youtube_url:
 | 
				
			||||||
 | 
					                raise ExtractorError('The news includes no videos!', expected=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return {
 | 
				
			||||||
 | 
					                '_type': 'url',
 | 
				
			||||||
 | 
					                'url': youtube_url,
 | 
				
			||||||
 | 
					                'ie_key': 'Youtube',
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        description = self._html_search_meta('description', page)
 | 
				
			||||||
 | 
					        title = self._html_search_meta('title', page)
 | 
				
			||||||
 | 
					        thumbnail = self._html_search_meta('image', page)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        datetime_str = self._html_search_regex(
 | 
				
			||||||
 | 
					            r'(\d{4}/\d{2}/\d{2} \d{2}:\d{2})', page, 'date and time')
 | 
				
			||||||
 | 
					        # Transform into ISO 8601 format with timezone info
 | 
				
			||||||
 | 
					        datetime_str = datetime_str.replace('/', '-') + ':00+0800'
 | 
				
			||||||
 | 
					        timestamp = parse_iso8601(datetime_str, delimiter=' ')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            'id': news_id,
 | 
				
			||||||
 | 
					            'url': video_url,
 | 
				
			||||||
 | 
					            'title': title,
 | 
				
			||||||
 | 
					            'description': description,
 | 
				
			||||||
 | 
					            'thumbnail': thumbnail,
 | 
				
			||||||
 | 
					            'timestamp': timestamp,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
@@ -6,7 +6,6 @@ import json
 | 
				
			|||||||
import itertools
 | 
					import itertools
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .common import InfoExtractor
 | 
					from .common import InfoExtractor
 | 
				
			||||||
from .subtitles import SubtitlesInfoExtractor
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
from ..compat import (
 | 
					from ..compat import (
 | 
				
			||||||
    compat_str,
 | 
					    compat_str,
 | 
				
			||||||
@@ -31,7 +30,7 @@ class DailymotionBaseInfoExtractor(InfoExtractor):
 | 
				
			|||||||
        return request
 | 
					        return request
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DailymotionIE(DailymotionBaseInfoExtractor, SubtitlesInfoExtractor):
 | 
					class DailymotionIE(DailymotionBaseInfoExtractor):
 | 
				
			||||||
    """Information Extractor for Dailymotion"""
 | 
					    """Information Extractor for Dailymotion"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    _VALID_URL = r'(?i)(?:https?://)?(?:(www|touch)\.)?dailymotion\.[a-z]{2,3}/(?:(embed|#)/)?video/(?P<id>[^/?_]+)'
 | 
					    _VALID_URL = r'(?i)(?:https?://)?(?:(www|touch)\.)?dailymotion\.[a-z]{2,3}/(?:(embed|#)/)?video/(?P<id>[^/?_]+)'
 | 
				
			||||||
@@ -143,9 +142,6 @@ class DailymotionIE(DailymotionBaseInfoExtractor, SubtitlesInfoExtractor):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        # subtitles
 | 
					        # subtitles
 | 
				
			||||||
        video_subtitles = self.extract_subtitles(video_id, webpage)
 | 
					        video_subtitles = self.extract_subtitles(video_id, webpage)
 | 
				
			||||||
        if self._downloader.params.get('listsubtitles', False):
 | 
					 | 
				
			||||||
            self._list_available_subtitles(video_id, webpage)
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        view_count = str_to_int(self._search_regex(
 | 
					        view_count = str_to_int(self._search_regex(
 | 
				
			||||||
            r'video_views_count[^>]+>\s+([\d\.,]+)',
 | 
					            r'video_views_count[^>]+>\s+([\d\.,]+)',
 | 
				
			||||||
@@ -169,7 +165,7 @@ class DailymotionIE(DailymotionBaseInfoExtractor, SubtitlesInfoExtractor):
 | 
				
			|||||||
            'view_count': view_count,
 | 
					            'view_count': view_count,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _get_available_subtitles(self, video_id, webpage):
 | 
					    def _get_subtitles(self, video_id, webpage):
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            sub_list = self._download_webpage(
 | 
					            sub_list = self._download_webpage(
 | 
				
			||||||
                'https://api.dailymotion.com/video/%s/subtitles?fields=id,language,url' % video_id,
 | 
					                'https://api.dailymotion.com/video/%s/subtitles?fields=id,language,url' % video_id,
 | 
				
			||||||
@@ -179,7 +175,7 @@ class DailymotionIE(DailymotionBaseInfoExtractor, SubtitlesInfoExtractor):
 | 
				
			|||||||
            return {}
 | 
					            return {}
 | 
				
			||||||
        info = json.loads(sub_list)
 | 
					        info = json.loads(sub_list)
 | 
				
			||||||
        if (info['total'] > 0):
 | 
					        if (info['total'] > 0):
 | 
				
			||||||
            sub_lang_list = dict((l['language'], l['url']) for l in info['list'])
 | 
					            sub_lang_list = dict((l['language'], [{'url': l['url'], 'ext': 'srt'}]) for l in info['list'])
 | 
				
			||||||
            return sub_lang_list
 | 
					            return sub_lang_list
 | 
				
			||||||
        self._downloader.report_warning('video doesn\'t have subtitles')
 | 
					        self._downloader.report_warning('video doesn\'t have subtitles')
 | 
				
			||||||
        return {}
 | 
					        return {}
 | 
				
			||||||
@@ -194,6 +190,7 @@ class DailymotionPlaylistIE(DailymotionBaseInfoExtractor):
 | 
				
			|||||||
        'url': 'http://www.dailymotion.com/playlist/xv4bw_nqtv_sport/1#video=xl8v3q',
 | 
					        'url': 'http://www.dailymotion.com/playlist/xv4bw_nqtv_sport/1#video=xl8v3q',
 | 
				
			||||||
        'info_dict': {
 | 
					        'info_dict': {
 | 
				
			||||||
            'title': 'SPORT',
 | 
					            'title': 'SPORT',
 | 
				
			||||||
 | 
					            'id': 'xv4bw_nqtv_sport',
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        'playlist_mincount': 20,
 | 
					        'playlist_mincount': 20,
 | 
				
			||||||
    }]
 | 
					    }]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -38,7 +38,7 @@ class DaumIE(InfoExtractor):
 | 
				
			|||||||
        canonical_url = 'http://tvpot.daum.net/v/%s' % video_id
 | 
					        canonical_url = 'http://tvpot.daum.net/v/%s' % video_id
 | 
				
			||||||
        webpage = self._download_webpage(canonical_url, video_id)
 | 
					        webpage = self._download_webpage(canonical_url, video_id)
 | 
				
			||||||
        full_id = self._search_regex(
 | 
					        full_id = self._search_regex(
 | 
				
			||||||
            r'<iframe src="http://videofarm.daum.net/controller/video/viewer/Video.html\?.*?vid=(.+?)[&"]',
 | 
					            r'src=["\']http://videofarm\.daum\.net/controller/video/viewer/Video\.html\?.*?vid=(.+?)[&"\']',
 | 
				
			||||||
            webpage, 'full id')
 | 
					            webpage, 'full id')
 | 
				
			||||||
        query = compat_urllib_parse.urlencode({'vid': full_id})
 | 
					        query = compat_urllib_parse.urlencode({'vid': full_id})
 | 
				
			||||||
        info = self._download_xml(
 | 
					        info = self._download_xml(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,6 +4,7 @@ from __future__ import unicode_literals
 | 
				
			|||||||
import re
 | 
					import re
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .common import InfoExtractor
 | 
					from .common import InfoExtractor
 | 
				
			||||||
 | 
					from ..compat import compat_str
 | 
				
			||||||
from ..utils import (
 | 
					from ..utils import (
 | 
				
			||||||
    float_or_none,
 | 
					    float_or_none,
 | 
				
			||||||
    int_or_none,
 | 
					    int_or_none,
 | 
				
			||||||
@@ -61,7 +62,7 @@ class DBTVIE(InfoExtractor):
 | 
				
			|||||||
        self._sort_formats(formats)
 | 
					        self._sort_formats(formats)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            'id': video['id'],
 | 
					            'id': compat_str(video['id']),
 | 
				
			||||||
            'display_id': display_id,
 | 
					            'display_id': display_id,
 | 
				
			||||||
            'title': video['title'],
 | 
					            'title': video['title'],
 | 
				
			||||||
            'description': clean_html(video['desc']),
 | 
					            'description': clean_html(video['desc']),
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										61
									
								
								youtube_dl/extractor/dctp.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								youtube_dl/extractor/dctp.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,61 @@
 | 
				
			|||||||
 | 
					# encoding: utf-8
 | 
				
			||||||
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .common import InfoExtractor
 | 
				
			||||||
 | 
					from ..compat import compat_str
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DctpTvIE(InfoExtractor):
 | 
				
			||||||
 | 
					    _VALID_URL = r'http://www.dctp.tv/(#/)?filme/(?P<id>.+?)/$'
 | 
				
			||||||
 | 
					    _TEST = {
 | 
				
			||||||
 | 
					        'url': 'http://www.dctp.tv/filme/videoinstallation-fuer-eine-kaufhausfassade/',
 | 
				
			||||||
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'id': '1324',
 | 
				
			||||||
 | 
					            'display_id': 'videoinstallation-fuer-eine-kaufhausfassade',
 | 
				
			||||||
 | 
					            'ext': 'flv',
 | 
				
			||||||
 | 
					            'title': 'Videoinstallation für eine Kaufhausfassade'
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        'params': {
 | 
				
			||||||
 | 
					            # rtmp download
 | 
				
			||||||
 | 
					            'skip_download': True,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
 | 
					        video_id = self._match_id(url)
 | 
				
			||||||
 | 
					        base_url = 'http://dctp-ivms2-restapi.s3.amazonaws.com/'
 | 
				
			||||||
 | 
					        version_json = self._download_json(
 | 
				
			||||||
 | 
					            base_url + 'version.json',
 | 
				
			||||||
 | 
					            video_id, note='Determining file version')
 | 
				
			||||||
 | 
					        version = version_json['version_name']
 | 
				
			||||||
 | 
					        info_json = self._download_json(
 | 
				
			||||||
 | 
					            '{0}{1}/restapi/slugs/{2}.json'.format(base_url, version, video_id),
 | 
				
			||||||
 | 
					            video_id, note='Fetching object ID')
 | 
				
			||||||
 | 
					        object_id = compat_str(info_json['object_id'])
 | 
				
			||||||
 | 
					        meta_json = self._download_json(
 | 
				
			||||||
 | 
					            '{0}{1}/restapi/media/{2}.json'.format(base_url, version, object_id),
 | 
				
			||||||
 | 
					            video_id, note='Downloading metadata')
 | 
				
			||||||
 | 
					        uuid = meta_json['uuid']
 | 
				
			||||||
 | 
					        title = meta_json['title']
 | 
				
			||||||
 | 
					        wide = meta_json['is_wide']
 | 
				
			||||||
 | 
					        if wide:
 | 
				
			||||||
 | 
					            ratio = '16x9'
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            ratio = '4x3'
 | 
				
			||||||
 | 
					        play_path = 'mp4:{0}_dctp_0500_{1}.m4v'.format(uuid, ratio)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        servers_json = self._download_json(
 | 
				
			||||||
 | 
					            'http://www.dctp.tv/streaming_servers/',
 | 
				
			||||||
 | 
					            video_id, note='Downloading server list')
 | 
				
			||||||
 | 
					        url = servers_json[0]['endpoint']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            'id': object_id,
 | 
				
			||||||
 | 
					            'title': title,
 | 
				
			||||||
 | 
					            'format': 'rtmp',
 | 
				
			||||||
 | 
					            'url': url,
 | 
				
			||||||
 | 
					            'play_path': play_path,
 | 
				
			||||||
 | 
					            'rtmp_real_time': True,
 | 
				
			||||||
 | 
					            'ext': 'flv',
 | 
				
			||||||
 | 
					            'display_id': video_id
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
@@ -1,40 +1,39 @@
 | 
				
			|||||||
from __future__ import unicode_literals
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import re
 | 
					 | 
				
			||||||
import json
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from .common import InfoExtractor
 | 
					from .common import InfoExtractor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DefenseGouvFrIE(InfoExtractor):
 | 
					class DefenseGouvFrIE(InfoExtractor):
 | 
				
			||||||
    IE_NAME = 'defense.gouv.fr'
 | 
					    IE_NAME = 'defense.gouv.fr'
 | 
				
			||||||
    _VALID_URL = (r'http://.*?\.defense\.gouv\.fr/layout/set/'
 | 
					    _VALID_URL = r'http://.*?\.defense\.gouv\.fr/layout/set/ligthboxvideo/base-de-medias/webtv/(?P<id>[^/?#]*)'
 | 
				
			||||||
                  r'ligthboxvideo/base-de-medias/webtv/(.*)')
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    _TEST = {
 | 
					    _TEST = {
 | 
				
			||||||
        'url': 'http://www.defense.gouv.fr/layout/set/ligthboxvideo/base-de-medias/webtv/attaque-chimique-syrienne-du-21-aout-2013-1',
 | 
					        'url': 'http://www.defense.gouv.fr/layout/set/ligthboxvideo/base-de-medias/webtv/attaque-chimique-syrienne-du-21-aout-2013-1',
 | 
				
			||||||
        'file': '11213.mp4',
 | 
					 | 
				
			||||||
        'md5': '75bba6124da7e63d2d60b5244ec9430c',
 | 
					        'md5': '75bba6124da7e63d2d60b5244ec9430c',
 | 
				
			||||||
        "info_dict": {
 | 
					        'info_dict': {
 | 
				
			||||||
            "title": "attaque-chimique-syrienne-du-21-aout-2013-1"
 | 
					            'id': '11213',
 | 
				
			||||||
 | 
					            'ext': 'mp4',
 | 
				
			||||||
 | 
					            'title': 'attaque-chimique-syrienne-du-21-aout-2013-1'
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _real_extract(self, url):
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
        title = re.match(self._VALID_URL, url).group(1)
 | 
					        title = self._match_id(url)
 | 
				
			||||||
        webpage = self._download_webpage(url, title)
 | 
					        webpage = self._download_webpage(url, title)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        video_id = self._search_regex(
 | 
					        video_id = self._search_regex(
 | 
				
			||||||
            r"flashvars.pvg_id=\"(\d+)\";",
 | 
					            r"flashvars.pvg_id=\"(\d+)\";",
 | 
				
			||||||
            webpage, 'ID')
 | 
					            webpage, 'ID')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        json_url = ('http://static.videos.gouv.fr/brightcovehub/export/json/'
 | 
					        json_url = (
 | 
				
			||||||
                    + video_id)
 | 
					            'http://static.videos.gouv.fr/brightcovehub/export/json/%s' %
 | 
				
			||||||
        info = self._download_webpage(json_url, title,
 | 
					            video_id)
 | 
				
			||||||
                                      'Downloading JSON config')
 | 
					        info = self._download_json(json_url, title, 'Downloading JSON config')
 | 
				
			||||||
        video_url = json.loads(info)['renditions'][0]['url']
 | 
					        video_url = info['renditions'][0]['url']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return {'id': video_id,
 | 
					        return {
 | 
				
			||||||
                'ext': 'mp4',
 | 
					            'id': video_id,
 | 
				
			||||||
                'url': video_url,
 | 
					            'ext': 'mp4',
 | 
				
			||||||
                'title': title,
 | 
					            'url': video_url,
 | 
				
			||||||
                }
 | 
					            'title': title,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,47 +1,45 @@
 | 
				
			|||||||
from __future__ import unicode_literals
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import re
 | 
					 | 
				
			||||||
import json
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from .common import InfoExtractor
 | 
					from .common import InfoExtractor
 | 
				
			||||||
 | 
					from ..utils import (
 | 
				
			||||||
 | 
					    parse_iso8601,
 | 
				
			||||||
 | 
					    int_or_none,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DiscoveryIE(InfoExtractor):
 | 
					class DiscoveryIE(InfoExtractor):
 | 
				
			||||||
    _VALID_URL = r'http://www\.discovery\.com\/[a-zA-Z0-9\-]*/[a-zA-Z0-9\-]*/videos/(?P<id>[a-zA-Z0-9\-]*)(.htm)?'
 | 
					    _VALID_URL = r'http://www\.discovery\.com\/[a-zA-Z0-9\-]*/[a-zA-Z0-9\-]*/videos/(?P<id>[a-zA-Z0-9_\-]*)(?:\.htm)?'
 | 
				
			||||||
    _TEST = {
 | 
					    _TEST = {
 | 
				
			||||||
        'url': 'http://www.discovery.com/tv-shows/mythbusters/videos/mission-impossible-outtakes.htm',
 | 
					        'url': 'http://www.discovery.com/tv-shows/mythbusters/videos/mission-impossible-outtakes.htm',
 | 
				
			||||||
        'md5': 'e12614f9ee303a6ccef415cb0793eba2',
 | 
					        'md5': '3c69d77d9b0d82bfd5e5932a60f26504',
 | 
				
			||||||
        'info_dict': {
 | 
					        'info_dict': {
 | 
				
			||||||
            'id': '614784',
 | 
					            'id': 'mission-impossible-outtakes',
 | 
				
			||||||
            'ext': 'mp4',
 | 
					            'ext': 'flv',
 | 
				
			||||||
            'title': 'MythBusters: Mission Impossible Outtakes',
 | 
					            'title': 'Mission Impossible Outtakes',
 | 
				
			||||||
            'description': ('Watch Jamie Hyneman and Adam Savage practice being'
 | 
					            'description': ('Watch Jamie Hyneman and Adam Savage practice being'
 | 
				
			||||||
                            ' each other -- to the point of confusing Jamie\'s dog -- and '
 | 
					                            ' each other -- to the point of confusing Jamie\'s dog -- and '
 | 
				
			||||||
                            'don\'t miss Adam moon-walking as Jamie ... behind Jamie\'s'
 | 
					                            'don\'t miss Adam moon-walking as Jamie ... behind Jamie\'s'
 | 
				
			||||||
                            ' back.'),
 | 
					                            ' back.'),
 | 
				
			||||||
            'duration': 156,
 | 
					            'duration': 156,
 | 
				
			||||||
 | 
					            'timestamp': 1303099200,
 | 
				
			||||||
 | 
					            'upload_date': '20110418',
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _real_extract(self, url):
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
        mobj = re.match(self._VALID_URL, url)
 | 
					        video_id = self._match_id(url)
 | 
				
			||||||
        video_id = mobj.group('id')
 | 
					 | 
				
			||||||
        webpage = self._download_webpage(url, video_id)
 | 
					        webpage = self._download_webpage(url, video_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        video_list_json = self._search_regex(r'var videoListJSON = ({.*?});',
 | 
					        info = self._parse_json(self._search_regex(
 | 
				
			||||||
                                             webpage, 'video list', flags=re.DOTALL)
 | 
					            r'(?s)<script type="application/ld\+json">(.*?)</script>',
 | 
				
			||||||
        video_list = json.loads(video_list_json)
 | 
					            webpage, 'video info'), video_id)
 | 
				
			||||||
        info = video_list['clips'][0]
 | 
					 | 
				
			||||||
        formats = []
 | 
					 | 
				
			||||||
        for f in info['mp4']:
 | 
					 | 
				
			||||||
            formats.append(
 | 
					 | 
				
			||||||
                {'url': f['src'], 'ext': 'mp4', 'tbr': int(f['bitrate'][:-1])})
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            'id': info['contentId'],
 | 
					            'id': video_id,
 | 
				
			||||||
            'title': video_list['name'],
 | 
					            'title': info['name'],
 | 
				
			||||||
            'formats': formats,
 | 
					            'url': info['contentURL'],
 | 
				
			||||||
            'description': info['videoCaption'],
 | 
					            'description': info.get('description'),
 | 
				
			||||||
            'thumbnail': info.get('videoStillURL') or info.get('thumbnailURL'),
 | 
					            'thumbnail': info.get('thumbnailUrl'),
 | 
				
			||||||
            'duration': info['duration'],
 | 
					            'timestamp': parse_iso8601(info.get('uploadDate')),
 | 
				
			||||||
 | 
					            'duration': int_or_none(info.get('duration')),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,13 +1,14 @@
 | 
				
			|||||||
from __future__ import unicode_literals
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import re
 | 
					 | 
				
			||||||
import time
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from .common import InfoExtractor
 | 
					from .common import InfoExtractor
 | 
				
			||||||
 | 
					from ..utils import (
 | 
				
			||||||
 | 
					    float_or_none,
 | 
				
			||||||
 | 
					    int_or_none,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DotsubIE(InfoExtractor):
 | 
					class DotsubIE(InfoExtractor):
 | 
				
			||||||
    _VALID_URL = r'http://(?:www\.)?dotsub\.com/view/(?P<id>[^/]+)'
 | 
					    _VALID_URL = r'https?://(?:www\.)?dotsub\.com/view/(?P<id>[^/]+)'
 | 
				
			||||||
    _TEST = {
 | 
					    _TEST = {
 | 
				
			||||||
        'url': 'http://dotsub.com/view/aed3b8b2-1889-4df5-ae63-ad85f5572f27',
 | 
					        'url': 'http://dotsub.com/view/aed3b8b2-1889-4df5-ae63-ad85f5572f27',
 | 
				
			||||||
        'md5': '0914d4d69605090f623b7ac329fea66e',
 | 
					        'md5': '0914d4d69605090f623b7ac329fea66e',
 | 
				
			||||||
@@ -15,28 +16,37 @@ class DotsubIE(InfoExtractor):
 | 
				
			|||||||
            'id': 'aed3b8b2-1889-4df5-ae63-ad85f5572f27',
 | 
					            'id': 'aed3b8b2-1889-4df5-ae63-ad85f5572f27',
 | 
				
			||||||
            'ext': 'flv',
 | 
					            'ext': 'flv',
 | 
				
			||||||
            'title': 'Pyramids of Waste (2010), AKA The Lightbulb Conspiracy - Planned obsolescence documentary',
 | 
					            'title': 'Pyramids of Waste (2010), AKA The Lightbulb Conspiracy - Planned obsolescence documentary',
 | 
				
			||||||
 | 
					            'description': 'md5:699a0f7f50aeec6042cb3b1db2d0d074',
 | 
				
			||||||
 | 
					            'thumbnail': 're:^https?://dotsub.com/media/aed3b8b2-1889-4df5-ae63-ad85f5572f27/p',
 | 
				
			||||||
 | 
					            'duration': 3169,
 | 
				
			||||||
            'uploader': '4v4l0n42',
 | 
					            'uploader': '4v4l0n42',
 | 
				
			||||||
            'description': 'Pyramids of Waste (2010) also known as "The lightbulb conspiracy" is a documentary about how our economic system based on consumerism  and planned obsolescence is breaking our planet down.\r\n\r\nSolutions to this can be found at:\r\nhttp://robotswillstealyourjob.com\r\nhttp://www.federicopistono.org\r\n\r\nhttp://opensourceecology.org\r\nhttp://thezeitgeistmovement.com',
 | 
					            'timestamp': 1292248482.625,
 | 
				
			||||||
            'thumbnail': 'http://dotsub.com/media/aed3b8b2-1889-4df5-ae63-ad85f5572f27/p',
 | 
					 | 
				
			||||||
            'upload_date': '20101213',
 | 
					            'upload_date': '20101213',
 | 
				
			||||||
 | 
					            'view_count': int,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _real_extract(self, url):
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
        mobj = re.match(self._VALID_URL, url)
 | 
					        video_id = self._match_id(url)
 | 
				
			||||||
        video_id = mobj.group('id')
 | 
					
 | 
				
			||||||
        info_url = "https://dotsub.com/api/media/%s/metadata" % video_id
 | 
					        info = self._download_json(
 | 
				
			||||||
        info = self._download_json(info_url, video_id)
 | 
					            'https://dotsub.com/api/media/%s/metadata' % video_id, video_id)
 | 
				
			||||||
        date = time.gmtime(info['dateCreated'] / 1000)  # The timestamp is in miliseconds
 | 
					        video_url = info.get('mediaURI')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not video_url:
 | 
				
			||||||
 | 
					            webpage = self._download_webpage(url, video_id)
 | 
				
			||||||
 | 
					            video_url = self._search_regex(
 | 
				
			||||||
 | 
					                r'"file"\s*:\s*\'([^\']+)', webpage, 'video url')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            'id': video_id,
 | 
					            'id': video_id,
 | 
				
			||||||
            'url': info['mediaURI'],
 | 
					            'url': video_url,
 | 
				
			||||||
            'ext': 'flv',
 | 
					            'ext': 'flv',
 | 
				
			||||||
            'title': info['title'],
 | 
					            'title': info['title'],
 | 
				
			||||||
            'thumbnail': info['screenshotURI'],
 | 
					            'description': info.get('description'),
 | 
				
			||||||
            'description': info['description'],
 | 
					            'thumbnail': info.get('screenshotURI'),
 | 
				
			||||||
            'uploader': info['user'],
 | 
					            'duration': int_or_none(info.get('duration'), 1000),
 | 
				
			||||||
            'view_count': info['numberOfViews'],
 | 
					            'uploader': info.get('user'),
 | 
				
			||||||
            'upload_date': '%04i%02i%02i' % (date.tm_year, date.tm_mon, date.tm_mday),
 | 
					            'timestamp': float_or_none(info.get('dateCreated'), 1000),
 | 
				
			||||||
 | 
					            'view_count': int_or_none(info.get('numberOfViews')),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										131
									
								
								youtube_dl/extractor/drbonanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								youtube_dl/extractor/drbonanza.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,131 @@
 | 
				
			|||||||
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					import re
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .common import InfoExtractor
 | 
				
			||||||
 | 
					from ..utils import (
 | 
				
			||||||
 | 
					    int_or_none,
 | 
				
			||||||
 | 
					    parse_iso8601,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DRBonanzaIE(InfoExtractor):
 | 
				
			||||||
 | 
					    _VALID_URL = r'https?://(?:www\.)?dr\.dk/bonanza/(?:[^/]+/)+(?:[^/])+?(?:assetId=(?P<id>\d+))?(?:[#&]|$)'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    _TESTS = [{
 | 
				
			||||||
 | 
					        'url': 'http://www.dr.dk/bonanza/serie/portraetter/Talkshowet.htm?assetId=65517',
 | 
				
			||||||
 | 
					        'md5': 'fe330252ddea607635cf2eb2c99a0af3',
 | 
				
			||||||
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'id': '65517',
 | 
				
			||||||
 | 
					            'ext': 'mp4',
 | 
				
			||||||
 | 
					            'title': 'Talkshowet - Leonard Cohen',
 | 
				
			||||||
 | 
					            'description': 'md5:8f34194fb30cd8c8a30ad8b27b70c0ca',
 | 
				
			||||||
 | 
					            'thumbnail': 're:^https?://.*\.(?:gif|jpg)$',
 | 
				
			||||||
 | 
					            'timestamp': 1295537932,
 | 
				
			||||||
 | 
					            'upload_date': '20110120',
 | 
				
			||||||
 | 
					            'duration': 3664,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    }, {
 | 
				
			||||||
 | 
					        'url': 'http://www.dr.dk/bonanza/radio/serie/sport/fodbold.htm?assetId=59410',
 | 
				
			||||||
 | 
					        'md5': '6dfe039417e76795fb783c52da3de11d',
 | 
				
			||||||
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'id': '59410',
 | 
				
			||||||
 | 
					            'ext': 'mp3',
 | 
				
			||||||
 | 
					            'title': 'EM fodbold 1992 Danmark - Tyskland finale Transmission',
 | 
				
			||||||
 | 
					            'description': 'md5:501e5a195749480552e214fbbed16c4e',
 | 
				
			||||||
 | 
					            'thumbnail': 're:^https?://.*\.(?:gif|jpg)$',
 | 
				
			||||||
 | 
					            'timestamp': 1223274900,
 | 
				
			||||||
 | 
					            'upload_date': '20081006',
 | 
				
			||||||
 | 
					            'duration': 7369,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    }]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
 | 
					        url_id = self._match_id(url)
 | 
				
			||||||
 | 
					        webpage = self._download_webpage(url, url_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if url_id:
 | 
				
			||||||
 | 
					            info = json.loads(self._html_search_regex(r'({.*?%s.*})' % url_id, webpage, 'json'))
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            # Just fetch the first video on that page
 | 
				
			||||||
 | 
					            info = json.loads(self._html_search_regex(r'bonanzaFunctions.newPlaylist\(({.*})\)', webpage, 'json'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        asset_id = str(info['AssetId'])
 | 
				
			||||||
 | 
					        title = info['Title'].rstrip(' \'\"-,.:;!?')
 | 
				
			||||||
 | 
					        duration = int_or_none(info.get('Duration'), scale=1000)
 | 
				
			||||||
 | 
					        # First published online. "FirstPublished" contains the date for original airing.
 | 
				
			||||||
 | 
					        timestamp = parse_iso8601(
 | 
				
			||||||
 | 
					            re.sub(r'\.\d+$', '', info['Created']))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def parse_filename_info(url):
 | 
				
			||||||
 | 
					            match = re.search(r'/\d+_(?P<width>\d+)x(?P<height>\d+)x(?P<bitrate>\d+)K\.(?P<ext>\w+)$', url)
 | 
				
			||||||
 | 
					            if match:
 | 
				
			||||||
 | 
					                return {
 | 
				
			||||||
 | 
					                    'width': int(match.group('width')),
 | 
				
			||||||
 | 
					                    'height': int(match.group('height')),
 | 
				
			||||||
 | 
					                    'vbr': int(match.group('bitrate')),
 | 
				
			||||||
 | 
					                    'ext': match.group('ext')
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            match = re.search(r'/\d+_(?P<bitrate>\d+)K\.(?P<ext>\w+)$', url)
 | 
				
			||||||
 | 
					            if match:
 | 
				
			||||||
 | 
					                return {
 | 
				
			||||||
 | 
					                    'vbr': int(match.group('bitrate')),
 | 
				
			||||||
 | 
					                    'ext': match.group(2)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            return {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        video_types = ['VideoHigh', 'VideoMid', 'VideoLow']
 | 
				
			||||||
 | 
					        preferencemap = {
 | 
				
			||||||
 | 
					            'VideoHigh': -1,
 | 
				
			||||||
 | 
					            'VideoMid': -2,
 | 
				
			||||||
 | 
					            'VideoLow': -3,
 | 
				
			||||||
 | 
					            'Audio': -4,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        formats = []
 | 
				
			||||||
 | 
					        for file in info['Files']:
 | 
				
			||||||
 | 
					            if info['Type'] == "Video":
 | 
				
			||||||
 | 
					                if file['Type'] in video_types:
 | 
				
			||||||
 | 
					                    format = parse_filename_info(file['Location'])
 | 
				
			||||||
 | 
					                    format.update({
 | 
				
			||||||
 | 
					                        'url': file['Location'],
 | 
				
			||||||
 | 
					                        'format_id': file['Type'].replace('Video', ''),
 | 
				
			||||||
 | 
					                        'preference': preferencemap.get(file['Type'], -10),
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                    formats.append(format)
 | 
				
			||||||
 | 
					                elif file['Type'] == "Thumb":
 | 
				
			||||||
 | 
					                    thumbnail = file['Location']
 | 
				
			||||||
 | 
					            elif info['Type'] == "Audio":
 | 
				
			||||||
 | 
					                if file['Type'] == "Audio":
 | 
				
			||||||
 | 
					                    format = parse_filename_info(file['Location'])
 | 
				
			||||||
 | 
					                    format.update({
 | 
				
			||||||
 | 
					                        'url': file['Location'],
 | 
				
			||||||
 | 
					                        'format_id': file['Type'],
 | 
				
			||||||
 | 
					                        'vcodec': 'none',
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                    formats.append(format)
 | 
				
			||||||
 | 
					                elif file['Type'] == "Thumb":
 | 
				
			||||||
 | 
					                    thumbnail = file['Location']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        description = '%s\n%s\n%s\n' % (
 | 
				
			||||||
 | 
					            info['Description'], info['Actors'], info['Colophon'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for f in formats:
 | 
				
			||||||
 | 
					            f['url'] = f['url'].replace('rtmp://vod-bonanza.gss.dr.dk/bonanza/', 'http://vodfiles.dr.dk/')
 | 
				
			||||||
 | 
					            f['url'] = f['url'].replace('mp4:bonanza', 'bonanza')
 | 
				
			||||||
 | 
					        self._sort_formats(formats)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        display_id = re.sub(r'[^\w\d-]', '', re.sub(r' ', '-', title.lower())) + '-' + asset_id
 | 
				
			||||||
 | 
					        display_id = re.sub(r'-+', '-', display_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            'id': asset_id,
 | 
				
			||||||
 | 
					            'display_id': display_id,
 | 
				
			||||||
 | 
					            'title': title,
 | 
				
			||||||
 | 
					            'formats': formats,
 | 
				
			||||||
 | 
					            'description': description,
 | 
				
			||||||
 | 
					            'thumbnail': thumbnail,
 | 
				
			||||||
 | 
					            'timestamp': timestamp,
 | 
				
			||||||
 | 
					            'duration': duration,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
@@ -15,7 +15,7 @@ class DrTuberIE(InfoExtractor):
 | 
				
			|||||||
            'id': '1740434',
 | 
					            'id': '1740434',
 | 
				
			||||||
            'display_id': 'hot-perky-blonde-naked-golf',
 | 
					            'display_id': 'hot-perky-blonde-naked-golf',
 | 
				
			||||||
            'ext': 'mp4',
 | 
					            'ext': 'mp4',
 | 
				
			||||||
            'title': 'Hot Perky Blonde Naked Golf',
 | 
					            'title': 'hot perky blonde naked golf',
 | 
				
			||||||
            'like_count': int,
 | 
					            'like_count': int,
 | 
				
			||||||
            'dislike_count': int,
 | 
					            'dislike_count': int,
 | 
				
			||||||
            'comment_count': int,
 | 
					            'comment_count': int,
 | 
				
			||||||
@@ -36,7 +36,8 @@ class DrTuberIE(InfoExtractor):
 | 
				
			|||||||
            r'<source src="([^"]+)"', webpage, 'video URL')
 | 
					            r'<source src="([^"]+)"', webpage, 'video URL')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        title = self._html_search_regex(
 | 
					        title = self._html_search_regex(
 | 
				
			||||||
            r'<title>([^<]+)\s*-\s*Free', webpage, 'title')
 | 
					            [r'class="hd_title" style="[^"]+">([^<]+)</h1>', r'<title>([^<]+) - \d+'],
 | 
				
			||||||
 | 
					            webpage, 'title')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        thumbnail = self._html_search_regex(
 | 
					        thumbnail = self._html_search_regex(
 | 
				
			||||||
            r'poster="([^"]+)"',
 | 
					            r'poster="([^"]+)"',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,12 +1,11 @@
 | 
				
			|||||||
from __future__ import unicode_literals
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .subtitles import SubtitlesInfoExtractor
 | 
					from .common import InfoExtractor, ExtractorError
 | 
				
			||||||
from .common import ExtractorError
 | 
					 | 
				
			||||||
from ..utils import parse_iso8601
 | 
					from ..utils import parse_iso8601
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DRTVIE(SubtitlesInfoExtractor):
 | 
					class DRTVIE(InfoExtractor):
 | 
				
			||||||
    _VALID_URL = r'http://(?:www\.)?dr\.dk/tv/se/(?:[^/]+/)+(?P<id>[\da-z-]+)(?:[/#?]|$)'
 | 
					    _VALID_URL = r'https?://(?:www\.)?dr\.dk/tv/se/(?:[^/]+/)*(?P<id>[\da-z-]+)(?:[/#?]|$)'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    _TEST = {
 | 
					    _TEST = {
 | 
				
			||||||
        'url': 'http://www.dr.dk/tv/se/partiets-mand/partiets-mand-7-8',
 | 
					        'url': 'http://www.dr.dk/tv/se/partiets-mand/partiets-mand-7-8',
 | 
				
			||||||
@@ -25,9 +24,15 @@ class DRTVIE(SubtitlesInfoExtractor):
 | 
				
			|||||||
    def _real_extract(self, url):
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
        video_id = self._match_id(url)
 | 
					        video_id = self._match_id(url)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        programcard = self._download_json(
 | 
					        webpage = self._download_webpage(url, video_id)
 | 
				
			||||||
            'http://www.dr.dk/mu/programcard/expanded/%s' % video_id, video_id, 'Downloading video JSON')
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        video_id = self._search_regex(
 | 
				
			||||||
 | 
					            r'data-(?:material-identifier|episode-slug)="([^"]+)"',
 | 
				
			||||||
 | 
					            webpage, 'video id')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        programcard = self._download_json(
 | 
				
			||||||
 | 
					            'http://www.dr.dk/mu/programcard/expanded/%s' % video_id,
 | 
				
			||||||
 | 
					            video_id, 'Downloading video JSON')
 | 
				
			||||||
        data = programcard['Data'][0]
 | 
					        data = programcard['Data'][0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        title = data['Title']
 | 
					        title = data['Title']
 | 
				
			||||||
@@ -48,14 +53,20 @@ class DRTVIE(SubtitlesInfoExtractor):
 | 
				
			|||||||
            elif asset['Kind'] == 'VideoResource':
 | 
					            elif asset['Kind'] == 'VideoResource':
 | 
				
			||||||
                duration = asset['DurationInMilliseconds'] / 1000.0
 | 
					                duration = asset['DurationInMilliseconds'] / 1000.0
 | 
				
			||||||
                restricted_to_denmark = asset['RestrictedToDenmark']
 | 
					                restricted_to_denmark = asset['RestrictedToDenmark']
 | 
				
			||||||
 | 
					                spoken_subtitles = asset['Target'] == 'SpokenSubtitles'
 | 
				
			||||||
                for link in asset['Links']:
 | 
					                for link in asset['Links']:
 | 
				
			||||||
                    target = link['Target']
 | 
					                    target = link['Target']
 | 
				
			||||||
                    uri = link['Uri']
 | 
					                    uri = link['Uri']
 | 
				
			||||||
 | 
					                    format_id = target
 | 
				
			||||||
 | 
					                    preference = -1 if target == 'HDS' else -2
 | 
				
			||||||
 | 
					                    if spoken_subtitles:
 | 
				
			||||||
 | 
					                        preference -= 2
 | 
				
			||||||
 | 
					                        format_id += '-spoken-subtitles'
 | 
				
			||||||
                    formats.append({
 | 
					                    formats.append({
 | 
				
			||||||
                        'url': uri + '?hdcore=3.3.0&plugin=aasp-3.3.0.99.43' if target == 'HDS' else uri,
 | 
					                        'url': uri + '?hdcore=3.3.0&plugin=aasp-3.3.0.99.43' if target == 'HDS' else uri,
 | 
				
			||||||
                        'format_id': target,
 | 
					                        'format_id': format_id,
 | 
				
			||||||
                        'ext': link['FileFormat'],
 | 
					                        'ext': link['FileFormat'],
 | 
				
			||||||
                        'preference': -1 if target == 'HDS' else -2,
 | 
					                        'preference': preference,
 | 
				
			||||||
                    })
 | 
					                    })
 | 
				
			||||||
                subtitles_list = asset.get('SubtitlesList')
 | 
					                subtitles_list = asset.get('SubtitlesList')
 | 
				
			||||||
                if isinstance(subtitles_list, list):
 | 
					                if isinstance(subtitles_list, list):
 | 
				
			||||||
@@ -64,7 +75,7 @@ class DRTVIE(SubtitlesInfoExtractor):
 | 
				
			|||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    for subs in subtitles_list:
 | 
					                    for subs in subtitles_list:
 | 
				
			||||||
                        lang = subs['Language']
 | 
					                        lang = subs['Language']
 | 
				
			||||||
                        subtitles[LANGS.get(lang, lang)] = subs['Uri']
 | 
					                        subtitles[LANGS.get(lang, lang)] = [{'url': subs['Uri'], 'ext': 'vtt'}]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if not formats and restricted_to_denmark:
 | 
					        if not formats and restricted_to_denmark:
 | 
				
			||||||
            raise ExtractorError(
 | 
					            raise ExtractorError(
 | 
				
			||||||
@@ -72,10 +83,6 @@ class DRTVIE(SubtitlesInfoExtractor):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        self._sort_formats(formats)
 | 
					        self._sort_formats(formats)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self._downloader.params.get('listsubtitles', False):
 | 
					 | 
				
			||||||
            self._list_available_subtitles(video_id, subtitles)
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            'id': video_id,
 | 
					            'id': video_id,
 | 
				
			||||||
            'title': title,
 | 
					            'title': title,
 | 
				
			||||||
@@ -84,5 +91,5 @@ class DRTVIE(SubtitlesInfoExtractor):
 | 
				
			|||||||
            'timestamp': timestamp,
 | 
					            'timestamp': timestamp,
 | 
				
			||||||
            'duration': duration,
 | 
					            'duration': duration,
 | 
				
			||||||
            'formats': formats,
 | 
					            'formats': formats,
 | 
				
			||||||
            'subtitles': self.extract_subtitles(video_id, subtitles),
 | 
					            'subtitles': subtitles,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										125
									
								
								youtube_dl/extractor/dvtv.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								youtube_dl/extractor/dvtv.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,125 @@
 | 
				
			|||||||
 | 
					# coding: utf-8
 | 
				
			||||||
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import re
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .common import InfoExtractor
 | 
				
			||||||
 | 
					from ..utils import (
 | 
				
			||||||
 | 
					    js_to_json,
 | 
				
			||||||
 | 
					    unescapeHTML,
 | 
				
			||||||
 | 
					    ExtractorError,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DVTVIE(InfoExtractor):
 | 
				
			||||||
 | 
					    IE_NAME = 'dvtv'
 | 
				
			||||||
 | 
					    IE_DESC = 'http://video.aktualne.cz/'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    _VALID_URL = r'http://video\.aktualne\.cz/(?:[^/]+/)+r~(?P<id>[0-9a-f]{32})'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    _TESTS = [{
 | 
				
			||||||
 | 
					        'url': 'http://video.aktualne.cz/dvtv/vondra-o-ceskem-stoleti-pri-pohledu-na-havla-mi-bylo-trapne/r~e5efe9ca855511e4833a0025900fea04/',
 | 
				
			||||||
 | 
					        'md5': '67cb83e4a955d36e1b5d31993134a0c2',
 | 
				
			||||||
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'id': 'dc0768de855511e49e4b0025900fea04',
 | 
				
			||||||
 | 
					            'ext': 'mp4',
 | 
				
			||||||
 | 
					            'title': 'Vondra o Českém století: Při pohledu na Havla mi bylo trapně',
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }, {
 | 
				
			||||||
 | 
					        'url': 'http://video.aktualne.cz/dvtv/stropnicky-policie-vrbetice-preventivne-nekontrolovala/r~82ed4322849211e4a10c0025900fea04/',
 | 
				
			||||||
 | 
					        'md5': '6388f1941b48537dbd28791f712af8bf',
 | 
				
			||||||
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'id': '72c02230849211e49f60002590604f2e',
 | 
				
			||||||
 | 
					            'ext': 'mp4',
 | 
				
			||||||
 | 
					            'title': 'Stropnický: Policie Vrbětice preventivně nekontrolovala',
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }, {
 | 
				
			||||||
 | 
					        'url': 'http://video.aktualne.cz/dvtv/dvtv-16-12-2014-utok-talibanu-boj-o-kliniku-uprchlici/r~973eb3bc854e11e498be002590604f2e/',
 | 
				
			||||||
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'title': 'DVTV 16. 12. 2014: útok Talibanu, boj o kliniku, uprchlíci',
 | 
				
			||||||
 | 
					            'id': '973eb3bc854e11e498be002590604f2e',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        'playlist': [{
 | 
				
			||||||
 | 
					            'md5': 'da7ca6be4935532241fa9520b3ad91e4',
 | 
				
			||||||
 | 
					            'info_dict': {
 | 
				
			||||||
 | 
					                'id': 'b0b40906854d11e4bdad0025900fea04',
 | 
				
			||||||
 | 
					                'ext': 'mp4',
 | 
				
			||||||
 | 
					                'title': 'Drtinová Veselovský TV 16. 12. 2014: Témata dne'
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }, {
 | 
				
			||||||
 | 
					            'md5': '5f7652a08b05009c1292317b449ffea2',
 | 
				
			||||||
 | 
					            'info_dict': {
 | 
				
			||||||
 | 
					                'id': '420ad9ec854a11e4bdad0025900fea04',
 | 
				
			||||||
 | 
					                'ext': 'mp4',
 | 
				
			||||||
 | 
					                'title': 'Školní masakr možná změní boj s Talibanem, říká novinářka'
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }, {
 | 
				
			||||||
 | 
					            'md5': '498eb9dfa97169f409126c617e2a3d64',
 | 
				
			||||||
 | 
					            'info_dict': {
 | 
				
			||||||
 | 
					                'id': '95d35580846a11e4b6d20025900fea04',
 | 
				
			||||||
 | 
					                'ext': 'mp4',
 | 
				
			||||||
 | 
					                'title': 'Boj o kliniku: Veřejný zájem, nebo právo na majetek?'
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }, {
 | 
				
			||||||
 | 
					            'md5': 'b8dc6b744844032dab6ba3781a7274b9',
 | 
				
			||||||
 | 
					            'info_dict': {
 | 
				
			||||||
 | 
					                'id': '6fe14d66853511e4833a0025900fea04',
 | 
				
			||||||
 | 
					                'ext': 'mp4',
 | 
				
			||||||
 | 
					                'title': 'Pánek: Odmítání syrských uprchlíků je ostudou české vlády'
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }],
 | 
				
			||||||
 | 
					    }, {
 | 
				
			||||||
 | 
					        'url': 'http://video.aktualne.cz/v-cechach-poprve-zazni-zelenkova-zrestaurovana-mse/r~45b4b00483ec11e4883b002590604f2e/',
 | 
				
			||||||
 | 
					        'only_matching': True,
 | 
				
			||||||
 | 
					    }]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _parse_video_metadata(self, js, video_id):
 | 
				
			||||||
 | 
					        metadata = self._parse_json(js, video_id, transform_source=js_to_json)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        formats = []
 | 
				
			||||||
 | 
					        for video in metadata['sources']:
 | 
				
			||||||
 | 
					            ext = video['type'][6:]
 | 
				
			||||||
 | 
					            formats.append({
 | 
				
			||||||
 | 
					                'url': video['file'],
 | 
				
			||||||
 | 
					                'ext': ext,
 | 
				
			||||||
 | 
					                'format_id': '%s-%s' % (ext, video['label']),
 | 
				
			||||||
 | 
					                'height': int(video['label'].rstrip('p')),
 | 
				
			||||||
 | 
					                'fps': 25,
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._sort_formats(formats)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            'id': metadata['mediaid'],
 | 
				
			||||||
 | 
					            'title': unescapeHTML(metadata['title']),
 | 
				
			||||||
 | 
					            'thumbnail': self._proto_relative_url(metadata['image'], 'http:'),
 | 
				
			||||||
 | 
					            'formats': formats
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
 | 
					        video_id = self._match_id(url)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        webpage = self._download_webpage(url, video_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # single video
 | 
				
			||||||
 | 
					        item = self._search_regex(
 | 
				
			||||||
 | 
					            r"(?s)embedData[0-9a-f]{32}\['asset'\]\s*=\s*(\{.+?\});",
 | 
				
			||||||
 | 
					            webpage, 'video', default=None, fatal=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if item:
 | 
				
			||||||
 | 
					            return self._parse_video_metadata(item, video_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # playlist
 | 
				
			||||||
 | 
					        items = re.findall(
 | 
				
			||||||
 | 
					            r"(?s)BBX\.context\.assets\['[0-9a-f]{32}'\]\.push\(({.+?})\);",
 | 
				
			||||||
 | 
					            webpage)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if items:
 | 
				
			||||||
 | 
					            return {
 | 
				
			||||||
 | 
					                '_type': 'playlist',
 | 
				
			||||||
 | 
					                'id': video_id,
 | 
				
			||||||
 | 
					                'title': self._og_search_title(webpage),
 | 
				
			||||||
 | 
					                'entries': [self._parse_video_metadata(i, video_id) for i in items]
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        raise ExtractorError('Could not find neither video nor playlist')
 | 
				
			||||||
							
								
								
									
										46
									
								
								youtube_dl/extractor/echomsk.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								youtube_dl/extractor/echomsk.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
				
			|||||||
 | 
					# coding: utf-8
 | 
				
			||||||
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import re
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .common import InfoExtractor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class EchoMskIE(InfoExtractor):
 | 
				
			||||||
 | 
					    _VALID_URL = r'http://(?:www\.)?echo\.msk\.ru/sounds/(?P<id>\d+)'
 | 
				
			||||||
 | 
					    _TEST = {
 | 
				
			||||||
 | 
					        'url': 'http://www.echo.msk.ru/sounds/1464134.html',
 | 
				
			||||||
 | 
					        'md5': '2e44b3b78daff5b458e4dbc37f191f7c',
 | 
				
			||||||
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'id': '1464134',
 | 
				
			||||||
 | 
					            'ext': 'mp3',
 | 
				
			||||||
 | 
					            'title': 'Особое мнение - 29 декабря 2014, 19:08',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
 | 
					        video_id = self._match_id(url)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        webpage = self._download_webpage(url, video_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        audio_url = self._search_regex(
 | 
				
			||||||
 | 
					            r'<a rel="mp3" href="([^"]+)">', webpage, 'audio URL')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        title = self._html_search_regex(
 | 
				
			||||||
 | 
					            r'<a href="/programs/[^"]+" target="_blank">([^<]+)</a>',
 | 
				
			||||||
 | 
					            webpage, 'title')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        air_date = self._html_search_regex(
 | 
				
			||||||
 | 
					            r'(?s)<div class="date">(.+?)</div>',
 | 
				
			||||||
 | 
					            webpage, 'date', fatal=False, default=None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if air_date:
 | 
				
			||||||
 | 
					            air_date = re.sub(r'(\s)\1+', r'\1', air_date)
 | 
				
			||||||
 | 
					            if air_date:
 | 
				
			||||||
 | 
					                title = '%s - %s' % (title, air_date)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            'id': video_id,
 | 
				
			||||||
 | 
					            'url': audio_url,
 | 
				
			||||||
 | 
					            'title': title,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
@@ -9,6 +9,9 @@ from .common import InfoExtractor
 | 
				
			|||||||
from ..compat import (
 | 
					from ..compat import (
 | 
				
			||||||
    compat_str,
 | 
					    compat_str,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					from ..utils import (
 | 
				
			||||||
 | 
					    ExtractorError,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class EightTracksIE(InfoExtractor):
 | 
					class EightTracksIE(InfoExtractor):
 | 
				
			||||||
@@ -112,14 +115,29 @@ class EightTracksIE(InfoExtractor):
 | 
				
			|||||||
        session = str(random.randint(0, 1000000000))
 | 
					        session = str(random.randint(0, 1000000000))
 | 
				
			||||||
        mix_id = data['id']
 | 
					        mix_id = data['id']
 | 
				
			||||||
        track_count = data['tracks_count']
 | 
					        track_count = data['tracks_count']
 | 
				
			||||||
 | 
					        duration = data['duration']
 | 
				
			||||||
 | 
					        avg_song_duration = float(duration) / track_count
 | 
				
			||||||
        first_url = 'http://8tracks.com/sets/%s/play?player=sm&mix_id=%s&format=jsonh' % (session, mix_id)
 | 
					        first_url = 'http://8tracks.com/sets/%s/play?player=sm&mix_id=%s&format=jsonh' % (session, mix_id)
 | 
				
			||||||
        next_url = first_url
 | 
					        next_url = first_url
 | 
				
			||||||
        entries = []
 | 
					        entries = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for i in range(track_count):
 | 
					        for i in range(track_count):
 | 
				
			||||||
            api_json = self._download_webpage(
 | 
					            api_json = None
 | 
				
			||||||
                next_url, playlist_id,
 | 
					            download_tries = 0
 | 
				
			||||||
                note='Downloading song information %d/%d' % (i + 1, track_count),
 | 
					
 | 
				
			||||||
                errnote='Failed to download song information')
 | 
					            while api_json is None:
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    api_json = self._download_webpage(
 | 
				
			||||||
 | 
					                        next_url, playlist_id,
 | 
				
			||||||
 | 
					                        note='Downloading song information %d/%d' % (i + 1, track_count),
 | 
				
			||||||
 | 
					                        errnote='Failed to download song information')
 | 
				
			||||||
 | 
					                except ExtractorError:
 | 
				
			||||||
 | 
					                    if download_tries > 3:
 | 
				
			||||||
 | 
					                        raise
 | 
				
			||||||
 | 
					                    else:
 | 
				
			||||||
 | 
					                        download_tries += 1
 | 
				
			||||||
 | 
					                        self._sleep(avg_song_duration, playlist_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            api_data = json.loads(api_json)
 | 
					            api_data = json.loads(api_json)
 | 
				
			||||||
            track_data = api_data['set']['track']
 | 
					            track_data = api_data['set']['track']
 | 
				
			||||||
            info = {
 | 
					            info = {
 | 
				
			||||||
@@ -131,6 +149,7 @@ class EightTracksIE(InfoExtractor):
 | 
				
			|||||||
                'ext': 'm4a',
 | 
					                'ext': 'm4a',
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            entries.append(info)
 | 
					            entries.append(info)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            next_url = 'http://8tracks.com/sets/%s/next?player=sm&mix_id=%s&format=jsonh&track_id=%s' % (
 | 
					            next_url = 'http://8tracks.com/sets/%s/next?player=sm&mix_id=%s&format=jsonh&track_id=%s' % (
 | 
				
			||||||
                session, mix_id, track_data['id'])
 | 
					                session, mix_id, track_data['id'])
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,6 @@
 | 
				
			|||||||
# coding: utf-8
 | 
					# coding: utf-8
 | 
				
			||||||
from __future__ import unicode_literals
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import re
 | 
					 | 
				
			||||||
import json
 | 
					import json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .common import InfoExtractor
 | 
					from .common import InfoExtractor
 | 
				
			||||||
@@ -12,32 +11,49 @@ from ..utils import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class EllenTVIE(InfoExtractor):
 | 
					class EllenTVIE(InfoExtractor):
 | 
				
			||||||
    _VALID_URL = r'https?://(?:www\.)?ellentv\.com/videos/(?P<id>[a-z0-9_-]+)'
 | 
					    _VALID_URL = r'https?://(?:www\.)?(?:ellentv|ellentube)\.com/videos/(?P<id>[a-z0-9_-]+)'
 | 
				
			||||||
    _TEST = {
 | 
					    _TESTS = [{
 | 
				
			||||||
        'url': 'http://www.ellentv.com/videos/0-7jqrsr18/',
 | 
					        'url': 'http://www.ellentv.com/videos/0-7jqrsr18/',
 | 
				
			||||||
        'md5': 'e4af06f3bf0d5f471921a18db5764642',
 | 
					        'md5': 'e4af06f3bf0d5f471921a18db5764642',
 | 
				
			||||||
        'info_dict': {
 | 
					        'info_dict': {
 | 
				
			||||||
            'id': '0-7jqrsr18',
 | 
					            'id': '0-7jqrsr18',
 | 
				
			||||||
            'ext': 'mp4',
 | 
					            'ext': 'mp4',
 | 
				
			||||||
            'title': 'What\'s Wrong with These Photos? A Whole Lot',
 | 
					            'title': 'What\'s Wrong with These Photos? A Whole Lot',
 | 
				
			||||||
 | 
					            'description': 'md5:35f152dc66b587cf13e6d2cf4fa467f6',
 | 
				
			||||||
            'timestamp': 1406876400,
 | 
					            'timestamp': 1406876400,
 | 
				
			||||||
            'upload_date': '20140801',
 | 
					            'upload_date': '20140801',
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }, {
 | 
				
			||||||
 | 
					        'url': 'http://ellentube.com/videos/0-dvzmabd5/',
 | 
				
			||||||
 | 
					        'md5': '98238118eaa2bbdf6ad7f708e3e4f4eb',
 | 
				
			||||||
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'id': '0-dvzmabd5',
 | 
				
			||||||
 | 
					            'ext': 'mp4',
 | 
				
			||||||
 | 
					            'title': '1 year old twin sister makes her brother laugh',
 | 
				
			||||||
 | 
					            'description': '1 year old twin sister makes her brother laugh',
 | 
				
			||||||
 | 
					            'timestamp': 1419542075,
 | 
				
			||||||
 | 
					            'upload_date': '20141225',
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _real_extract(self, url):
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
        mobj = re.match(self._VALID_URL, url)
 | 
					        video_id = self._match_id(url)
 | 
				
			||||||
        video_id = mobj.group('id')
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        webpage = self._download_webpage(url, video_id)
 | 
					        webpage = self._download_webpage(url, video_id)
 | 
				
			||||||
 | 
					        video_url = self._html_search_meta('VideoURL', webpage, 'url')
 | 
				
			||||||
 | 
					        title = self._og_search_title(webpage, default=None) or self._search_regex(
 | 
				
			||||||
 | 
					            r'pageName\s*=\s*"([^"]+)"', webpage, 'title')
 | 
				
			||||||
 | 
					        description = self._html_search_meta(
 | 
				
			||||||
 | 
					            'description', webpage, 'description') or self._og_search_description(webpage)
 | 
				
			||||||
        timestamp = parse_iso8601(self._search_regex(
 | 
					        timestamp = parse_iso8601(self._search_regex(
 | 
				
			||||||
            r'<span class="publish-date"><time datetime="([^"]+)">',
 | 
					            r'<span class="publish-date"><time datetime="([^"]+)">',
 | 
				
			||||||
            webpage, 'timestamp'))
 | 
					            webpage, 'timestamp'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            'id': video_id,
 | 
					            'id': video_id,
 | 
				
			||||||
            'title': self._og_search_title(webpage),
 | 
					            'url': video_url,
 | 
				
			||||||
            'url': self._html_search_meta('VideoURL', webpage, 'url'),
 | 
					            'title': title,
 | 
				
			||||||
 | 
					            'description': description,
 | 
				
			||||||
            'timestamp': timestamp,
 | 
					            'timestamp': timestamp,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -55,8 +71,7 @@ class EllenTVClipsIE(InfoExtractor):
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _real_extract(self, url):
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
        mobj = re.match(self._VALID_URL, url)
 | 
					        playlist_id = self._match_id(url)
 | 
				
			||||||
        playlist_id = mobj.group('id')
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        webpage = self._download_webpage(url, playlist_id)
 | 
					        webpage = self._download_webpage(url, playlist_id)
 | 
				
			||||||
        playlist = self._extract_playlist(webpage)
 | 
					        playlist = self._extract_playlist(webpage)
 | 
				
			||||||
 
 | 
				
			|||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user